diff options
| author | svn2git <svn2git@FreeBSD.org> | 1994-05-01 08:00:00 +0000 | 
|---|---|---|
| committer | svn2git <svn2git@FreeBSD.org> | 1994-05-01 08:00:00 +0000 | 
| commit | a16f65c7d117419bd266c28a1901ef129a337569 (patch) | |
| tree | 2626602f66dc3551e7a7c7bc9ad763c3bc7ab40a /gnu/usr.bin/diff/diff.c | |
| parent | 8503f4f13f77abf7adc8f7e329c6f9c1d52b6a20 (diff) | |
Diffstat (limited to 'gnu/usr.bin/diff/diff.c')
| -rw-r--r-- | gnu/usr.bin/diff/diff.c | 997 | 
1 files changed, 997 insertions, 0 deletions
diff --git a/gnu/usr.bin/diff/diff.c b/gnu/usr.bin/diff/diff.c new file mode 100644 index 000000000000..6004f843b8d3 --- /dev/null +++ b/gnu/usr.bin/diff/diff.c @@ -0,0 +1,997 @@ +/* GNU DIFF main routine. +   Copyright (C) 1988, 1989, 1992, 1993 Free Software Foundation, Inc. + +This file is part of GNU DIFF. + +GNU DIFF is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2, or (at your option) +any later version. + +GNU DIFF is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with GNU DIFF; see the file COPYING.  If not, write to +the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */ + +/* GNU DIFF was written by Mike Haertel, David Hayes, +   Richard Stallman, Len Tower, and Paul Eggert.  */ + +#define GDIFF_MAIN +#include "diff.h" +#include "getopt.h" +#include "fnmatch.h" + +#ifndef DEFAULT_WIDTH +#define DEFAULT_WIDTH 130 +#endif + +#ifndef GUTTER_WIDTH_MINIMUM +#define GUTTER_WIDTH_MINIMUM 3 +#endif + +static char const *filetype PARAMS((struct stat const *)); +static char *option_list PARAMS((char **, int)); +static int add_exclude_file PARAMS((char const *)); +static int ck_atoi PARAMS((char const *, int *)); +static int compare_files PARAMS((char const *, char const *, char const *, char const *, int)); +static int specify_format PARAMS((char **, char *)); +static void add_exclude PARAMS((char const *)); +static void add_regexp PARAMS((struct regexp_list **, char const *)); +static void specify_style PARAMS((enum output_style)); +static void usage PARAMS((char const *)); + +/* Nonzero for -r: if comparing two directories, +   compare their common subdirectories recursively.  */ + +static int recursive; + +/* For debugging: don't do discard_confusing_lines.  */ + +int no_discards; + +/* Return a string containing the command options with which diff was invoked. +   Spaces appear between what were separate ARGV-elements. +   There is a space at the beginning but none at the end. +   If there were no options, the result is an empty string. + +   Arguments: OPTIONVEC, a vector containing separate ARGV-elements, and COUNT, +   the length of that vector.  */ + +static char * +option_list (optionvec, count) +     char **optionvec;  /* Was `vector', but that collides on Alliant.  */ +     int count; +{ +  int i; +  size_t length = 0; +  char *result; + +  for (i = 0; i < count; i++) +    length += strlen (optionvec[i]) + 1; + +  result = xmalloc (length + 1); +  result[0] = 0; + +  for (i = 0; i < count; i++) +    { +      strcat (result, " "); +      strcat (result, optionvec[i]); +    } + +  return result; +} + +/* Convert STR to a positive integer, storing the result in *OUT. +   If STR is not a valid integer, return -1 (otherwise 0). */ +static int +ck_atoi (str, out) +     char const *str; +     int *out; +{ +  char const *p; +  for (p = str; *p; p++) +    if (*p < '0' || *p > '9') +      return -1; + +  *out = atoi (optarg); +  return 0; +} + +/* Keep track of excluded file name patterns.  */ + +static char const **exclude; +static int exclude_alloc, exclude_count; + +int +excluded_filename (f) +     char const *f; +{ +  int i; +  for (i = 0;  i < exclude_count;  i++) +    if (fnmatch (exclude[i], f, 0) == 0) +      return 1; +  return 0; +} + +static void +add_exclude (pattern) +     char const *pattern; +{ +  if (exclude_alloc <= exclude_count) +    exclude = (char const **) +	      (exclude_alloc == 0 +	       ? xmalloc ((exclude_alloc = 64) * sizeof (*exclude)) +	       : xrealloc (exclude, (exclude_alloc *= 2) * sizeof (*exclude))); + +  exclude[exclude_count++] = pattern; +} + +static int +add_exclude_file (name) +     char const *name; +{ +  struct file_data f; +  char *p, *q, *lim; + +  f.name = optarg; +  f.desc = (strcmp (optarg, "-") == 0 +	    ? STDIN_FILENO +	    : open (optarg, O_RDONLY, 0)); +  if (f.desc < 0 || fstat (f.desc, &f.stat) != 0) +    return -1; + +  sip (&f, 1); +  slurp (&f); + +  for (p = f.buffer, lim = p + f.buffered_chars;  p < lim;  p = q) +    { +      q = (char *) memchr (p, '\n', lim - p); +      if (!q) +	q = lim; +      *q++ = 0; +      add_exclude (p); +    } + +  return close (f.desc); +} + +/* The numbers 129- that appear in the fourth element of some entries +   tell the big switch in `main' how to process those options.  */ + +static struct option const longopts[] = +{ +  {"ignore-blank-lines", 0, 0, 'B'}, +  {"context", 2, 0, 'C'}, +  {"ifdef", 1, 0, 'D'}, +  {"show-function-line", 1, 0, 'F'}, +  {"speed-large-files", 0, 0, 'H'}, +  {"ignore-matching-lines", 1, 0, 'I'}, +  {"label", 1, 0, 'L'}, +  {"file-label", 1, 0, 'L'},	/* An alias, no longer recommended */ +  {"new-file", 0, 0, 'N'}, +  {"entire-new-file", 0, 0, 'N'},	/* An alias, no longer recommended */ +  {"unidirectional-new-file", 0, 0, 'P'}, +  {"starting-file", 1, 0, 'S'}, +  {"initial-tab", 0, 0, 'T'}, +  {"width", 1, 0, 'W'}, +  {"text", 0, 0, 'a'}, +  {"ascii", 0, 0, 'a'},		/* An alias, no longer recommended */ +  {"ignore-space-change", 0, 0, 'b'}, +  {"minimal", 0, 0, 'd'}, +  {"ed", 0, 0, 'e'}, +  {"forward-ed", 0, 0, 'f'}, +  {"ignore-case", 0, 0, 'i'}, +  {"paginate", 0, 0, 'l'}, +  {"print", 0, 0, 'l'},		/* An alias, no longer recommended */ +  {"rcs", 0, 0, 'n'}, +  {"show-c-function", 0, 0, 'p'}, +  {"binary", 0, 0, 'q'},	/* An alias, no longer recommended */ +  {"brief", 0, 0, 'q'}, +  {"recursive", 0, 0, 'r'}, +  {"report-identical-files", 0, 0, 's'}, +  {"expand-tabs", 0, 0, 't'}, +  {"version", 0, 0, 'v'}, +  {"ignore-all-space", 0, 0, 'w'}, +  {"exclude", 1, 0, 'x'}, +  {"exclude-from", 1, 0, 'X'}, +  {"side-by-side", 0, 0, 'y'}, +  {"unified", 2, 0, 'U'}, +  {"left-column", 0, 0, 129}, +  {"suppress-common-lines", 0, 0, 130}, +  {"sdiff-merge-assist", 0, 0, 131}, +  {"old-line-format", 1, 0, 132}, +  {"new-line-format", 1, 0, 133}, +  {"unchanged-line-format", 1, 0, 134}, +  {"line-format", 1, 0, 135}, +  {"old-group-format", 1, 0, 136}, +  {"new-group-format", 1, 0, 137}, +  {"unchanged-group-format", 1, 0, 138}, +  {"changed-group-format", 1, 0, 139}, +  {"horizon-lines", 1, 0, 140}, +  {"help", 0, 0, 141}, +  {0, 0, 0, 0} +}; + +int +main (argc, argv) +     int argc; +     char *argv[]; +{ +  int val; +  int c; +  int prev = -1; +  int width = DEFAULT_WIDTH; + +  /* Do our initializations.  */ +  program = argv[0]; +  output_style = OUTPUT_NORMAL; +  context = -1; +  line_end_char = '\n'; + +  /* Decode the options.  */ + +  while ((c = getopt_long (argc, argv, +			   "0123456789abBcC:dD:efF:hHiI:lL:nNpPqrsS:tTuU:vwW:x:X:y", +			   longopts, 0)) != EOF) +    { +      switch (c) +	{ +	  /* All digits combine in decimal to specify the context-size.  */ +	case '1': +	case '2': +	case '3': +	case '4': +	case '5': +	case '6': +	case '7': +	case '8': +	case '9': +	case '0': +	  if (context == -1) +	    context = 0; +	  /* If a context length has already been specified, +	     more digits allowed only if they follow right after the others. +	     Reject two separate runs of digits, or digits after -C.  */ +	  else if (prev < '0' || prev > '9') +	    fatal ("context length specified twice"); + +	  context = context * 10 + c - '0'; +	  break; + +	case 'a': +	  /* Treat all files as text files; never treat as binary.  */ +	  always_text_flag = 1; +	  break; + +	case 'b': +	  /* Ignore changes in amount of white space.  */ +	  ignore_space_change_flag = 1; +	  length_varies = 1; +	  ignore_some_changes = 1; +	  break; + +	case 'B': +	  /* Ignore changes affecting only blank lines.  */ +	  ignore_blank_lines_flag = 1; +	  ignore_some_changes = 1; +	  break; + +	case 'C':		/* +context[=lines] */ +	case 'U':		/* +unified[=lines] */ +	  if (optarg) +	    { +	      if (context >= 0) +		fatal ("context length specified twice"); + +	      if (ck_atoi (optarg, &context)) +		fatal ("invalid context length argument"); +	    } + +	  /* Falls through.  */ +	case 'c': +	  /* Make context-style output.  */ +	  specify_style (c == 'U' ? OUTPUT_UNIFIED : OUTPUT_CONTEXT); +	  break; + +	case 'd': +	  /* Don't discard lines.  This makes things slower (sometimes much +	     slower) but will find a guaranteed minimal set of changes.  */ +	  no_discards = 1; +	  break; + +	case 'D': +	  /* Make merged #ifdef output.  */ +	  specify_style (OUTPUT_IFDEF); +	  { +	    int i, err = 0; +	    static char const C_ifdef_group_formats[] = +	      "#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"; +	    char *b = xmalloc (sizeof (C_ifdef_group_formats) +			       + 7 * strlen(optarg) - 14 /* 7*"%s" */ +			       - 8 /* 5*"%%" + 3*"%c" */); +	    sprintf (b, C_ifdef_group_formats, +		     optarg, optarg, 0, +		     optarg, optarg, 0, 0, +		     optarg, optarg, optarg); +	    for (i = 0; i < 4; i++) +	      { +		err |= specify_format (&group_format[i], b); +		b += strlen (b) + 1; +	      } +	    if (err) +	      error ("conflicting #ifdef formats", 0, 0); +	  } +	  break; + +	case 'e': +	  /* Make output that is a valid `ed' script.  */ +	  specify_style (OUTPUT_ED); +	  break; + +	case 'f': +	  /* Make output that looks vaguely like an `ed' script +	     but has changes in the order they appear in the file.  */ +	  specify_style (OUTPUT_FORWARD_ED); +	  break; + +	case 'F': +	  /* Show, for each set of changes, the previous line that +	     matches the specified regexp.  Currently affects only +	     context-style output.  */ +	  add_regexp (&function_regexp_list, optarg); +	  break; + +	case 'h': +	  /* Split the files into chunks of around 1500 lines +	     for faster processing.  Usually does not change the result. + +	     This currently has no effect.  */ +	  break; + +	case 'H': +	  /* Turn on heuristics that speed processing of large files +	     with a small density of changes.  */ +	  heuristic = 1; +	  break; + +	case 'i': +	  /* Ignore changes in case.  */ +	  ignore_case_flag = 1; +	  ignore_some_changes = 1; +	  break; + +	case 'I': +	  /* Ignore changes affecting only lines that match the +	     specified regexp.  */ +	  add_regexp (&ignore_regexp_list, optarg); +	  ignore_some_changes = 1; +	  break; + +	case 'l': +	  /* Pass the output through `pr' to paginate it.  */ +	  paginate_flag = 1; +	  break; + +	case 'L': +	  /* Specify file labels for `-c' output headers.  */ +	  if (!file_label[0]) +	    file_label[0] = optarg; +	  else if (!file_label[1]) +	    file_label[1] = optarg; +	  else +	    fatal ("too many file label options"); +	  break; + +	case 'n': +	  /* Output RCS-style diffs, like `-f' except that each command +	     specifies the number of lines affected.  */ +	  specify_style (OUTPUT_RCS); +	  break; + +	case 'N': +	  /* When comparing directories, if a file appears only in one +	     directory, treat it as present but empty in the other.  */ +	  entire_new_file_flag = 1; +	  break; + +	case 'p': +	  /* Make context-style output and show name of last C function.  */ +	  specify_style (OUTPUT_CONTEXT); +	  add_regexp (&function_regexp_list, "^[_a-zA-Z$]"); +	  break; + +	case 'P': +	  /* When comparing directories, if a file appears only in +	     the second directory of the two, +	     treat it as present but empty in the other.  */ +	  unidirectional_new_file_flag = 1; +	  break; + +	case 'q': +	  no_details_flag = 1; +	  break; + +	case 'r': +	  /* When comparing directories, +	     recursively compare any subdirectories found.  */ +	  recursive = 1; +	  break; + +	case 's': +	  /* Print a message if the files are the same.  */ +	  print_file_same_flag = 1; +	  break; + +	case 'S': +	  /* When comparing directories, start with the specified +	     file name.  This is used for resuming an aborted comparison.  */ +	  dir_start_file = optarg; +	  break; + +	case 't': +	  /* Expand tabs to spaces in the output so that it preserves +	     the alignment of the input files.  */ +	  tab_expand_flag = 1; +	  break; + +	case 'T': +	  /* Use a tab in the output, rather than a space, before the +	     text of an input line, so as to keep the proper alignment +	     in the input line without changing the characters in it.  */ +	  tab_align_flag = 1; +	  break; + +	case 'u': +	  /* Output the context diff in unidiff format.  */ +	  specify_style (OUTPUT_UNIFIED); +	  break; + +	case 'v': +	  printf ("GNU diff version %s\n", version_string); +	  exit (0); + +	case 'w': +	  /* Ignore horizontal white space when comparing lines.  */ +	  ignore_all_space_flag = 1; +	  ignore_some_changes = 1; +	  length_varies = 1; +	  break; + +	case 'x': +	  add_exclude (optarg); +	  break; + +	case 'X': +	  if (add_exclude_file (optarg) != 0) +	    pfatal_with_name (optarg); +	  break; + +	case 'y': +	  /* Use side-by-side (sdiff-style) columnar output. */ +	  specify_style (OUTPUT_SDIFF); +	  break; + +	case 'W': +	  /* Set the line width for OUTPUT_SDIFF.  */ +	  if (ck_atoi (optarg, &width) || width <= 0) +	    fatal ("column width must be a positive integer"); +	  break; + +	case 129: +	  sdiff_left_only = 1; +	  break; + +	case 130: +	  sdiff_skip_common_lines = 1; +	  break; + +	case 131: +	  /* sdiff-style columns output. */ +	  specify_style (OUTPUT_SDIFF); +	  sdiff_help_sdiff = 1; +	  break; + +	case 132: +	case 133: +	case 134: +	  specify_style (OUTPUT_IFDEF); +	  if (specify_format (&line_format[c - 132], optarg) != 0) +	    error ("conflicting line format", 0, 0); +	  break; + +	case 135: +	  specify_style (OUTPUT_IFDEF); +	  { +	    int i, err = 0; +	    for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++) +	      err |= specify_format (&line_format[i], optarg); +	    if (err) +	      error ("conflicting line format", 0, 0); +	  } +	  break; + +	case 136: +	case 137: +	case 138: +	case 139: +	  specify_style (OUTPUT_IFDEF); +	  if (specify_format (&group_format[c - 136], optarg) != 0) +	    error ("conflicting group format", 0, 0); +	  break; + +	case 140: +	  if (ck_atoi (optarg, &horizon_lines) || horizon_lines < 0) +	    fatal ("horizon must be a nonnegative integer"); +	  break; + +	case 141: +	  usage (0); + +	default: +	  usage (""); +	} +      prev = c; +    } + +  if (optind != argc - 2) +    usage (optind < argc - 2 ? "extra operand" : "missing operand"); + + +  { +    /* +     *	We maximize first the half line width, and then the gutter width, +     *	according to the following constraints: +     *	1.  Two half lines plus a gutter must fit in a line. +     *	2.  If the half line width is nonzero: +     *	    a.  The gutter width is at least GUTTER_WIDTH_MINIMUM. +     *	    b.  If tabs are not expanded to spaces, +     *		a half line plus a gutter is an integral number of tabs, +     *		so that tabs in the right column line up. +     */ +    int t = tab_expand_flag ? 1 : TAB_WIDTH; +    int off = (width + t + GUTTER_WIDTH_MINIMUM) / (2*t)  *  t; +    sdiff_half_width = max (0, min (off - GUTTER_WIDTH_MINIMUM, width - off)), +    sdiff_column2_offset = sdiff_half_width ? off : width; +  } + +  if (output_style != OUTPUT_CONTEXT && output_style != OUTPUT_UNIFIED) +    context = 0; +  else if (context == -1) +    /* Default amount of context for -c.  */ +    context = 3; + +  if (output_style == OUTPUT_IFDEF) +    { +      int i; +      for (i = 0; i < sizeof (line_format) / sizeof (*line_format); i++) +	if (!line_format[i]) +	  line_format[i] = "%l\n"; +      if (!group_format[OLD]) +	group_format[OLD] +	  = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%<"; +      if (!group_format[NEW]) +	group_format[NEW] +	  = group_format[UNCHANGED] ? group_format[UNCHANGED] : "%>"; +      if (!group_format[UNCHANGED]) +	group_format[UNCHANGED] = "%="; +      if (!group_format[CHANGED]) +	group_format[CHANGED] = concat (group_format[OLD], +					group_format[NEW], ""); +    } + +  no_diff_means_no_output = +    (output_style == OUTPUT_IFDEF ? +      (!*group_format[UNCHANGED] +       || (strcmp (group_format[UNCHANGED], "%=") == 0 +	   && !*line_format[UNCHANGED])) +     : output_style == OUTPUT_SDIFF ? sdiff_skip_common_lines : 1); + +  switch_string = option_list (argv + 1, optind - 1); + +  val = compare_files (0, argv[optind], 0, argv[optind + 1], 0); + +  /* Print any messages that were saved up for last.  */ +  print_message_queue (); + +  if (ferror (stdout) || fclose (stdout) != 0) +    fatal ("write error"); +  exit (val); +  return val; +} + +/* Add the compiled form of regexp PATTERN to REGLIST.  */ + +static void +add_regexp (reglist, pattern) +     struct regexp_list **reglist; +     char const *pattern; +{ +  struct regexp_list *r; +  char const *m; + +  r = (struct regexp_list *) xmalloc (sizeof (*r)); +  bzero (r, sizeof (*r)); +  r->buf.fastmap = xmalloc (256); +  m = re_compile_pattern (pattern, strlen (pattern), &r->buf); +  if (m != 0) +    error ("%s: %s", pattern, m); + +  /* Add to the start of the list, since it's easier than the end.  */ +  r->next = *reglist; +  *reglist = r; +} + +static void +usage (reason) +     char const *reason; +{ +  if (reason && *reason) +    fprintf (stderr, "%s: %s\n", program, reason); +  fflush (stderr); +  printf ("Usage: %s [options] from-file to-file\n", program); +  printf ("Options:\n\ +	[-abBcdefhHilnNpPqrstTuvwy] [-C lines] [-D name] [-F regexp]\n\ +	[-I regexp] [-L from-label [-L to-label]] [-S starting-file] [-U lines]\n\ +	[-W columns] [-x pattern] [-X pattern-file]\n"); +  printf ("\ +	[--brief] [--changed-group-format=format] [--context[=lines]] [--ed]\n\ +	[--exclude=pattern] [--exclude-from=pattern-file] [--expand-tabs]\n\ +	[--forward-ed] [--help] [--horizon-lines=lines] [--ifdef=name]\n\ +	[--ignore-all-space] [--ignore-blank-lines] [--ignore-case]\n"); +  printf ("\ +	[--ignore-matching-lines=regexp] [--ignore-space-change]\n\ +	[--initial-tab] [--label=from-label [--label=to-label]]\n\ +	[--left-column] [--minimal] [--new-file] [--new-group-format=format]\n\ +	[--new-line-format=format] [--old-group-format=format]\n"); +  printf ("\ +	[--old-line-format=format] [--paginate] [--rcs] [--recursive]\n\ +	[--report-identical-files] [--sdiff-merge-assist] [--show-c-function]\n\ +	[--show-function-line=regexp] [--side-by-side] [--speed-large-files]\n\ +	[--starting-file=starting-file] [--suppress-common-lines] [--text]\n"); +  printf ("\ +	[--unchanged-group-format=format] [--unchanged-line-format=format]\n\ +	[--unidirectional-new-file] [--unified[=lines]] [--version]\n\ +	[--width=columns]\n"); +  exit (reason ? 2 : 0); +} + +static int +specify_format (var, value) +     char **var; +     char *value; +{ +  int err = *var ? strcmp (*var, value) : 0; +  *var = value; +  return err; +} + +static void +specify_style (style) +     enum output_style style; +{ +  if (output_style != OUTPUT_NORMAL +      && output_style != style) +    error ("conflicting specifications of output style", 0, 0); +  output_style = style; +} + +static char const * +filetype (st) +     struct stat const *st; +{ +  /* See Posix.2 section 4.17.6.1.1 and Table 5-1 for these formats. +     To keep diagnostics grammatical, the returned string must start +     with a consonant.  */ + +  if (S_ISREG (st->st_mode)) +    { +      if (st->st_size == 0) +	return "regular empty file"; +      /* Posix.2 section 5.14.2 seems to suggest that we must read the file +	 and guess whether it's C, Fortran, etc., but this is somewhat useless +	 and doesn't reflect historical practice.  We're allowed to guess +	 wrong, so we don't bother to read the file.  */ +      return "regular file"; +    } +  if (S_ISDIR (st->st_mode)) return "directory"; + +  /* other Posix.1 file types */ +#ifdef S_ISBLK +  if (S_ISBLK (st->st_mode)) return "block special file"; +#endif +#ifdef S_ISCHR +  if (S_ISCHR (st->st_mode)) return "character special file"; +#endif +#ifdef S_ISFIFO +  if (S_ISFIFO (st->st_mode)) return "fifo"; +#endif + +  /* other popular file types */ +  /* S_ISLNK is impossible with `stat'.  */ +#ifdef S_ISSOCK +  if (S_ISSOCK (st->st_mode)) return "socket"; +#endif + +  return "weird file"; +} + +/* Compare two files (or dirs) with specified names +   DIR0/NAME0 and DIR1/NAME1, at level DEPTH in directory recursion. +   (if DIR0 is 0, then the name is just NAME0, etc.) +   This is self-contained; it opens the files and closes them. + +   Value is 0 if files are the same, 1 if different, +   2 if there is a problem opening them.  */ + +static int +compare_files (dir0, name0, dir1, name1, depth) +     char const *dir0, *dir1; +     char const *name0, *name1; +     int depth; +{ +  struct file_data inf[2]; +  register int i; +  int val; +  int same_files; +  int failed = 0; +  char *free0 = 0, *free1 = 0; + +  /* If this is directory comparison, perhaps we have a file +     that exists only in one of the directories. +     If so, just print a message to that effect.  */ + +  if (! ((name0 != 0 && name1 != 0) +	 || (unidirectional_new_file_flag && name1 != 0) +	 || entire_new_file_flag)) +    { +      char const *name = name0 == 0 ? name1 : name0; +      char const *dir = name0 == 0 ? dir1 : dir0; +      message ("Only in %s: %s\n", dir, name); +      /* Return 1 so that diff_dirs will return 1 ("some files differ").  */ +      return 1; +    } + +  bzero (inf, sizeof (inf)); + +  /* Mark any nonexistent file with -1 in the desc field.  */ +  /* Mark unopened files (e.g. directories) with -2. */ + +  inf[0].desc = name0 == 0 ? -1 : -2; +  inf[1].desc = name1 == 0 ? -1 : -2; + +  /* Now record the full name of each file, including nonexistent ones.  */ + +  if (name0 == 0) +    name0 = name1; +  if (name1 == 0) +    name1 = name0; + +  inf[0].name = dir0 == 0 ? name0 : (free0 = dir_file_pathname (dir0, name0)); +  inf[1].name = dir1 == 0 ? name1 : (free1 = dir_file_pathname (dir1, name1)); + +  /* Stat the files.  Record whether they are directories.  */ + +  for (i = 0; i <= 1; i++) +    { +      if (inf[i].desc != -1) +	{ +	  int stat_result; + +	  if (i && strcmp (inf[i].name, inf[0].name) == 0) +	    { +	      inf[i].stat = inf[0].stat; +	      stat_result = 0; +	    } +	  else if (strcmp (inf[i].name, "-") == 0) +	    { +	      inf[i].desc = STDIN_FILENO; +	      stat_result = fstat (STDIN_FILENO, &inf[i].stat); +	      if (stat_result == 0 && S_ISREG (inf[i].stat.st_mode)) +		{ +		  off_t pos = lseek (STDIN_FILENO, (off_t) 0, SEEK_CUR); +		  if (pos == -1) +		    stat_result = -1; +		  else +		    { +		      if (pos <= inf[i].stat.st_size) +			inf[i].stat.st_size -= pos; +		      else +			inf[i].stat.st_size = 0; +		      /* Posix.2 4.17.6.1.4 requires current time for stdin.  */ +		      time (&inf[i].stat.st_mtime); +		    } +		} +	    } +	  else +	    stat_result = stat (inf[i].name, &inf[i].stat); + +	  if (stat_result != 0) +	    { +	      perror_with_name (inf[i].name); +	      failed = 1; +	    } +	  else +	    { +	      inf[i].dir_p = S_ISDIR (inf[i].stat.st_mode) && inf[i].desc != 0; +	      if (inf[1 - i].desc == -1) +		{ +		  inf[1 - i].dir_p = inf[i].dir_p; +		  inf[1 - i].stat.st_mode = inf[i].stat.st_mode; +		} +	    } +	} +    } + +  if (! failed && depth == 0 && inf[0].dir_p != inf[1].dir_p) +    { +      /* If one is a directory, and it was specified in the command line, +	 use the file in that dir with the other file's basename.  */ + +      int fnm_arg = inf[0].dir_p; +      int dir_arg = 1 - fnm_arg; +      char const *fnm = inf[fnm_arg].name; +      char const *dir = inf[dir_arg].name; +      char const *p = strrchr (fnm, '/'); +      char const *filename = inf[dir_arg].name +	= dir_file_pathname (dir, p ? p + 1 : fnm); + +      if (strcmp (fnm, "-") == 0) +	fatal ("can't compare - to a directory"); + +      if (stat (filename, &inf[dir_arg].stat) != 0) +	{ +	  perror_with_name (filename); +	  failed = 1; +	} +      else +	inf[dir_arg].dir_p = S_ISDIR (inf[dir_arg].stat.st_mode); +    } + +  if (failed) +    { + +      /* If either file should exist but does not, return 2.  */ + +      val = 2; + +    } +  else if ((same_files =    inf[0].stat.st_ino == inf[1].stat.st_ino +			 && inf[0].stat.st_dev == inf[1].stat.st_dev +			 && inf[0].stat.st_size == inf[1].stat.st_size +			 && inf[0].desc != -1 +			 && inf[1].desc != -1) +	   && no_diff_means_no_output) +    { +      /* The two named files are actually the same physical file. +	 We know they are identical without actually reading them.  */ + +      val = 0; +    } +  else if (inf[0].dir_p & inf[1].dir_p) +    { +      if (output_style == OUTPUT_IFDEF) +	fatal ("-D option not supported with directories"); + +      /* If both are directories, compare the files in them.  */ + +      if (depth > 0 && !recursive) +	{ +	  /* But don't compare dir contents one level down +	     unless -r was specified.  */ +	  message ("Common subdirectories: %s and %s\n", +		   inf[0].name, inf[1].name); +	  val = 0; +	} +      else +	{ +	  val = diff_dirs (inf, compare_files, depth); +	} + +    } +  else if ((inf[0].dir_p | inf[1].dir_p) +	   || (depth > 0 +	       && (! S_ISREG (inf[0].stat.st_mode) +		   || ! S_ISREG (inf[1].stat.st_mode)))) +    { +      /* Perhaps we have a subdirectory that exists only in one directory. +	 If so, just print a message to that effect.  */ + +      if (inf[0].desc == -1 || inf[1].desc == -1) +	{ +	  if ((inf[0].dir_p | inf[1].dir_p) +	      && recursive +	      && (entire_new_file_flag +		  || (unidirectional_new_file_flag && inf[0].desc == -1))) +	    val = diff_dirs (inf, compare_files, depth); +	  else +	    { +	      char const *dir = (inf[0].desc == -1) ? dir1 : dir0; +	      /* See Posix.2 section 4.17.6.1.1 for this format.  */ +	      message ("Only in %s: %s\n", dir, name0); +	      val = 1; +	    } +	} +      else +	{ +	  /* We have two files that are not to be compared.  */ + +	  /* See Posix.2 section 4.17.6.1.1 for this format.  */ +	  message5 ("File %s is a %s while file %s is a %s\n", +		    inf[0].name, filetype (&inf[0].stat), +		    inf[1].name, filetype (&inf[1].stat)); + +	  /* This is a difference.  */ +	  val = 1; +	} +    } +  else if ((no_details_flag & ~ignore_some_changes) +	   && inf[0].stat.st_size != inf[1].stat.st_size +	   && (inf[0].desc == -1 || S_ISREG (inf[0].stat.st_mode)) +	   && (inf[1].desc == -1 || S_ISREG (inf[1].stat.st_mode))) +    { +      message ("Files %s and %s differ\n", inf[0].name, inf[1].name); +      val = 1; +    } +  else +    { +      /* Both exist and neither is a directory.  */ + +      /* Open the files and record their descriptors.  */ + +      if (inf[0].desc == -2) +	if ((inf[0].desc = open (inf[0].name, O_RDONLY, 0)) < 0) +	  { +	    perror_with_name (inf[0].name); +	    failed = 1; +	  } +      if (inf[1].desc == -2) +	if (same_files) +	  inf[1].desc = inf[0].desc; +	else if ((inf[1].desc = open (inf[1].name, O_RDONLY, 0)) < 0) +	  { +	    perror_with_name (inf[1].name); +	    failed = 1; +	  } + +      /* Compare the files, if no error was found.  */ + +      val = failed ? 2 : diff_2_files (inf, depth); + +      /* Close the file descriptors.  */ + +      if (inf[0].desc >= 0 && close (inf[0].desc) != 0) +	{ +	  perror_with_name (inf[0].name); +	  val = 2; +	} +      if (inf[1].desc >= 0 && inf[0].desc != inf[1].desc +	  && close (inf[1].desc) != 0) +	{ +	  perror_with_name (inf[1].name); +	  val = 2; +	} +    } + +  /* Now the comparison has been done, if no error prevented it, +     and VAL is the value this function will return.  */ + +  if (val == 0 && !inf[0].dir_p) +    { +      if (print_file_same_flag) +	message ("Files %s and %s are identical\n", +		 inf[0].name, inf[1].name); +    } +  else +    fflush (stdout); + +  if (free0) +    free (free0); +  if (free1) +    free (free1); + +  return val; +}  | 
