xref: /openbsd-src/gnu/usr.bin/cvs/diff/diff.c (revision c71bc7e269286e43816004eb0fcd7a55f036cd69)
12286d8edStholo /* GNU DIFF entry routine.
2b2346922Stholo    Copyright (C) 1988, 1989, 1992, 1993, 1994, 1997, 1998 Free Software Foundation, Inc.
32286d8edStholo 
42286d8edStholo This file is part of GNU DIFF.
52286d8edStholo 
62286d8edStholo GNU DIFF is free software; you can redistribute it and/or modify
72286d8edStholo it under the terms of the GNU General Public License as published by
82286d8edStholo the Free Software Foundation; either version 2, or (at your option)
92286d8edStholo any later version.
102286d8edStholo 
112286d8edStholo GNU DIFF is distributed in the hope that it will be useful,
122286d8edStholo but WITHOUT ANY WARRANTY; without even the implied warranty of
132286d8edStholo MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
142286d8edStholo GNU General Public License for more details.
152286d8edStholo 
16*c71bc7e2Stholo */
172286d8edStholo 
182286d8edStholo /* GNU DIFF was written by Mike Haertel, David Hayes,
192286d8edStholo    Richard Stallman, Len Tower, and Paul Eggert.  */
202286d8edStholo 
212286d8edStholo #define GDIFF_MAIN
222286d8edStholo #include "diff.h"
232286d8edStholo #include <signal.h>
242286d8edStholo #include "getopt.h"
252286d8edStholo #include "fnmatch.h"
262286d8edStholo 
272286d8edStholo #ifndef DEFAULT_WIDTH
282286d8edStholo #define DEFAULT_WIDTH 130
292286d8edStholo #endif
302286d8edStholo 
312286d8edStholo #ifndef GUTTER_WIDTH_MINIMUM
322286d8edStholo #define GUTTER_WIDTH_MINIMUM 3
332286d8edStholo #endif
342286d8edStholo 
352286d8edStholo /* diff.c has a real initialize_main function. */
362286d8edStholo #ifdef initialize_main
372286d8edStholo #undef initialize_main
382286d8edStholo #endif
392286d8edStholo 
402286d8edStholo static char const *filetype PARAMS((struct stat const *));
412286d8edStholo static char *option_list PARAMS((char **, int));
422286d8edStholo static int add_exclude_file PARAMS((char const *));
432286d8edStholo static int ck_atoi PARAMS((char const *, int *));
442286d8edStholo static int compare_files PARAMS((char const *, char const *, char const *, char const *, int));
452286d8edStholo static int specify_format PARAMS((char **, char *));
462286d8edStholo static void add_exclude PARAMS((char const *));
472286d8edStholo static void add_regexp PARAMS((struct regexp_list **, char const *));
482286d8edStholo static void specify_style PARAMS((enum output_style));
492286d8edStholo static int try_help PARAMS((char const *));
502286d8edStholo static void check_output PARAMS((FILE *));
512286d8edStholo static void usage PARAMS((void));
522286d8edStholo static void initialize_main PARAMS((int *, char ***));
532286d8edStholo 
542286d8edStholo /* Nonzero for -r: if comparing two directories,
552286d8edStholo    compare their common subdirectories recursively.  */
562286d8edStholo 
572286d8edStholo static int recursive;
582286d8edStholo 
592286d8edStholo /* For debugging: don't do discard_confusing_lines.  */
602286d8edStholo 
612286d8edStholo int no_discards;
622286d8edStholo 
632286d8edStholo #if HAVE_SETMODE
642286d8edStholo /* I/O mode: nonzero only if using binary input/output.  */
652286d8edStholo static int binary_I_O;
662286d8edStholo #endif
672286d8edStholo 
682286d8edStholo /* Return a string containing the command options with which diff was invoked.
692286d8edStholo    Spaces appear between what were separate ARGV-elements.
702286d8edStholo    There is a space at the beginning but none at the end.
712286d8edStholo    If there were no options, the result is an empty string.
722286d8edStholo 
732286d8edStholo    Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT,
742286d8edStholo    the length of that vector.  */
752286d8edStholo 
762286d8edStholo static char *
772286d8edStholo option_list (optionvec, count)
782286d8edStholo      char **optionvec;  /* Was `vector', but that collides on Alliant.  */
792286d8edStholo      int count;
802286d8edStholo {
812286d8edStholo   int i;
822286d8edStholo   size_t length = 0;
832286d8edStholo   char *result;
842286d8edStholo 
852286d8edStholo   for (i = 0; i < count; i++)
862286d8edStholo     length += strlen (optionvec[i]) + 1;
872286d8edStholo 
882286d8edStholo   result = xmalloc (length + 1);
892286d8edStholo   result[0] = 0;
902286d8edStholo 
912286d8edStholo   for (i = 0; i < count; i++)
922286d8edStholo     {
932286d8edStholo       strcat (result, " ");
942286d8edStholo       strcat (result, optionvec[i]);
952286d8edStholo     }
962286d8edStholo 
972286d8edStholo   return result;
982286d8edStholo }
992286d8edStholo 
1002286d8edStholo /* Convert STR to a positive integer, storing the result in *OUT.
1012286d8edStholo    If STR is not a valid integer, return -1 (otherwise 0). */
1022286d8edStholo static int
1032286d8edStholo ck_atoi (str, out)
1042286d8edStholo      char const *str;
1052286d8edStholo      int *out;
1062286d8edStholo {
1072286d8edStholo   char const *p;
1082286d8edStholo   for (p = str; *p; p++)
1092286d8edStholo     if (*p < '0' || *p > '9')
1102286d8edStholo       return -1;
1112286d8edStholo 
1122286d8edStholo   *out = atoi (optarg);
1132286d8edStholo   return 0;
1142286d8edStholo }
1152286d8edStholo 
1162286d8edStholo /* Keep track of excluded file name patterns.  */
1172286d8edStholo 
1182286d8edStholo static char const **exclude;
1192286d8edStholo static int exclude_alloc, exclude_count;
1202286d8edStholo 
1212286d8edStholo int
1222286d8edStholo excluded_filename (f)
1232286d8edStholo      char const *f;
1242286d8edStholo {
1252286d8edStholo   int i;
1262286d8edStholo   for (i = 0;  i < exclude_count;  i++)
1272286d8edStholo     if (fnmatch (exclude[i], f, 0) == 0)
1282286d8edStholo       return 1;
1292286d8edStholo   return 0;
1302286d8edStholo }
1312286d8edStholo 
1322286d8edStholo static void
1332286d8edStholo add_exclude (pattern)
1342286d8edStholo      char const *pattern;
1352286d8edStholo {
1362286d8edStholo   if (exclude_alloc <= exclude_count)
1372286d8edStholo     exclude = (char const **)
1382286d8edStholo 	      (exclude_alloc == 0
1392286d8edStholo 	       ? xmalloc ((exclude_alloc = 64) * sizeof (*exclude))
1402286d8edStholo 	       : xrealloc (exclude, (exclude_alloc *= 2) * sizeof (*exclude)));
1412286d8edStholo 
1422286d8edStholo   exclude[exclude_count++] = pattern;
1432286d8edStholo }
1442286d8edStholo 
1452286d8edStholo static int
1462286d8edStholo add_exclude_file (name)
1472286d8edStholo      char const *name;
1482286d8edStholo {
1492286d8edStholo   struct file_data f;
1502286d8edStholo   char *p, *q, *lim;
1512286d8edStholo 
1522286d8edStholo   f.name = optarg;
1532286d8edStholo   f.desc = (strcmp (optarg, "-") == 0
1542286d8edStholo 	    ? STDIN_FILENO
1552286d8edStholo 	    : open (optarg, O_RDONLY, 0));
1562286d8edStholo   if (f.desc < 0 || fstat (f.desc, &f.stat) != 0)
1572286d8edStholo     return -1;
1582286d8edStholo 
1592286d8edStholo   sip (&f, 1);
1602286d8edStholo   slurp (&f);
1612286d8edStholo 
1622286d8edStholo   for (p = f.buffer, lim = p + f.buffered_chars;  p < lim;  p = q)
1632286d8edStholo     {
1642286d8edStholo       q = (char *) memchr (p, '\n', lim - p);
1652286d8edStholo       if (!q)
1662286d8edStholo 	q = lim;
1672286d8edStholo       *q++ = 0;
1682286d8edStholo       add_exclude (p);
1692286d8edStholo     }
1702286d8edStholo 
1712286d8edStholo   return close (f.desc);
1722286d8edStholo }
1732286d8edStholo 
1742286d8edStholo /* The numbers 129- that appear in the fourth element of some entries
1752286d8edStholo    tell the big switch in `diff_run' how to process those options.  */
1762286d8edStholo 
1772286d8edStholo static struct option const longopts[] =
1782286d8edStholo {
1792286d8edStholo   {"ignore-blank-lines", 0, 0, 'B'},
1802286d8edStholo   {"context", 2, 0, 'C'},
1812286d8edStholo   {"ifdef", 1, 0, 'D'},
1822286d8edStholo   {"show-function-line", 1, 0, 'F'},
1832286d8edStholo   {"speed-large-files", 0, 0, 'H'},
1842286d8edStholo   {"ignore-matching-lines", 1, 0, 'I'},
1852286d8edStholo   {"label", 1, 0, 'L'},
1862286d8edStholo   {"file-label", 1, 0, 'L'},	/* An alias, no longer recommended */
1872286d8edStholo   {"new-file", 0, 0, 'N'},
1882286d8edStholo   {"entire-new-file", 0, 0, 'N'},	/* An alias, no longer recommended */
1892286d8edStholo   {"unidirectional-new-file", 0, 0, 'P'},
1902286d8edStholo   {"starting-file", 1, 0, 'S'},
1912286d8edStholo   {"initial-tab", 0, 0, 'T'},
1922286d8edStholo   {"width", 1, 0, 'W'},
1932286d8edStholo   {"text", 0, 0, 'a'},
1942286d8edStholo   {"ascii", 0, 0, 'a'},		/* An alias, no longer recommended */
1952286d8edStholo   {"ignore-space-change", 0, 0, 'b'},
1962286d8edStholo   {"minimal", 0, 0, 'd'},
1972286d8edStholo   {"ed", 0, 0, 'e'},
1982286d8edStholo   {"forward-ed", 0, 0, 'f'},
1992286d8edStholo   {"ignore-case", 0, 0, 'i'},
2002286d8edStholo   {"paginate", 0, 0, 'l'},
2012286d8edStholo   {"print", 0, 0, 'l'},		/* An alias, no longer recommended */
2022286d8edStholo   {"rcs", 0, 0, 'n'},
2032286d8edStholo   {"show-c-function", 0, 0, 'p'},
2042286d8edStholo   {"brief", 0, 0, 'q'},
2052286d8edStholo   {"recursive", 0, 0, 'r'},
2062286d8edStholo   {"report-identical-files", 0, 0, 's'},
2072286d8edStholo   {"expand-tabs", 0, 0, 't'},
2082286d8edStholo   {"version", 0, 0, 'v'},
2092286d8edStholo   {"ignore-all-space", 0, 0, 'w'},
2102286d8edStholo   {"exclude", 1, 0, 'x'},
2112286d8edStholo   {"exclude-from", 1, 0, 'X'},
2122286d8edStholo   {"side-by-side", 0, 0, 'y'},
2132286d8edStholo   {"unified", 2, 0, 'U'},
2142286d8edStholo   {"left-column", 0, 0, 129},
2152286d8edStholo   {"suppress-common-lines", 0, 0, 130},
2162286d8edStholo   {"sdiff-merge-assist", 0, 0, 131},
2172286d8edStholo   {"old-line-format", 1, 0, 132},
2182286d8edStholo   {"new-line-format", 1, 0, 133},
2192286d8edStholo   {"unchanged-line-format", 1, 0, 134},
2202286d8edStholo   {"line-format", 1, 0, 135},
2212286d8edStholo   {"old-group-format", 1, 0, 136},
2222286d8edStholo   {"new-group-format", 1, 0, 137},
2232286d8edStholo   {"unchanged-group-format", 1, 0, 138},
2242286d8edStholo   {"changed-group-format", 1, 0, 139},
2252286d8edStholo   {"horizon-lines", 1, 0, 140},
2262286d8edStholo   {"help", 0, 0, 141},
2272286d8edStholo   {"binary", 0, 0, 142},
2282286d8edStholo   {0, 0, 0, 0}
2292286d8edStholo };
2302286d8edStholo 
2312286d8edStholo int
232b2346922Stholo diff_run (argc, argv, out, callbacks_arg)
2332286d8edStholo      int argc;
2342286d8edStholo      char *argv[];
2352286d8edStholo      char *out;
236b2346922Stholo      const struct diff_callbacks *callbacks_arg;
2372286d8edStholo {
2382286d8edStholo   int val;
2392286d8edStholo   int c;
2402286d8edStholo   int prev = -1;
2412286d8edStholo   int width = DEFAULT_WIDTH;
2422286d8edStholo   int show_c_function = 0;
2432286d8edStholo   int optind_old;
244b2346922Stholo   int opened_file = 0;
245b2346922Stholo 
246b2346922Stholo   callbacks = callbacks_arg;
2472286d8edStholo 
2482286d8edStholo   /* Do our initializations.  */
2492286d8edStholo   initialize_main (&argc, &argv);
2502286d8edStholo 
2512286d8edStholo   /* Decode the options.  */
2522286d8edStholo 
2532286d8edStholo   optind_old = optind;
2542286d8edStholo   optind = 0;
2552286d8edStholo   while ((c = getopt_long (argc, argv,
2562286d8edStholo 			   "0123456789abBcC:dD:efF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:y",
2572286d8edStholo 			   longopts, 0)) != EOF)
2582286d8edStholo     {
2592286d8edStholo       switch (c)
2602286d8edStholo 	{
2612286d8edStholo 	  /* All digits combine in decimal to specify the context-size.  */
2622286d8edStholo 	case '1':
2632286d8edStholo 	case '2':
2642286d8edStholo 	case '3':
2652286d8edStholo 	case '4':
2662286d8edStholo 	case '5':
2672286d8edStholo 	case '6':
2682286d8edStholo 	case '7':
2692286d8edStholo 	case '8':
2702286d8edStholo 	case '9':
2712286d8edStholo 	case '0':
2722286d8edStholo 	  if (context == -1)
2732286d8edStholo 	    context = 0;
2742286d8edStholo 	  /* If a context length has already been specified,
2752286d8edStholo 	     more digits allowed only if they follow right after the others.
2762286d8edStholo 	     Reject two separate runs of digits, or digits after -C.  */
2772286d8edStholo 	  else if (prev < '0' || prev > '9')
2782286d8edStholo 	    fatal ("context length specified twice");
2792286d8edStholo 
2802286d8edStholo 	  context = context * 10 + c - '0';
2812286d8edStholo 	  break;
2822286d8edStholo 
2832286d8edStholo 	case 'a':
2842286d8edStholo 	  /* Treat all files as text files; never treat as binary.  */
2852286d8edStholo 	  always_text_flag = 1;
2862286d8edStholo 	  break;
2872286d8edStholo 
2882286d8edStholo 	case 'b':
2892286d8edStholo 	  /* Ignore changes in amount of white space.  */
2902286d8edStholo 	  ignore_space_change_flag = 1;
2912286d8edStholo 	  ignore_some_changes = 1;
2922286d8edStholo 	  ignore_some_line_changes = 1;
2932286d8edStholo 	  break;
2942286d8edStholo 
2952286d8edStholo 	case 'B':
2962286d8edStholo 	  /* Ignore changes affecting only blank lines.  */
2972286d8edStholo 	  ignore_blank_lines_flag = 1;
2982286d8edStholo 	  ignore_some_changes = 1;
2992286d8edStholo 	  break;
3002286d8edStholo 
3012286d8edStholo 	case 'C':		/* +context[=lines] */
3022286d8edStholo 	case 'U':		/* +unified[=lines] */
3032286d8edStholo 	  if (optarg)
3042286d8edStholo 	    {
3052286d8edStholo 	      if (context >= 0)
3062286d8edStholo 		fatal ("context length specified twice");
3072286d8edStholo 
3082286d8edStholo 	      if (ck_atoi (optarg, &context))
3092286d8edStholo 		fatal ("invalid context length argument");
3102286d8edStholo 	    }
3112286d8edStholo 
3122286d8edStholo 	  /* Falls through.  */
3132286d8edStholo 	case 'c':
3142286d8edStholo 	  /* Make context-style output.  */
3152286d8edStholo 	  specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT);
3162286d8edStholo 	  break;
3172286d8edStholo 
3182286d8edStholo 	case 'd':
3192286d8edStholo 	  /* Don't discard lines.  This makes things slower (sometimes much
3202286d8edStholo 	     slower) but will find a guaranteed minimal set of changes.  */
3212286d8edStholo 	  no_discards = 1;
3222286d8edStholo 	  break;
3232286d8edStholo 
3242286d8edStholo 	case 'D':
3252286d8edStholo 	  /* Make merged #ifdef output.  */
3262286d8edStholo 	  specify_style (OUTPUT_IFDEF);
3272286d8edStholo 	  {
3282286d8edStholo 	    int i, err = 0;
3292286d8edStholo 	    static char const C_ifdef_group_formats[] =
3302286d8edStholo 	      "#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";
3312286d8edStholo 	    char *b = xmalloc (sizeof (C_ifdef_group_formats)
3322286d8edStholo 			       + 7 * strlen(optarg) - 14 /* 7*"%s" */
3332286d8edStholo 			       - 8 /* 5*"%%" + 3*"%c" */);
3342286d8edStholo 	    sprintf (b, C_ifdef_group_formats,
3352286d8edStholo 		     optarg, optarg, 0,
3362286d8edStholo 		     optarg, optarg, 0, 0,
3372286d8edStholo 		     optarg, optarg, optarg);
3382286d8edStholo 	    for (i = 0; i < 4; i++)
3392286d8edStholo 	      {
3402286d8edStholo 		err |= specify_format (&group_format[i], b);
3412286d8edStholo 		b += strlen (b) + 1;
3422286d8edStholo 	      }
3432286d8edStholo 	    if (err)
3442286d8edStholo 	      diff_error ("conflicting #ifdef formats", 0, 0);
3452286d8edStholo 	  }
3462286d8edStholo 	  break;
3472286d8edStholo 
3482286d8edStholo 	case 'e':
3492286d8edStholo 	  /* Make output that is a valid `ed' script.  */
3502286d8edStholo 	  specify_style (OUTPUT_ED);
3512286d8edStholo 	  break;
3522286d8edStholo 
3532286d8edStholo 	case 'f':
3542286d8edStholo 	  /* Make output that looks vaguely like an `ed' script
3552286d8edStholo 	     but has changes in the order they appear in the file.  */
3562286d8edStholo 	  specify_style (OUTPUT_FORWARD_ED);
3572286d8edStholo 	  break;
3582286d8edStholo 
3592286d8edStholo 	case 'F':
3602286d8edStholo 	  /* Show, for each set of changes, the previous line that
3612286d8edStholo 	     matches the specified regexp.  Currently affects only
3622286d8edStholo 	     context-style output.  */
3632286d8edStholo 	  add_regexp (&function_regexp_list, optarg);
3642286d8edStholo 	  break;
3652286d8edStholo 
3662286d8edStholo 	case 'h':
3672286d8edStholo 	  /* Split the files into chunks of around 1500 lines
3682286d8edStholo 	     for faster processing.  Usually does not change the result.
3692286d8edStholo 
3702286d8edStholo 	     This currently has no effect.  */
3712286d8edStholo 	  break;
3722286d8edStholo 
3732286d8edStholo 	case 'H':
3742286d8edStholo 	  /* Turn on heuristics that speed processing of large files
3752286d8edStholo 	     with a small density of changes.  */
3762286d8edStholo 	  heuristic = 1;
3772286d8edStholo 	  break;
3782286d8edStholo 
3792286d8edStholo 	case 'i':
3802286d8edStholo 	  /* Ignore changes in case.  */
3812286d8edStholo 	  ignore_case_flag = 1;
3822286d8edStholo 	  ignore_some_changes = 1;
3832286d8edStholo 	  ignore_some_line_changes = 1;
3842286d8edStholo 	  break;
3852286d8edStholo 
3862286d8edStholo 	case 'I':
3872286d8edStholo 	  /* Ignore changes affecting only lines that match the
3882286d8edStholo 	     specified regexp.  */
3892286d8edStholo 	  add_regexp (&ignore_regexp_list, optarg);
3902286d8edStholo 	  ignore_some_changes = 1;
3912286d8edStholo 	  break;
3922286d8edStholo 
3932286d8edStholo 	case 'l':
3942286d8edStholo 	  /* Pass the output through `pr' to paginate it.  */
3952286d8edStholo 	  paginate_flag = 1;
3962286d8edStholo #if !defined(SIGCHLD) && defined(SIGCLD)
3972286d8edStholo #define SIGCHLD SIGCLD
3982286d8edStholo #endif
3992286d8edStholo #ifdef SIGCHLD
4002286d8edStholo 	  /* Pagination requires forking and waiting, and
4012286d8edStholo 	     System V fork+wait does not work if SIGCHLD is ignored.  */
4022286d8edStholo 	  signal (SIGCHLD, SIG_DFL);
4032286d8edStholo #endif
4042286d8edStholo 	  break;
4052286d8edStholo 
4062286d8edStholo 	case 'L':
4072286d8edStholo 	  /* Specify file labels for `-c' output headers.  */
4082286d8edStholo 	  if (!file_label[0])
4092286d8edStholo 	    file_label[0] = optarg;
4102286d8edStholo 	  else if (!file_label[1])
4112286d8edStholo 	    file_label[1] = optarg;
4122286d8edStholo 	  else
4132286d8edStholo 	    fatal ("too many file label options");
4142286d8edStholo 	  break;
4152286d8edStholo 
4162286d8edStholo 	case 'n':
4172286d8edStholo 	  /* Output RCS-style diffs, like `-f' except that each command
4182286d8edStholo 	     specifies the number of lines affected.  */
4192286d8edStholo 	  specify_style (OUTPUT_RCS);
4202286d8edStholo 	  break;
4212286d8edStholo 
4222286d8edStholo 	case 'N':
4232286d8edStholo 	  /* When comparing directories, if a file appears only in one
4242286d8edStholo 	     directory, treat it as present but empty in the other.  */
4252286d8edStholo 	  entire_new_file_flag = 1;
4262286d8edStholo 	  break;
4272286d8edStholo 
4282286d8edStholo 	case 'p':
4292286d8edStholo 	  /* Make context-style output and show name of last C function.  */
4302286d8edStholo 	  show_c_function = 1;
4312286d8edStholo 	  add_regexp (&function_regexp_list, "^[_a-zA-Z$]");
4322286d8edStholo 	  break;
4332286d8edStholo 
4342286d8edStholo 	case 'P':
4352286d8edStholo 	  /* When comparing directories, if a file appears only in
4362286d8edStholo 	     the second directory of the two,
4372286d8edStholo 	     treat it as present but empty in the other.  */
4382286d8edStholo 	  unidirectional_new_file_flag = 1;
4392286d8edStholo 	  break;
4402286d8edStholo 
4412286d8edStholo 	case 'q':
4422286d8edStholo 	  no_details_flag = 1;
4432286d8edStholo 	  break;
4442286d8edStholo 
4452286d8edStholo 	case 'r':
4462286d8edStholo 	  /* When comparing directories,
4472286d8edStholo 	     recursively compare any subdirectories found.  */
4482286d8edStholo 	  recursive = 1;
4492286d8edStholo 	  break;
4502286d8edStholo 
4512286d8edStholo 	case 's':
4522286d8edStholo 	  /* Print a message if the files are the same.  */
4532286d8edStholo 	  print_file_same_flag = 1;
4542286d8edStholo 	  break;
4552286d8edStholo 
4562286d8edStholo 	case 'S':
4572286d8edStholo 	  /* When comparing directories, start with the specified
4582286d8edStholo 	     file name.  This is used for resuming an aborted comparison.  */
4592286d8edStholo 	  dir_start_file = optarg;
4602286d8edStholo 	  break;
4612286d8edStholo 
4622286d8edStholo 	case 't':
4632286d8edStholo 	  /* Expand tabs to spaces in the output so that it preserves
4642286d8edStholo 	     the alignment of the input files.  */
4652286d8edStholo 	  tab_expand_flag = 1;
4662286d8edStholo 	  break;
4672286d8edStholo 
4682286d8edStholo 	case 'T':
4692286d8edStholo 	  /* Use a tab in the output, rather than a space, before the
4702286d8edStholo 	     text of an input line, so as to keep the proper alignment
4712286d8edStholo 	     in the input line without changing the characters in it.  */
4722286d8edStholo 	  tab_align_flag = 1;
4732286d8edStholo 	  break;
4742286d8edStholo 
4752286d8edStholo 	case 'u':
4762286d8edStholo 	  /* Output the context diff in unidiff format.  */
4772286d8edStholo 	  specify_style (OUTPUT_UNIFIED);
4782286d8edStholo 	  break;
4792286d8edStholo 
4802286d8edStholo 	case 'v':
481b2346922Stholo 	  if (callbacks && callbacks->write_stdout)
482b2346922Stholo 	    {
483b2346922Stholo 	      (*callbacks->write_stdout) ("diff - GNU diffutils version ");
484b2346922Stholo 	      (*callbacks->write_stdout) (diff_version_string);
485b2346922Stholo 	      (*callbacks->write_stdout) ("\n");
486b2346922Stholo 	    }
487b2346922Stholo 	  else
4882286d8edStholo 	    printf ("diff - GNU diffutils version %s\n", diff_version_string);
4892286d8edStholo 	  return 0;
4902286d8edStholo 
4912286d8edStholo 	case 'w':
4922286d8edStholo 	  /* Ignore horizontal white space when comparing lines.  */
4932286d8edStholo 	  ignore_all_space_flag = 1;
4942286d8edStholo 	  ignore_some_changes = 1;
4952286d8edStholo 	  ignore_some_line_changes = 1;
4962286d8edStholo 	  break;
4972286d8edStholo 
4982286d8edStholo 	case 'x':
4992286d8edStholo 	  add_exclude (optarg);
5002286d8edStholo 	  break;
5012286d8edStholo 
5022286d8edStholo 	case 'X':
5032286d8edStholo 	  if (add_exclude_file (optarg) != 0)
5042286d8edStholo 	    pfatal_with_name (optarg);
5052286d8edStholo 	  break;
5062286d8edStholo 
5072286d8edStholo 	case 'y':
5082286d8edStholo 	  /* Use side-by-side (sdiff-style) columnar output. */
5092286d8edStholo 	  specify_style (OUTPUT_SDIFF);
5102286d8edStholo 	  break;
5112286d8edStholo 
5122286d8edStholo 	case 'W':
5132286d8edStholo 	  /* Set the line width for OUTPUT_SDIFF.  */
5142286d8edStholo 	  if (ck_atoi (optarg, &width) || width <= 0)
5152286d8edStholo 	    fatal ("column width must be a positive integer");
5162286d8edStholo 	  break;
5172286d8edStholo 
5182286d8edStholo 	case 129:
5192286d8edStholo 	  sdiff_left_only = 1;
5202286d8edStholo 	  break;
5212286d8edStholo 
5222286d8edStholo 	case 130:
5232286d8edStholo 	  sdiff_skip_common_lines = 1;
5242286d8edStholo 	  break;
5252286d8edStholo 
5262286d8edStholo 	case 131:
5272286d8edStholo 	  /* sdiff-style columns output. */
5282286d8edStholo 	  specify_style (OUTPUT_SDIFF);
5292286d8edStholo 	  sdiff_help_sdiff = 1;
5302286d8edStholo 	  break;
5312286d8edStholo 
5322286d8edStholo 	case 132:
5332286d8edStholo 	case 133:
5342286d8edStholo 	case 134:
5352286d8edStholo 	  specify_style (OUTPUT_IFDEF);
5362286d8edStholo 	  if (specify_format (&line_format[c - 132], optarg) != 0)
5372286d8edStholo 	    diff_error ("conflicting line format", 0, 0);
5382286d8edStholo 	  break;
5392286d8edStholo 
5402286d8edStholo 	case 135:
5412286d8edStholo 	  specify_style (OUTPUT_IFDEF);
5422286d8edStholo 	  {
5432286d8edStholo 	    int i, err = 0;
5442286d8edStholo 	    for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++)
5452286d8edStholo 	      err |= specify_format (&line_format[i], optarg);
5462286d8edStholo 	    if (err)
5472286d8edStholo 	      diff_error ("conflicting line format", 0, 0);
5482286d8edStholo 	  }
5492286d8edStholo 	  break;
5502286d8edStholo 
5512286d8edStholo 	case 136:
5522286d8edStholo 	case 137:
5532286d8edStholo 	case 138:
5542286d8edStholo 	case 139:
5552286d8edStholo 	  specify_style (OUTPUT_IFDEF);
5562286d8edStholo 	  if (specify_format (&group_format[c - 136], optarg) != 0)
5572286d8edStholo 	    diff_error ("conflicting group format", 0, 0);
5582286d8edStholo 	  break;
5592286d8edStholo 
5602286d8edStholo 	case 140:
5612286d8edStholo 	  if (ck_atoi (optarg, &horizon_lines) || horizon_lines < 0)
5622286d8edStholo 	    fatal ("horizon must be a nonnegative integer");
5632286d8edStholo 	  break;
5642286d8edStholo 
5652286d8edStholo 	case 141:
5662286d8edStholo 	  usage ();
567b2346922Stholo 	  if (! callbacks || ! callbacks->write_stdout)
5682286d8edStholo 	    check_output (stdout);
5692286d8edStholo 	  return 0;
5702286d8edStholo 
5712286d8edStholo 	case 142:
5722286d8edStholo 	  /* Use binary I/O when reading and writing data.
5732286d8edStholo 	     On Posix hosts, this has no effect.  */
5742286d8edStholo #if HAVE_SETMODE
5752286d8edStholo 	  binary_I_O = 1;
5762286d8edStholo #  if 0
5772286d8edStholo 	  /* Because this code is leftover from pre-library days,
5782286d8edStholo 	     there is no way to set stdout back to the default mode
5792286d8edStholo 	     when we are done.  As it turns out, I think the only
5802286d8edStholo 	     parts of CVS that pass out == NULL, and thus cause diff
5812286d8edStholo 	     to write to stdout, are "cvs diff" and "cvs rdiff".  So
5822286d8edStholo 	     I'm not going to worry about this too much yet.  */
5832286d8edStholo 	  setmode (STDOUT_FILENO, O_BINARY);
5842286d8edStholo #  else
5852286d8edStholo 	  if (out == NULL)
5862286d8edStholo 	    error (0, 0, "warning: did not set stdout to binary mode");
5872286d8edStholo #  endif
5882286d8edStholo #endif
5892286d8edStholo 	  break;
5902286d8edStholo 
5912286d8edStholo 	default:
5922286d8edStholo 	  return try_help (0);
5932286d8edStholo 	}
5942286d8edStholo       prev = c;
5952286d8edStholo     }
5962286d8edStholo 
5972286d8edStholo   if (argc - optind != 2)
5982286d8edStholo     return try_help (argc - optind < 2 ? "missing operand" : "extra operand");
5992286d8edStholo 
6002286d8edStholo   {
6012286d8edStholo     /*
6022286d8edStholo      *	We maximize first the half line width, and then the gutter width,
6032286d8edStholo      *	according to the following constraints:
6042286d8edStholo      *	1.  Two half lines plus a gutter must fit in a line.
6052286d8edStholo      *	2.  If the half line width is nonzero:
6062286d8edStholo      *	    a.  The gutter width is at least GUTTER_WIDTH_MINIMUM.
6072286d8edStholo      *	    b.  If tabs are not expanded to spaces,
6082286d8edStholo      *		a half line plus a gutter is an integral number of tabs,
6092286d8edStholo      *		so that tabs in the right column line up.
6102286d8edStholo      */
6112286d8edStholo     int t = tab_expand_flag ? 1 : TAB_WIDTH;
6122286d8edStholo     int off = (width + t + GUTTER_WIDTH_MINIMUM) / (2*t)  *  t;
6132286d8edStholo     sdiff_half_width = max (0, min (off - GUTTER_WIDTH_MINIMUM, width - off)),
6142286d8edStholo     sdiff_column2_offset = sdiff_half_width ? off : width;
6152286d8edStholo   }
6162286d8edStholo 
6172286d8edStholo   if (show_c_function && output_style != OUTPUT_UNIFIED)
6182286d8edStholo     specify_style (OUTPUT_CONTEXT);
6192286d8edStholo 
6202286d8edStholo   if (output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED)
6212286d8edStholo     context = 0;
6222286d8edStholo   else if (context == -1)
6232286d8edStholo     /* Default amount of context for -c.  */
6242286d8edStholo     context = 3;
6252286d8edStholo 
6262286d8edStholo   if (output_style == OUTPUT_IFDEF)
6272286d8edStholo     {
6282286d8edStholo       /* Format arrays are char *, not char const *,
6292286d8edStholo 	 because integer formats are temporarily modified.
6302286d8edStholo 	 But it is safe to assign a constant like "%=" to a format array,
6312286d8edStholo 	 since "%=" does not format any integers.  */
6322286d8edStholo       int i;
6332286d8edStholo       for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++)
6342286d8edStholo 	if (!line_format[i])
6352286d8edStholo 	  line_format[i] = "%l\n";
6362286d8edStholo       if (!group_format[OLD])
6372286d8edStholo 	group_format[OLD]
6382286d8edStholo 	  = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%<";
6392286d8edStholo       if (!group_format[NEW])
6402286d8edStholo 	group_format[NEW]
6412286d8edStholo 	  = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%>";
6422286d8edStholo       if (!group_format[UNCHANGED])
6432286d8edStholo 	group_format[UNCHANGED] = "%=";
6442286d8edStholo       if (!group_format[CHANGED])
6452286d8edStholo 	group_format[CHANGED] = concat (group_format[OLD],
6462286d8edStholo 					group_format[NEW], "");
6472286d8edStholo     }
6482286d8edStholo 
6492286d8edStholo   no_diff_means_no_output =
6502286d8edStholo     (output_style == OUTPUT_IFDEF ?
6512286d8edStholo       (!*group_format[UNCHANGED]
6522286d8edStholo        || (strcmp (group_format[UNCHANGED], "%=") == 0
6532286d8edStholo 	   && !*line_format[UNCHANGED]))
6542286d8edStholo      : output_style == OUTPUT_SDIFF ? sdiff_skip_common_lines : 1);
6552286d8edStholo 
6562286d8edStholo   switch_string = option_list (argv + 1, optind - 1);
6572286d8edStholo 
658b2346922Stholo   if (callbacks && callbacks->write_output)
659b2346922Stholo     {
660b2346922Stholo       if (out != NULL)
661b2346922Stholo 	{
662b2346922Stholo 	  diff_error ("write callback with output file", 0, 0);
663b2346922Stholo 	  return 2;
664b2346922Stholo 	}
665b2346922Stholo     }
666b2346922Stholo   else
667b2346922Stholo     {
6682286d8edStholo       if (out == NULL)
6692286d8edStholo 	outfile = stdout;
6702286d8edStholo       else
6712286d8edStholo 	{
6722286d8edStholo #if HAVE_SETMODE
6732286d8edStholo 	  /* A diff which is full of ^Z and such isn't going to work
6742286d8edStholo 	     very well in text mode.  */
6752286d8edStholo 	  if (binary_I_O)
6762286d8edStholo 	    outfile = fopen (out, "wb");
6772286d8edStholo 	  else
6782286d8edStholo #endif
6792286d8edStholo 	    outfile = fopen (out, "w");
6802286d8edStholo 	  if (outfile == NULL)
6812286d8edStholo 	    {
6822286d8edStholo 	      perror_with_name ("could not open output file");
6832286d8edStholo 	      return 2;
6842286d8edStholo 	    }
685b2346922Stholo 	  opened_file = 1;
686b2346922Stholo 	}
6872286d8edStholo     }
6882286d8edStholo 
6892286d8edStholo   /* Set the jump buffer, so that diff may abort execution without
6902286d8edStholo      terminating the process. */
6912286d8edStholo   if ((val = setjmp (diff_abort_buf)) != 0)
6922286d8edStholo     {
6932286d8edStholo       optind = optind_old;
694b2346922Stholo       if (opened_file)
6952286d8edStholo 	fclose (outfile);
6962286d8edStholo       return val;
6972286d8edStholo     }
6982286d8edStholo 
6992286d8edStholo   val = compare_files (0, argv[optind], 0, argv[optind + 1], 0);
7002286d8edStholo 
7012286d8edStholo   /* Print any messages that were saved up for last.  */
7022286d8edStholo   print_message_queue ();
7032286d8edStholo 
7042286d8edStholo   free (switch_string);
7052286d8edStholo 
7062286d8edStholo   optind = optind_old;
707b2346922Stholo 
708b2346922Stholo   if (! callbacks || ! callbacks->write_output)
7092286d8edStholo     check_output (outfile);
710b2346922Stholo 
711b2346922Stholo   if (opened_file)
7122286d8edStholo     if (fclose (outfile) != 0)
713b2346922Stholo 	perror_with_name ("close error on output file");
714b2346922Stholo 
7152286d8edStholo   return val;
7162286d8edStholo }
7172286d8edStholo 
7182286d8edStholo /* Add the compiled form of regexp PATTERN to REGLIST.  */
7192286d8edStholo 
7202286d8edStholo static void
7212286d8edStholo add_regexp (reglist, pattern)
7222286d8edStholo      struct regexp_list **reglist;
7232286d8edStholo      char const *pattern;
7242286d8edStholo {
7252286d8edStholo   struct regexp_list *r;
7262286d8edStholo   char const *m;
7272286d8edStholo 
7282286d8edStholo   r = (struct regexp_list *) xmalloc (sizeof (*r));
7292286d8edStholo   bzero (r, sizeof (*r));
7302286d8edStholo   r->buf.fastmap = xmalloc (256);
7312286d8edStholo   m = re_compile_pattern (pattern, strlen (pattern), &r->buf);
7322286d8edStholo   if (m != 0)
7332286d8edStholo     diff_error ("%s: %s", pattern, m);
7342286d8edStholo 
7352286d8edStholo   /* Add to the start of the list, since it's easier than the end.  */
7362286d8edStholo   r->next = *reglist;
7372286d8edStholo   *reglist = r;
7382286d8edStholo }
7392286d8edStholo 
7402286d8edStholo static int
7412286d8edStholo try_help (reason)
7422286d8edStholo      char const *reason;
7432286d8edStholo {
7442286d8edStholo   if (reason)
7452286d8edStholo     diff_error ("%s", reason, 0);
7462286d8edStholo   diff_error ("Try `%s --help' for more information.", diff_program_name, 0);
7472286d8edStholo   return 2;
7482286d8edStholo }
7492286d8edStholo 
7502286d8edStholo static void
7512286d8edStholo check_output (file)
7522286d8edStholo     FILE *file;
7532286d8edStholo {
7542286d8edStholo   if (ferror (file) || fflush (file) != 0)
7552286d8edStholo     fatal ("write error");
7562286d8edStholo }
7572286d8edStholo 
7582286d8edStholo static char const * const option_help[] = {
7592286d8edStholo "-i  --ignore-case  Consider upper- and lower-case to be the same.",
7602286d8edStholo "-w  --ignore-all-space  Ignore all white space.",
7612286d8edStholo "-b  --ignore-space-change  Ignore changes in the amount of white space.",
7622286d8edStholo "-B  --ignore-blank-lines  Ignore changes whose lines are all blank.",
7632286d8edStholo "-I RE  --ignore-matching-lines=RE  Ignore changes whose lines all match RE.",
7642286d8edStholo #if HAVE_SETMODE
7652286d8edStholo "--binary  Read and write data in binary mode.",
7662286d8edStholo #endif
7672286d8edStholo "-a  --text  Treat all files as text.\n",
7682286d8edStholo "-c  -C NUM  --context[=NUM]  Output NUM (default 2) lines of copied context.",
7692286d8edStholo "-u  -U NUM  --unified[=NUM]  Output NUM (default 2) lines of unified context.",
7702286d8edStholo "  -NUM  Use NUM context lines.",
7712286d8edStholo "  -L LABEL  --label LABEL  Use LABEL instead of file name.",
7722286d8edStholo "  -p  --show-c-function  Show which C function each change is in.",
7732286d8edStholo "  -F RE  --show-function-line=RE  Show the most recent line matching RE.",
7742286d8edStholo "-q  --brief  Output only whether files differ.",
7752286d8edStholo "-e  --ed  Output an ed script.",
7762286d8edStholo "-n  --rcs  Output an RCS format diff.",
7772286d8edStholo "-y  --side-by-side  Output in two columns.",
7782286d8edStholo "  -w NUM  --width=NUM  Output at most NUM (default 130) characters per line.",
7792286d8edStholo "  --left-column  Output only the left column of common lines.",
7802286d8edStholo "  --suppress-common-lines  Do not output common lines.",
7812286d8edStholo "-DNAME  --ifdef=NAME  Output merged file to show `#ifdef NAME' diffs.",
7822286d8edStholo "--GTYPE-group-format=GFMT  Similar, but format GTYPE input groups with GFMT.",
7832286d8edStholo "--line-format=LFMT  Similar, but format all input lines with LFMT.",
7842286d8edStholo "--LTYPE-line-format=LFMT  Similar, but format LTYPE input lines with LFMT.",
7852286d8edStholo "  LTYPE is `old', `new', or `unchanged'.  GTYPE is LTYPE or `changed'.",
7862286d8edStholo "  GFMT may contain:",
7872286d8edStholo "    %<  lines from FILE1",
7882286d8edStholo "    %>  lines from FILE2",
7892286d8edStholo "    %=  lines common to FILE1 and FILE2",
7902286d8edStholo "    %[-][WIDTH][.[PREC]]{doxX}LETTER  printf-style spec for LETTER",
7912286d8edStholo "      LETTERs are as follows for new group, lower case for old group:",
7922286d8edStholo "        F  first line number",
7932286d8edStholo "        L  last line number",
7942286d8edStholo "        N  number of lines = L-F+1",
7952286d8edStholo "        E  F-1",
7962286d8edStholo "        M  L+1",
7972286d8edStholo "  LFMT may contain:",
7982286d8edStholo "    %L  contents of line",
7992286d8edStholo "    %l  contents of line, excluding any trailing newline",
8002286d8edStholo "    %[-][WIDTH][.[PREC]]{doxX}n  printf-style spec for input line number",
8012286d8edStholo "  Either GFMT or LFMT may contain:",
8022286d8edStholo "    %%  %",
8032286d8edStholo "    %c'C'  the single character C",
8042286d8edStholo "    %c'\\OOO'  the character with octal code OOO\n",
8052286d8edStholo "-l  --paginate  Pass the output through `pr' to paginate it.",
8062286d8edStholo "-t  --expand-tabs  Expand tabs to spaces in output.",
8072286d8edStholo "-T  --initial-tab  Make tabs line up by prepending a tab.\n",
8082286d8edStholo "-r  --recursive  Recursively compare any subdirectories found.",
8092286d8edStholo "-N  --new-file  Treat absent files as empty.",
8102286d8edStholo "-P  --unidirectional-new-file  Treat absent first files as empty.",
8112286d8edStholo "-s  --report-identical-files  Report when two files are the same.",
8122286d8edStholo "-x PAT  --exclude=PAT  Exclude files that match PAT.",
8132286d8edStholo "-X FILE  --exclude-from=FILE  Exclude files that match any pattern in FILE.",
8142286d8edStholo "-S FILE  --starting-file=FILE  Start with FILE when comparing directories.\n",
8152286d8edStholo "--horizon-lines=NUM  Keep NUM lines of the common prefix and suffix.",
8162286d8edStholo "-d  --minimal  Try hard to find a smaller set of changes.",
8172286d8edStholo "-H  --speed-large-files  Assume large files and many scattered small changes.\n",
8182286d8edStholo "-v  --version  Output version info.",
8192286d8edStholo "--help  Output this help.",
8202286d8edStholo 0
8212286d8edStholo };
8222286d8edStholo 
8232286d8edStholo static void
8242286d8edStholo usage ()
8252286d8edStholo {
8262286d8edStholo   char const * const *p;
8272286d8edStholo 
828b2346922Stholo   if (callbacks && callbacks->write_stdout)
829b2346922Stholo     {
830b2346922Stholo       (*callbacks->write_stdout) ("Usage: ");
831b2346922Stholo       (*callbacks->write_stdout) (diff_program_name);
832b2346922Stholo       (*callbacks->write_stdout) (" [OPTION]... FILE1 FILE2\n\n");
833b2346922Stholo       for (p = option_help;  *p;  p++)
834b2346922Stholo 	{
835b2346922Stholo 	  (*callbacks->write_stdout) ("  ");
836b2346922Stholo 	  (*callbacks->write_stdout) (*p);
837b2346922Stholo 	  (*callbacks->write_stdout) ("\n");
838b2346922Stholo 	}
839b2346922Stholo       (*callbacks->write_stdout)
840b2346922Stholo 	("\nIf FILE1 or FILE2 is `-', read standard input.\n");
841b2346922Stholo     }
842b2346922Stholo   else
843b2346922Stholo     {
8442286d8edStholo       printf ("Usage: %s [OPTION]... FILE1 FILE2\n\n", diff_program_name);
8452286d8edStholo       for (p = option_help;  *p;  p++)
8462286d8edStholo 	printf ("  %s\n", *p);
8472286d8edStholo       printf ("\nIf FILE1 or FILE2 is `-', read standard input.\n");
8482286d8edStholo     }
849b2346922Stholo }
8502286d8edStholo 
8512286d8edStholo static int
8522286d8edStholo specify_format (var, value)
8532286d8edStholo      char **var;
8542286d8edStholo      char *value;
8552286d8edStholo {
8562286d8edStholo   int err = *var ? strcmp (*var, value) : 0;
8572286d8edStholo   *var = value;
8582286d8edStholo   return err;
8592286d8edStholo }
8602286d8edStholo 
8612286d8edStholo static void
8622286d8edStholo specify_style (style)
8632286d8edStholo      enum output_style style;
8642286d8edStholo {
8652286d8edStholo   if (output_style != OUTPUT_NORMAL
8662286d8edStholo       && output_style != style)
8672286d8edStholo     diff_error ("conflicting specifications of output style", 0, 0);
8682286d8edStholo   output_style = style;
8692286d8edStholo }
8702286d8edStholo 
8712286d8edStholo static char const *
8722286d8edStholo filetype (st)
8732286d8edStholo      struct stat const *st;
8742286d8edStholo {
8752286d8edStholo   /* See Posix.2 section 4.17.6.1.1 and Table 5-1 for these formats.
8762286d8edStholo      To keep diagnostics grammatical, the returned string must start
8772286d8edStholo      with a consonant.  */
8782286d8edStholo 
8792286d8edStholo   if (S_ISREG (st->st_mode))
8802286d8edStholo     {
8812286d8edStholo       if (st->st_size == 0)
8822286d8edStholo 	return "regular empty file";
8832286d8edStholo       /* Posix.2 section 5.14.2 seems to suggest that we must read the file
8842286d8edStholo 	 and guess whether it's C, Fortran, etc., but this is somewhat useless
8852286d8edStholo 	 and doesn't reflect historical practice.  We're allowed to guess
8862286d8edStholo 	 wrong, so we don't bother to read the file.  */
8872286d8edStholo       return "regular file";
8882286d8edStholo     }
8892286d8edStholo   if (S_ISDIR (st->st_mode)) return "directory";
8902286d8edStholo 
8912286d8edStholo   /* other Posix.1 file types */
8922286d8edStholo #ifdef S_ISBLK
8932286d8edStholo   if (S_ISBLK (st->st_mode)) return "block special file";
8942286d8edStholo #endif
8952286d8edStholo #ifdef S_ISCHR
8962286d8edStholo   if (S_ISCHR (st->st_mode)) return "character special file";
8972286d8edStholo #endif
8982286d8edStholo #ifdef S_ISFIFO
8992286d8edStholo   if (S_ISFIFO (st->st_mode)) return "fifo";
9002286d8edStholo #endif
9012286d8edStholo 
9022286d8edStholo   /* other Posix.1b file types */
9032286d8edStholo #ifdef S_TYPEISMQ
9042286d8edStholo   if (S_TYPEISMQ (st)) return "message queue";
9052286d8edStholo #endif
9062286d8edStholo #ifdef S_TYPEISSEM
9072286d8edStholo   if (S_TYPEISSEM (st)) return "semaphore";
9082286d8edStholo #endif
9092286d8edStholo #ifdef S_TYPEISSHM
9102286d8edStholo   if (S_TYPEISSHM (st)) return "shared memory object";
9112286d8edStholo #endif
9122286d8edStholo 
9132286d8edStholo   /* other popular file types */
9142286d8edStholo   /* S_ISLNK is impossible with `fstat' and `stat'.  */
9152286d8edStholo #ifdef S_ISSOCK
9162286d8edStholo   if (S_ISSOCK (st->st_mode)) return "socket";
9172286d8edStholo #endif
9182286d8edStholo 
9192286d8edStholo   return "weird file";
9202286d8edStholo }
9212286d8edStholo 
9222286d8edStholo /* Compare two files (or dirs) with specified names
9232286d8edStholo    DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion.
9242286d8edStholo    (if DIR0 is 0, then the name is just NAME0, etc.)
9252286d8edStholo    This is self-contained; it opens the files and closes them.
9262286d8edStholo 
9272286d8edStholo    Value is 0 if files are the same, 1 if different,
9282286d8edStholo    2 if there is a problem opening them.  */
9292286d8edStholo 
9302286d8edStholo static int
9312286d8edStholo compare_files (dir0, name0, dir1, name1, depth)
9322286d8edStholo      char const *dir0, *dir1;
9332286d8edStholo      char const *name0, *name1;
9342286d8edStholo      int depth;
9352286d8edStholo {
9362286d8edStholo   struct file_data inf[2];
9372286d8edStholo   register int i;
9382286d8edStholo   int val;
9392286d8edStholo   int same_files;
9402286d8edStholo   int failed = 0;
9412286d8edStholo   char *free0 = 0, *free1 = 0;
9422286d8edStholo 
9432286d8edStholo   /* If this is directory comparison, perhaps we have a file
9442286d8edStholo      that exists only in one of the directories.
9452286d8edStholo      If so, just print a message to that effect.  */
9462286d8edStholo 
9472286d8edStholo   if (! ((name0 != 0 && name1 != 0)
9482286d8edStholo 	 || (unidirectional_new_file_flag && name1 != 0)
9492286d8edStholo 	 || entire_new_file_flag))
9502286d8edStholo     {
9512286d8edStholo       char const *name = name0 == 0 ? name1 : name0;
9522286d8edStholo       char const *dir = name0 == 0 ? dir1 : dir0;
9532286d8edStholo       message ("Only in %s: %s\n", dir, name);
9542286d8edStholo       /* Return 1 so that diff_dirs will return 1 ("some files differ").  */
9552286d8edStholo       return 1;
9562286d8edStholo     }
9572286d8edStholo 
9582286d8edStholo   bzero (inf, sizeof (inf));
9592286d8edStholo 
9602286d8edStholo   /* Mark any nonexistent file with -1 in the desc field.  */
9612286d8edStholo   /* Mark unopened files (e.g. directories) with -2. */
9622286d8edStholo 
9632286d8edStholo   inf[0].desc = name0 == 0 ? -1 : -2;
9642286d8edStholo   inf[1].desc = name1 == 0 ? -1 : -2;
9652286d8edStholo 
9662286d8edStholo   /* Now record the full name of each file, including nonexistent ones.  */
9672286d8edStholo 
9682286d8edStholo   if (name0 == 0)
9692286d8edStholo     name0 = name1;
9702286d8edStholo   if (name1 == 0)
9712286d8edStholo     name1 = name0;
9722286d8edStholo 
9732286d8edStholo   inf[0].name = dir0 == 0 ? name0 : (free0 = dir_file_pathname (dir0, name0));
9742286d8edStholo   inf[1].name = dir1 == 0 ? name1 : (free1 = dir_file_pathname (dir1, name1));
9752286d8edStholo 
9762286d8edStholo   /* Stat the files.  Record whether they are directories.  */
9772286d8edStholo 
9782286d8edStholo   for (i = 0; i <= 1; i++)
9792286d8edStholo     {
9802286d8edStholo       if (inf[i].desc != -1)
9812286d8edStholo 	{
9822286d8edStholo 	  int stat_result;
9832286d8edStholo 
9842286d8edStholo 	  if (i && filename_cmp (inf[i].name, inf[0].name) == 0)
9852286d8edStholo 	    {
9862286d8edStholo 	      inf[i].stat = inf[0].stat;
9872286d8edStholo 	      stat_result = 0;
9882286d8edStholo 	    }
9892286d8edStholo 	  else if (strcmp (inf[i].name, "-") == 0)
9902286d8edStholo 	    {
9912286d8edStholo 	      inf[i].desc = STDIN_FILENO;
9922286d8edStholo 	      stat_result = fstat (STDIN_FILENO, &inf[i].stat);
9932286d8edStholo 	      if (stat_result == 0 && S_ISREG (inf[i].stat.st_mode))
9942286d8edStholo 		{
9952286d8edStholo 		  off_t pos = lseek (STDIN_FILENO, (off_t) 0, SEEK_CUR);
9962286d8edStholo 		  if (pos == -1)
9972286d8edStholo 		    stat_result = -1;
9982286d8edStholo 		  else
9992286d8edStholo 		    {
10002286d8edStholo 		      if (pos <= inf[i].stat.st_size)
10012286d8edStholo 			inf[i].stat.st_size -= pos;
10022286d8edStholo 		      else
10032286d8edStholo 			inf[i].stat.st_size = 0;
10042286d8edStholo 		      /* Posix.2 4.17.6.1.4 requires current time for stdin.  */
10052286d8edStholo 		      time (&inf[i].stat.st_mtime);
10062286d8edStholo 		    }
10072286d8edStholo 		}
10082286d8edStholo 	    }
10092286d8edStholo 	  else
10102286d8edStholo 	    stat_result = stat (inf[i].name, &inf[i].stat);
10112286d8edStholo 
10122286d8edStholo 	  if (stat_result != 0)
10132286d8edStholo 	    {
10142286d8edStholo 	      perror_with_name (inf[i].name);
10152286d8edStholo 	      failed = 1;
10162286d8edStholo 	    }
10172286d8edStholo 	  else
10182286d8edStholo 	    {
10192286d8edStholo 	      inf[i].dir_p = S_ISDIR (inf[i].stat.st_mode) && inf[i].desc != 0;
10202286d8edStholo 	      if (inf[1 - i].desc == -1)
10212286d8edStholo 		{
10222286d8edStholo 		  inf[1 - i].dir_p = inf[i].dir_p;
10232286d8edStholo 		  inf[1 - i].stat.st_mode = inf[i].stat.st_mode;
10242286d8edStholo 		}
10252286d8edStholo 	    }
10262286d8edStholo 	}
10272286d8edStholo     }
10282286d8edStholo 
10292286d8edStholo   if (! failed && depth == 0 && inf[0].dir_p != inf[1].dir_p)
10302286d8edStholo     {
10312286d8edStholo       /* If one is a directory, and it was specified in the command line,
10322286d8edStholo 	 use the file in that dir with the other file's basename.  */
10332286d8edStholo 
10342286d8edStholo       int fnm_arg = inf[0].dir_p;
10352286d8edStholo       int dir_arg = 1 - fnm_arg;
10362286d8edStholo       char const *fnm = inf[fnm_arg].name;
10372286d8edStholo       char const *dir = inf[dir_arg].name;
10382286d8edStholo       char const *p = filename_lastdirchar (fnm);
10392286d8edStholo       char const *filename = inf[dir_arg].name
10402286d8edStholo 	= dir_file_pathname (dir, p ? p + 1 : fnm);
10412286d8edStholo 
10422286d8edStholo       if (strcmp (fnm, "-") == 0)
10432286d8edStholo 	fatal ("can't compare - to a directory");
10442286d8edStholo 
10452286d8edStholo       if (stat (filename, &inf[dir_arg].stat) != 0)
10462286d8edStholo 	{
10472286d8edStholo 	  perror_with_name (filename);
10482286d8edStholo 	  failed = 1;
10492286d8edStholo 	}
10502286d8edStholo       else
10512286d8edStholo 	inf[dir_arg].dir_p = S_ISDIR (inf[dir_arg].stat.st_mode);
10522286d8edStholo     }
10532286d8edStholo 
10542286d8edStholo   if (failed)
10552286d8edStholo     {
10562286d8edStholo 
10572286d8edStholo       /* If either file should exist but does not, return 2.  */
10582286d8edStholo 
10592286d8edStholo       val = 2;
10602286d8edStholo 
10612286d8edStholo     }
10622286d8edStholo   else if ((same_files = inf[0].desc != -1 && inf[1].desc != -1
10632286d8edStholo 			 && 0 < same_file (&inf[0].stat, &inf[1].stat))
10642286d8edStholo 	   && no_diff_means_no_output)
10652286d8edStholo     {
10662286d8edStholo       /* The two named files are actually the same physical file.
10672286d8edStholo 	 We know they are identical without actually reading them.  */
10682286d8edStholo 
10692286d8edStholo       val = 0;
10702286d8edStholo     }
10712286d8edStholo   else if (inf[0].dir_p & inf[1].dir_p)
10722286d8edStholo     {
10732286d8edStholo       if (output_style == OUTPUT_IFDEF)
10742286d8edStholo 	fatal ("-D option not supported with directories");
10752286d8edStholo 
10762286d8edStholo       /* If both are directories, compare the files in them.  */
10772286d8edStholo 
10782286d8edStholo       if (depth > 0 && !recursive)
10792286d8edStholo 	{
10802286d8edStholo 	  /* But don't compare dir contents one level down
10812286d8edStholo 	     unless -r was specified.  */
10822286d8edStholo 	  message ("Common subdirectories: %s and %s\n",
10832286d8edStholo 		   inf[0].name, inf[1].name);
10842286d8edStholo 	  val = 0;
10852286d8edStholo 	}
10862286d8edStholo       else
10872286d8edStholo 	{
10882286d8edStholo 	  val = diff_dirs (inf, compare_files, depth);
10892286d8edStholo 	}
10902286d8edStholo 
10912286d8edStholo     }
10922286d8edStholo   else if ((inf[0].dir_p | inf[1].dir_p)
10932286d8edStholo 	   || (depth > 0
10942286d8edStholo 	       && (! S_ISREG (inf[0].stat.st_mode)
10952286d8edStholo 		   || ! S_ISREG (inf[1].stat.st_mode))))
10962286d8edStholo     {
10972286d8edStholo       /* Perhaps we have a subdirectory that exists only in one directory.
10982286d8edStholo 	 If so, just print a message to that effect.  */
10992286d8edStholo 
11002286d8edStholo       if (inf[0].desc == -1 || inf[1].desc == -1)
11012286d8edStholo 	{
11022286d8edStholo 	  if ((inf[0].dir_p | inf[1].dir_p)
11032286d8edStholo 	      && recursive
11042286d8edStholo 	      && (entire_new_file_flag
11052286d8edStholo 		  || (unidirectional_new_file_flag && inf[0].desc == -1)))
11062286d8edStholo 	    val = diff_dirs (inf, compare_files, depth);
11072286d8edStholo 	  else
11082286d8edStholo 	    {
11092286d8edStholo 	      char const *dir = (inf[0].desc == -1) ? dir1 : dir0;
11102286d8edStholo 	      /* See Posix.2 section 4.17.6.1.1 for this format.  */
11112286d8edStholo 	      message ("Only in %s: %s\n", dir, name0);
11122286d8edStholo 	      val = 1;
11132286d8edStholo 	    }
11142286d8edStholo 	}
11152286d8edStholo       else
11162286d8edStholo 	{
11172286d8edStholo 	  /* We have two files that are not to be compared.  */
11182286d8edStholo 
11192286d8edStholo 	  /* See Posix.2 section 4.17.6.1.1 for this format.  */
11202286d8edStholo 	  message5 ("File %s is a %s while file %s is a %s\n",
11212286d8edStholo 		    inf[0].name, filetype (&inf[0].stat),
11222286d8edStholo 		    inf[1].name, filetype (&inf[1].stat));
11232286d8edStholo 
11242286d8edStholo 	  /* This is a difference.  */
11252286d8edStholo 	  val = 1;
11262286d8edStholo 	}
11272286d8edStholo     }
11282286d8edStholo   else if ((no_details_flag & ~ignore_some_changes)
11292286d8edStholo 	   && inf[0].stat.st_size != inf[1].stat.st_size
11302286d8edStholo 	   && (inf[0].desc == -1 || S_ISREG (inf[0].stat.st_mode))
11312286d8edStholo 	   && (inf[1].desc == -1 || S_ISREG (inf[1].stat.st_mode)))
11322286d8edStholo     {
11332286d8edStholo       message ("Files %s and %s differ\n", inf[0].name, inf[1].name);
11342286d8edStholo       val = 1;
11352286d8edStholo     }
11362286d8edStholo   else
11372286d8edStholo     {
11382286d8edStholo       /* Both exist and neither is a directory.  */
11392286d8edStholo 
11402286d8edStholo       /* Open the files and record their descriptors.  */
11412286d8edStholo 
11422286d8edStholo       if (inf[0].desc == -2)
11432286d8edStholo 	if ((inf[0].desc = open (inf[0].name, O_RDONLY, 0)) < 0)
11442286d8edStholo 	  {
11452286d8edStholo 	    perror_with_name (inf[0].name);
11462286d8edStholo 	    failed = 1;
11472286d8edStholo 	  }
11482286d8edStholo       if (inf[1].desc == -2)
11492286d8edStholo 	if (same_files)
11502286d8edStholo 	  inf[1].desc = inf[0].desc;
11512286d8edStholo 	else if ((inf[1].desc = open (inf[1].name, O_RDONLY, 0)) < 0)
11522286d8edStholo 	  {
11532286d8edStholo 	    perror_with_name (inf[1].name);
11542286d8edStholo 	    failed = 1;
11552286d8edStholo 	  }
11562286d8edStholo 
11572286d8edStholo #if HAVE_SETMODE
11582286d8edStholo       if (binary_I_O)
11592286d8edStholo 	for (i = 0; i <= 1; i++)
11602286d8edStholo 	  if (0 <= inf[i].desc)
11612286d8edStholo 	    setmode (inf[i].desc, O_BINARY);
11622286d8edStholo #endif
11632286d8edStholo 
11642286d8edStholo       /* Compare the files, if no error was found.  */
11652286d8edStholo 
11662286d8edStholo       val = failed ? 2 : diff_2_files (inf, depth);
11672286d8edStholo 
11682286d8edStholo       /* Close the file descriptors.  */
11692286d8edStholo 
11702286d8edStholo       if (inf[0].desc >= 0 && close (inf[0].desc) != 0)
11712286d8edStholo 	{
11722286d8edStholo 	  perror_with_name (inf[0].name);
11732286d8edStholo 	  val = 2;
11742286d8edStholo 	}
11752286d8edStholo       if (inf[1].desc >= 0 && inf[0].desc != inf[1].desc
11762286d8edStholo 	  && close (inf[1].desc) != 0)
11772286d8edStholo 	{
11782286d8edStholo 	  perror_with_name (inf[1].name);
11792286d8edStholo 	  val = 2;
11802286d8edStholo 	}
11812286d8edStholo     }
11822286d8edStholo 
11832286d8edStholo   /* Now the comparison has been done, if no error prevented it,
11842286d8edStholo      and VAL is the value this function will return.  */
11852286d8edStholo 
11862286d8edStholo   if (val == 0 && !inf[0].dir_p)
11872286d8edStholo     {
11882286d8edStholo       if (print_file_same_flag)
11892286d8edStholo 	message ("Files %s and %s are identical\n",
11902286d8edStholo 		 inf[0].name, inf[1].name);
11912286d8edStholo     }
11922286d8edStholo   else
1193b2346922Stholo     flush_output ();
11942286d8edStholo 
11952286d8edStholo   if (free0)
11962286d8edStholo     free (free0);
11972286d8edStholo   if (free1)
11982286d8edStholo     free (free1);
11992286d8edStholo 
12002286d8edStholo   return val;
12012286d8edStholo }
12022286d8edStholo 
12032286d8edStholo /* Initialize status variables and flag variables used in libdiff,
12042286d8edStholo    to permit repeated calls to diff_run. */
12052286d8edStholo 
12062286d8edStholo static void
12072286d8edStholo initialize_main (argcp, argvp)
12082286d8edStholo     int *argcp;
12092286d8edStholo     char ***argvp;
12102286d8edStholo {
12112286d8edStholo   /* These variables really must be reset each time diff_run is called. */
12122286d8edStholo   output_style = OUTPUT_NORMAL;
12132286d8edStholo   context = -1;
12142286d8edStholo   file_label[0] = NULL;
12152286d8edStholo   file_label[1] = NULL;
12162286d8edStholo   diff_program_name = (*argvp)[0];
12172286d8edStholo   outfile = NULL;
12182286d8edStholo 
12192286d8edStholo   /* Reset these also, just for safety's sake. (If one invocation turns
12202286d8edStholo      on ignore_case_flag, it must be turned off before diff_run is called
12212286d8edStholo      again.  But it is possible to make many diffs before encountering
12222286d8edStholo      such a problem. */
12232286d8edStholo   recursive = 0;
12242286d8edStholo   no_discards = 0;
12252286d8edStholo #if HAVE_SETMODE
12262286d8edStholo   binary_I_O = 0;
12272286d8edStholo #endif
12282286d8edStholo   no_diff_means_no_output = 0;
12292286d8edStholo   always_text_flag = 0;
12302286d8edStholo   horizon_lines = 0;
12312286d8edStholo   ignore_space_change_flag = 0;
12322286d8edStholo   ignore_all_space_flag = 0;
12332286d8edStholo   ignore_blank_lines_flag = 0;
12342286d8edStholo   ignore_some_line_changes = 0;
12352286d8edStholo   ignore_some_changes = 0;
12362286d8edStholo   ignore_case_flag = 0;
12372286d8edStholo   function_regexp_list = NULL;
12382286d8edStholo   ignore_regexp_list = NULL;
12392286d8edStholo   no_details_flag = 0;
12402286d8edStholo   print_file_same_flag = 0;
12412286d8edStholo   tab_align_flag = 0;
12422286d8edStholo   tab_expand_flag = 0;
12432286d8edStholo   dir_start_file = NULL;
12442286d8edStholo   entire_new_file_flag = 0;
12452286d8edStholo   unidirectional_new_file_flag = 0;
12462286d8edStholo   paginate_flag = 0;
12472286d8edStholo   bzero (group_format, sizeof (group_format));
12482286d8edStholo   bzero (line_format, sizeof (line_format));
12492286d8edStholo   sdiff_help_sdiff = 0;
12502286d8edStholo   sdiff_left_only = 0;
12512286d8edStholo   sdiff_skip_common_lines = 0;
12522286d8edStholo   sdiff_half_width = 0;
12532286d8edStholo   sdiff_column2_offset = 0;
12542286d8edStholo   switch_string = NULL;
12552286d8edStholo   heuristic = 0;
12562286d8edStholo   bzero (files, sizeof (files));
12572286d8edStholo }
1258