1170754Sdelphij/* cmp - compare two files byte by byte
2170754Sdelphij
3170754Sdelphij   Copyright (C) 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1998, 2001,
4170754Sdelphij   2002, 2004 Free Software Foundation, Inc.
5170754Sdelphij
6170754Sdelphij   This program is free software; you can redistribute it and/or modify
7170754Sdelphij   it under the terms of the GNU General Public License as published by
8170754Sdelphij   the Free Software Foundation; either version 2, or (at your option)
9170754Sdelphij   any later version.
10170754Sdelphij
11170754Sdelphij   This program is distributed in the hope that it will be useful,
12170754Sdelphij   but WITHOUT ANY WARRANTY; without even the implied warranty of
13170754Sdelphij   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
14170754Sdelphij   See the GNU General Public License for more details.
15170754Sdelphij
16170754Sdelphij   You should have received a copy of the GNU General Public License
17170754Sdelphij   along with this program; see the file COPYING.
18170754Sdelphij   If not, write to the Free Software Foundation,
19170754Sdelphij   59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  */
20170754Sdelphij
21170754Sdelphij#include "system.h"
22170754Sdelphij#include "paths.h"
23170754Sdelphij
24170754Sdelphij#include <stdio.h>
25170754Sdelphij
26170754Sdelphij#include <c-stack.h>
27170754Sdelphij#include <cmpbuf.h>
28170754Sdelphij#include <error.h>
29170754Sdelphij#include <exit.h>
30170754Sdelphij#include <exitfail.h>
31170754Sdelphij#include <file-type.h>
32170754Sdelphij#include <getopt.h>
33170754Sdelphij#include <hard-locale.h>
34170754Sdelphij#include <inttostr.h>
35170754Sdelphij#include <setmode.h>
36170754Sdelphij#include <unlocked-io.h>
37170754Sdelphij#include <version-etc.h>
38170754Sdelphij#include <xalloc.h>
39170754Sdelphij#include <xstrtol.h>
40170754Sdelphij
41170754Sdelphij#if defined LC_MESSAGES && ENABLE_NLS
42170754Sdelphij# define hard_locale_LC_MESSAGES hard_locale (LC_MESSAGES)
43170754Sdelphij#else
44170754Sdelphij# define hard_locale_LC_MESSAGES 0
45170754Sdelphij#endif
46170754Sdelphij
47170754Sdelphijstatic int cmp (void);
48170754Sdelphijstatic off_t file_position (int);
49170754Sdelphijstatic size_t block_compare (word const *, word const *);
50170754Sdelphijstatic size_t block_compare_and_count (word const *, word const *, off_t *);
51170754Sdelphijstatic void sprintc (char *, unsigned char);
52170754Sdelphij
53170754Sdelphij/* Name under which this program was invoked.  */
54170754Sdelphijchar *program_name;
55170754Sdelphij
56170754Sdelphij/* Filenames of the compared files.  */
57170754Sdelphijstatic char const *file[2];
58170754Sdelphij
59170754Sdelphij/* File descriptors of the files.  */
60170754Sdelphijstatic int file_desc[2];
61170754Sdelphij
62170754Sdelphij/* Status of the files.  */
63170754Sdelphijstatic struct stat stat_buf[2];
64170754Sdelphij
65170754Sdelphij/* Read buffers for the files.  */
66170754Sdelphijstatic word *buffer[2];
67170754Sdelphij
68170754Sdelphij/* Optimal block size for the files.  */
69170754Sdelphijstatic size_t buf_size;
70170754Sdelphij
71170754Sdelphij/* Initial prefix to ignore for each file.  */
72170754Sdelphijstatic off_t ignore_initial[2];
73170754Sdelphij
74170754Sdelphij/* Number of bytes to compare.  */
75170754Sdelphijstatic uintmax_t bytes = UINTMAX_MAX;
76170754Sdelphij
77170754Sdelphij/* Output format.  */
78170754Sdelphijstatic enum comparison_type
79170754Sdelphij  {
80170754Sdelphij    type_first_diff,	/* Print the first difference.  */
81170754Sdelphij    type_all_diffs,	/* Print all differences.  */
82170754Sdelphij    type_status		/* Exit status only.  */
83170754Sdelphij  } comparison_type;
84170754Sdelphij
85170754Sdelphij/* If nonzero, print values of bytes quoted like cat -t does. */
86170754Sdelphijstatic bool opt_print_bytes;
87170754Sdelphij
88170754Sdelphij/* Values for long options that do not have single-letter equivalents.  */
89170754Sdelphijenum
90170754Sdelphij{
91170754Sdelphij  HELP_OPTION = CHAR_MAX + 1
92170754Sdelphij};
93170754Sdelphij
94170754Sdelphijstatic struct option const long_options[] =
95170754Sdelphij{
96170754Sdelphij  {"print-bytes", 0, 0, 'b'},
97170754Sdelphij  {"print-chars", 0, 0, 'c'}, /* obsolescent as of diffutils 2.7.3 */
98170754Sdelphij  {"ignore-initial", 1, 0, 'i'},
99170754Sdelphij  {"verbose", 0, 0, 'l'},
100170754Sdelphij  {"bytes", 1, 0, 'n'},
101170754Sdelphij  {"silent", 0, 0, 's'},
102170754Sdelphij  {"quiet", 0, 0, 's'},
103170754Sdelphij  {"version", 0, 0, 'v'},
104170754Sdelphij  {"help", 0, 0, HELP_OPTION},
105170754Sdelphij  {0, 0, 0, 0}
106170754Sdelphij};
107170754Sdelphij
108170754Sdelphijstatic void try_help (char const *, char const *) __attribute__((noreturn));
109170754Sdelphijstatic void
110170754Sdelphijtry_help (char const *reason_msgid, char const *operand)
111170754Sdelphij{
112170754Sdelphij  if (reason_msgid)
113170754Sdelphij    error (0, 0, _(reason_msgid), operand);
114170754Sdelphij  error (EXIT_TROUBLE, 0,
115170754Sdelphij	 _("Try `%s --help' for more information."), program_name);
116170754Sdelphij  abort ();
117170754Sdelphij}
118170754Sdelphij
119170754Sdelphijstatic char const valid_suffixes[] = "kKMGTPEZY0";
120170754Sdelphij
121170754Sdelphij/* Update ignore_initial[F] according to the result of parsing an
122170754Sdelphij   *operand ARGPTR of --ignore-initial, updating *ARGPTR to point
123170754Sdelphij   *after the operand.  If DELIMITER is nonzero, the operand may be
124170754Sdelphij   *followed by DELIMITER; otherwise it must be null-terminated.  */
125170754Sdelphijstatic void
126170754Sdelphijspecify_ignore_initial (int f, char **argptr, char delimiter)
127170754Sdelphij{
128170754Sdelphij  uintmax_t val;
129170754Sdelphij  off_t o;
130170754Sdelphij  char const *arg = *argptr;
131170754Sdelphij  strtol_error e = xstrtoumax (arg, argptr, 0, &val, valid_suffixes);
132170754Sdelphij  if (! (e == LONGINT_OK
133170754Sdelphij	 || (e == LONGINT_INVALID_SUFFIX_CHAR && **argptr == delimiter))
134170754Sdelphij      || (o = val) < 0 || o != val || val == UINTMAX_MAX)
135170754Sdelphij    try_help ("invalid --ignore-initial value `%s'", arg);
136170754Sdelphij  if (ignore_initial[f] < o)
137170754Sdelphij    ignore_initial[f] = o;
138170754Sdelphij}
139170754Sdelphij
140170754Sdelphij/* Specify the output format.  */
141170754Sdelphijstatic void
142170754Sdelphijspecify_comparison_type (enum comparison_type t)
143170754Sdelphij{
144170754Sdelphij  if (comparison_type && comparison_type != t)
145170754Sdelphij    try_help ("options -l and -s are incompatible", 0);
146170754Sdelphij  comparison_type = t;
147170754Sdelphij}
148170754Sdelphij
149170754Sdelphijstatic void
150170754Sdelphijcheck_stdout (void)
151170754Sdelphij{
152170754Sdelphij  if (ferror (stdout))
153170754Sdelphij    error (EXIT_TROUBLE, 0, "%s", _("write failed"));
154170754Sdelphij  else if (fclose (stdout) != 0)
155170754Sdelphij    error (EXIT_TROUBLE, errno, "%s", _("standard output"));
156170754Sdelphij}
157170754Sdelphij
158170754Sdelphijstatic char const * const option_help_msgid[] = {
159170754Sdelphij  N_("-b  --print-bytes  Print differing bytes."),
160170754Sdelphij  N_("-i SKIP  --ignore-initial=SKIP  Skip the first SKIP bytes of input."),
161170754Sdelphij  N_("-i SKIP1:SKIP2  --ignore-initial=SKIP1:SKIP2"),
162170754Sdelphij  N_("  Skip the first SKIP1 bytes of FILE1 and the first SKIP2 bytes of FILE2."),
163170754Sdelphij  N_("-l  --verbose  Output byte numbers and values of all differing bytes."),
164170754Sdelphij  N_("-n LIMIT  --bytes=LIMIT  Compare at most LIMIT bytes."),
165170754Sdelphij  N_("-s  --quiet  --silent  Output nothing; yield exit status only."),
166170754Sdelphij  N_("-v  --version  Output version info."),
167170754Sdelphij  N_("--help  Output this help."),
168170754Sdelphij  0
169170754Sdelphij};
170170754Sdelphij
171170754Sdelphijstatic void
172170754Sdelphijusage (void)
173170754Sdelphij{
174170754Sdelphij  char const * const *p;
175170754Sdelphij
176170754Sdelphij  printf (_("Usage: %s [OPTION]... FILE1 [FILE2 [SKIP1 [SKIP2]]]\n"),
177170754Sdelphij	  program_name);
178170754Sdelphij  printf ("%s\n\n", _("Compare two files byte by byte."));
179170754Sdelphij  for (p = option_help_msgid;  *p;  p++)
180170754Sdelphij    printf ("  %s\n", _(*p));
181170754Sdelphij  printf ("\n%s\n%s\n\n%s\n%s\n\n%s\n",
182170754Sdelphij	  _("SKIP1 and SKIP2 are the number of bytes to skip in each file."),
183170754Sdelphij	  _("SKIP values may be followed by the following multiplicative suffixes:\n\
184170754SdelphijkB 1000, K 1024, MB 1,000,000, M 1,048,576,\n\
185170754SdelphijGB 1,000,000,000, G 1,073,741,824, and so on for T, P, E, Z, Y."),
186170754Sdelphij	  _("If a FILE is `-' or missing, read standard input."),
187170754Sdelphij	  _("Exit status is 0 if inputs are the same, 1 if different, 2 if trouble."),
188170754Sdelphij	  _("Report bugs to <bug-gnu-utils@gnu.org>."));
189170754Sdelphij}
190170754Sdelphij
191170754Sdelphijint
192170754Sdelphijmain (int argc, char **argv)
193170754Sdelphij{
194170754Sdelphij  int c, f, exit_status;
195170754Sdelphij  size_t words_per_buffer;
196170754Sdelphij
197170754Sdelphij  exit_failure = EXIT_TROUBLE;
198170754Sdelphij  initialize_main (&argc, &argv);
199170754Sdelphij  program_name = argv[0];
200170754Sdelphij  setlocale (LC_ALL, "");
201170754Sdelphij  bindtextdomain (PACKAGE, LOCALEDIR);
202170754Sdelphij  textdomain (PACKAGE);
203170754Sdelphij  c_stack_action (0);
204170754Sdelphij
205170754Sdelphij  /* Parse command line options.  */
206170754Sdelphij
207170754Sdelphij  while ((c = getopt_long (argc, argv, "bci:ln:sv", long_options, 0))
208170754Sdelphij	 != -1)
209170754Sdelphij    switch (c)
210170754Sdelphij      {
211170754Sdelphij      case 'b':
212170754Sdelphij      case 'c': /* 'c' is obsolescent as of diffutils 2.7.3 */
213170754Sdelphij	opt_print_bytes = true;
214170754Sdelphij	break;
215170754Sdelphij
216170754Sdelphij      case 'i':
217170754Sdelphij	specify_ignore_initial (0, &optarg, ':');
218170754Sdelphij	if (*optarg++ == ':')
219170754Sdelphij	  specify_ignore_initial (1, &optarg, 0);
220170754Sdelphij	else if (ignore_initial[1] < ignore_initial[0])
221170754Sdelphij	  ignore_initial[1] = ignore_initial[0];
222170754Sdelphij	break;
223170754Sdelphij
224170754Sdelphij      case 'l':
225170754Sdelphij	specify_comparison_type (type_all_diffs);
226170754Sdelphij	break;
227170754Sdelphij
228170754Sdelphij      case 'n':
229170754Sdelphij	{
230170754Sdelphij	  uintmax_t n;
231170754Sdelphij	  if (xstrtoumax (optarg, 0, 0, &n, valid_suffixes) != LONGINT_OK)
232170754Sdelphij	    try_help ("invalid --bytes value `%s'", optarg);
233170754Sdelphij	  if (n < bytes)
234170754Sdelphij	    bytes = n;
235170754Sdelphij	}
236170754Sdelphij	break;
237170754Sdelphij
238170754Sdelphij      case 's':
239170754Sdelphij	specify_comparison_type (type_status);
240170754Sdelphij	break;
241170754Sdelphij
242170754Sdelphij      case 'v':
243170754Sdelphij	/* TRANSLATORS: Please translate the second "o" in "Torbjorn
244170754Sdelphij	   Granlund" to an o-with-umlaut (U+00F6, LATIN SMALL LETTER O
245170754Sdelphij	   WITH DIAERESIS) if possible.  */
246170754Sdelphij	version_etc (stdout, "cmp", PACKAGE_NAME, PACKAGE_VERSION,
247170754Sdelphij		     _("Torbjorn Granlund"), "David MacKenzie", (char *) 0);
248170754Sdelphij	check_stdout ();
249170754Sdelphij	return EXIT_SUCCESS;
250170754Sdelphij
251170754Sdelphij      case HELP_OPTION:
252170754Sdelphij	usage ();
253170754Sdelphij	check_stdout ();
254170754Sdelphij	return EXIT_SUCCESS;
255170754Sdelphij
256170754Sdelphij      default:
257170754Sdelphij	try_help (0, 0);
258170754Sdelphij      }
259170754Sdelphij
260170754Sdelphij  if (optind == argc)
261170754Sdelphij    try_help ("missing operand after `%s'", argv[argc - 1]);
262170754Sdelphij
263170754Sdelphij  file[0] = argv[optind++];
264170754Sdelphij  file[1] = optind < argc ? argv[optind++] : "-";
265170754Sdelphij
266170754Sdelphij  for (f = 0; f < 2 && optind < argc; f++)
267170754Sdelphij    {
268170754Sdelphij      char *arg = argv[optind++];
269170754Sdelphij      specify_ignore_initial (f, &arg, 0);
270170754Sdelphij    }
271170754Sdelphij
272170754Sdelphij  if (optind < argc)
273170754Sdelphij    try_help ("extra operand `%s'", argv[optind]);
274170754Sdelphij
275170754Sdelphij  for (f = 0; f < 2; f++)
276170754Sdelphij    {
277170754Sdelphij      /* If file[1] is "-", treat it first; this avoids a misdiagnostic if
278170754Sdelphij	 stdin is closed and opening file[0] yields file descriptor 0.  */
279170754Sdelphij      int f1 = f ^ (strcmp (file[1], "-") == 0);
280170754Sdelphij
281170754Sdelphij      /* Two files with the same name and offset are identical.
282170754Sdelphij	 But wait until we open the file once, for proper diagnostics.  */
283170754Sdelphij      if (f && ignore_initial[0] == ignore_initial[1]
284170754Sdelphij	  && file_name_cmp (file[0], file[1]) == 0)
285170754Sdelphij	return EXIT_SUCCESS;
286170754Sdelphij
287170754Sdelphij      file_desc[f1] = (strcmp (file[f1], "-") == 0
288170754Sdelphij		       ? STDIN_FILENO
289170754Sdelphij		       : open (file[f1], O_RDONLY, 0));
290170754Sdelphij      if (file_desc[f1] < 0 || fstat (file_desc[f1], stat_buf + f1) != 0)
291170754Sdelphij	{
292170754Sdelphij	  if (file_desc[f1] < 0 && comparison_type == type_status)
293170754Sdelphij	    exit (EXIT_TROUBLE);
294170754Sdelphij	  else
295170754Sdelphij	    error (EXIT_TROUBLE, errno, "%s", file[f1]);
296170754Sdelphij	}
297170754Sdelphij
298170754Sdelphij      set_binary_mode (file_desc[f1], true);
299170754Sdelphij    }
300170754Sdelphij
301170754Sdelphij  /* If the files are links to the same inode and have the same file position,
302170754Sdelphij     they are identical.  */
303170754Sdelphij
304170754Sdelphij  if (0 < same_file (&stat_buf[0], &stat_buf[1])
305170754Sdelphij      && same_file_attributes (&stat_buf[0], &stat_buf[1])
306170754Sdelphij      && file_position (0) == file_position (1))
307170754Sdelphij    return EXIT_SUCCESS;
308170754Sdelphij
309170754Sdelphij  /* If output is redirected to the null device, we may assume `-s'.  */
310170754Sdelphij
311170754Sdelphij  if (comparison_type != type_status)
312170754Sdelphij    {
313170754Sdelphij      struct stat outstat, nullstat;
314170754Sdelphij
315170754Sdelphij      if (fstat (STDOUT_FILENO, &outstat) == 0
316170754Sdelphij	  && stat (NULL_DEVICE, &nullstat) == 0
317170754Sdelphij	  && 0 < same_file (&outstat, &nullstat))
318170754Sdelphij	comparison_type = type_status;
319170754Sdelphij    }
320170754Sdelphij
321170754Sdelphij  /* If only a return code is needed,
322170754Sdelphij     and if both input descriptors are associated with plain files,
323170754Sdelphij     conclude that the files differ if they have different sizes
324170754Sdelphij     and if more bytes will be compared than are in the smaller file.  */
325170754Sdelphij
326170754Sdelphij  if (comparison_type == type_status
327170754Sdelphij      && S_ISREG (stat_buf[0].st_mode)
328170754Sdelphij      && S_ISREG (stat_buf[1].st_mode))
329170754Sdelphij    {
330170754Sdelphij      off_t s0 = stat_buf[0].st_size - file_position (0);
331170754Sdelphij      off_t s1 = stat_buf[1].st_size - file_position (1);
332170754Sdelphij      if (s0 < 0)
333170754Sdelphij	s0 = 0;
334170754Sdelphij      if (s1 < 0)
335170754Sdelphij	s1 = 0;
336170754Sdelphij      if (s0 != s1 && MIN (s0, s1) < bytes)
337170754Sdelphij	exit (EXIT_FAILURE);
338170754Sdelphij    }
339170754Sdelphij
340170754Sdelphij  /* Get the optimal block size of the files.  */
341170754Sdelphij
342170754Sdelphij  buf_size = buffer_lcm (STAT_BLOCKSIZE (stat_buf[0]),
343170754Sdelphij			 STAT_BLOCKSIZE (stat_buf[1]),
344170754Sdelphij			 PTRDIFF_MAX - sizeof (word));
345170754Sdelphij
346170754Sdelphij  /* Allocate word-aligned buffers, with space for sentinels at the end.  */
347170754Sdelphij
348170754Sdelphij  words_per_buffer = (buf_size + 2 * sizeof (word) - 1) / sizeof (word);
349170754Sdelphij  buffer[0] = xmalloc (2 * sizeof (word) * words_per_buffer);
350170754Sdelphij  buffer[1] = buffer[0] + words_per_buffer;
351170754Sdelphij
352170754Sdelphij  exit_status = cmp ();
353170754Sdelphij
354170754Sdelphij  for (f = 0; f < 2; f++)
355170754Sdelphij    if (close (file_desc[f]) != 0)
356170754Sdelphij      error (EXIT_TROUBLE, errno, "%s", file[f]);
357170754Sdelphij  if (exit_status != 0  &&  comparison_type != type_status)
358170754Sdelphij    check_stdout ();
359170754Sdelphij  exit (exit_status);
360170754Sdelphij  return exit_status;
361170754Sdelphij}
362170754Sdelphij
363170754Sdelphij/* Compare the two files already open on `file_desc[0]' and `file_desc[1]',
364170754Sdelphij   using `buffer[0]' and `buffer[1]'.
365170754Sdelphij   Return EXIT_SUCCESS if identical, EXIT_FAILURE if different,
366170754Sdelphij   >1 if error.  */
367170754Sdelphij
368170754Sdelphijstatic int
369170754Sdelphijcmp (void)
370170754Sdelphij{
371170754Sdelphij  off_t line_number = 1;	/* Line number (1...) of difference. */
372170754Sdelphij  off_t byte_number = 1;	/* Byte number (1...) of difference. */
373170754Sdelphij  uintmax_t remaining = bytes;	/* Remaining number of bytes to compare.  */
374170754Sdelphij  size_t read0, read1;		/* Number of bytes read from each file. */
375170754Sdelphij  size_t first_diff;		/* Offset (0...) in buffers of 1st diff. */
376170754Sdelphij  size_t smaller;		/* The lesser of `read0' and `read1'. */
377170754Sdelphij  word *buffer0 = buffer[0];
378170754Sdelphij  word *buffer1 = buffer[1];
379170754Sdelphij  char *buf0 = (char *) buffer0;
380170754Sdelphij  char *buf1 = (char *) buffer1;
381170754Sdelphij  int ret = EXIT_SUCCESS;
382170754Sdelphij  int f;
383170754Sdelphij  int offset_width;
384170754Sdelphij
385170754Sdelphij  if (comparison_type == type_all_diffs)
386170754Sdelphij    {
387170754Sdelphij      off_t byte_number_max = MIN (bytes, TYPE_MAXIMUM (off_t));
388170754Sdelphij
389170754Sdelphij      for (f = 0; f < 2; f++)
390170754Sdelphij	if (S_ISREG (stat_buf[f].st_mode))
391170754Sdelphij	  {
392170754Sdelphij	    off_t file_bytes = stat_buf[f].st_size - file_position (f);
393170754Sdelphij	    if (file_bytes < byte_number_max)
394170754Sdelphij	      byte_number_max = file_bytes;
395170754Sdelphij	  }
396170754Sdelphij
397170754Sdelphij      for (offset_width = 1; (byte_number_max /= 10) != 0; offset_width++)
398170754Sdelphij	continue;
399170754Sdelphij    }
400170754Sdelphij
401170754Sdelphij  for (f = 0; f < 2; f++)
402170754Sdelphij    {
403170754Sdelphij      off_t ig = ignore_initial[f];
404170754Sdelphij      if (ig && file_position (f) == -1)
405170754Sdelphij	{
406170754Sdelphij	  /* lseek failed; read and discard the ignored initial prefix.  */
407170754Sdelphij	  do
408170754Sdelphij	    {
409170754Sdelphij	      size_t bytes_to_read = MIN (ig, buf_size);
410170754Sdelphij	      size_t r = block_read (file_desc[f], buf0, bytes_to_read);
411170754Sdelphij	      if (r != bytes_to_read)
412170754Sdelphij		{
413170754Sdelphij		  if (r == SIZE_MAX)
414170754Sdelphij		    error (EXIT_TROUBLE, errno, "%s", file[f]);
415170754Sdelphij		  break;
416170754Sdelphij		}
417170754Sdelphij	      ig -= r;
418170754Sdelphij	    }
419170754Sdelphij	  while (ig);
420170754Sdelphij	}
421170754Sdelphij    }
422170754Sdelphij
423170754Sdelphij  do
424170754Sdelphij    {
425170754Sdelphij      size_t bytes_to_read = buf_size;
426170754Sdelphij
427170754Sdelphij      if (remaining != UINTMAX_MAX)
428170754Sdelphij	{
429170754Sdelphij	  if (remaining < bytes_to_read)
430170754Sdelphij	    bytes_to_read = remaining;
431170754Sdelphij	  remaining -= bytes_to_read;
432170754Sdelphij	}
433170754Sdelphij
434170754Sdelphij      read0 = block_read (file_desc[0], buf0, bytes_to_read);
435170754Sdelphij      if (read0 == SIZE_MAX)
436170754Sdelphij	error (EXIT_TROUBLE, errno, "%s", file[0]);
437170754Sdelphij      read1 = block_read (file_desc[1], buf1, bytes_to_read);
438170754Sdelphij      if (read1 == SIZE_MAX)
439170754Sdelphij	error (EXIT_TROUBLE, errno, "%s", file[1]);
440170754Sdelphij
441170754Sdelphij      /* Insert sentinels for the block compare.  */
442170754Sdelphij
443170754Sdelphij      buf0[read0] = ~buf1[read0];
444170754Sdelphij      buf1[read1] = ~buf0[read1];
445170754Sdelphij
446170754Sdelphij      /* If the line number should be written for differing files,
447170754Sdelphij	 compare the blocks and count the number of newlines
448170754Sdelphij	 simultaneously.  */
449170754Sdelphij      first_diff = (comparison_type == type_first_diff
450170754Sdelphij		    ? block_compare_and_count (buffer0, buffer1, &line_number)
451170754Sdelphij		    : block_compare (buffer0, buffer1));
452170754Sdelphij
453170754Sdelphij      byte_number += first_diff;
454170754Sdelphij      smaller = MIN (read0, read1);
455170754Sdelphij
456170754Sdelphij      if (first_diff < smaller)
457170754Sdelphij	{
458170754Sdelphij	  switch (comparison_type)
459170754Sdelphij	    {
460170754Sdelphij	    case type_first_diff:
461170754Sdelphij	      {
462170754Sdelphij		char byte_buf[INT_BUFSIZE_BOUND (off_t)];
463170754Sdelphij		char line_buf[INT_BUFSIZE_BOUND (off_t)];
464170754Sdelphij		char const *byte_num = offtostr (byte_number, byte_buf);
465170754Sdelphij		char const *line_num = offtostr (line_number, line_buf);
466170754Sdelphij		if (!opt_print_bytes)
467170754Sdelphij		  {
468170754Sdelphij		    /* See POSIX 1003.1-2001 for this format.  This
469170754Sdelphij		       message is used only in the POSIX locale, so it
470170754Sdelphij		       need not be translated.  */
471170754Sdelphij		    static char const char_message[] =
472170754Sdelphij		      "%s %s differ: char %s, line %s\n";
473170754Sdelphij
474170754Sdelphij		    /* The POSIX rationale recommends using the word
475170754Sdelphij		       "byte" outside the POSIX locale.  Some gettext
476170754Sdelphij		       implementations translate even in the POSIX
477170754Sdelphij		       locale if certain other environment variables
478170754Sdelphij		       are set, so use "byte" if a translation is
479170754Sdelphij		       available, or if outside the POSIX locale.  */
480170754Sdelphij		    static char const byte_msgid[] =
481170754Sdelphij		      N_("%s %s differ: byte %s, line %s\n");
482170754Sdelphij		    char const *byte_message = _(byte_msgid);
483170754Sdelphij		    bool use_byte_message = (byte_message != byte_msgid
484170754Sdelphij					     || hard_locale_LC_MESSAGES);
485170754Sdelphij
486170754Sdelphij		    printf (use_byte_message ? byte_message : char_message,
487170754Sdelphij			    file[0], file[1], byte_num, line_num);
488170754Sdelphij		  }
489170754Sdelphij		else
490170754Sdelphij		  {
491170754Sdelphij		    unsigned char c0 = buf0[first_diff];
492170754Sdelphij		    unsigned char c1 = buf1[first_diff];
493170754Sdelphij		    char s0[5];
494170754Sdelphij		    char s1[5];
495170754Sdelphij		    sprintc (s0, c0);
496170754Sdelphij		    sprintc (s1, c1);
497170754Sdelphij		    printf (_("%s %s differ: byte %s, line %s is %3o %s %3o %s\n"),
498170754Sdelphij			    file[0], file[1], byte_num, line_num,
499170754Sdelphij			    c0, s0, c1, s1);
500170754Sdelphij		}
501170754Sdelphij	      }
502170754Sdelphij	      /* Fall through.  */
503170754Sdelphij	    case type_status:
504170754Sdelphij	      return EXIT_FAILURE;
505170754Sdelphij
506170754Sdelphij	    case type_all_diffs:
507170754Sdelphij	      do
508170754Sdelphij		{
509170754Sdelphij		  unsigned char c0 = buf0[first_diff];
510170754Sdelphij		  unsigned char c1 = buf1[first_diff];
511170754Sdelphij		  if (c0 != c1)
512170754Sdelphij		    {
513170754Sdelphij		      char byte_buf[INT_BUFSIZE_BOUND (off_t)];
514170754Sdelphij		      char const *byte_num = offtostr (byte_number, byte_buf);
515170754Sdelphij		      if (!opt_print_bytes)
516170754Sdelphij			{
517170754Sdelphij			  /* See POSIX 1003.1-2001 for this format.  */
518170754Sdelphij			  printf ("%*s %3o %3o\n",
519170754Sdelphij				  offset_width, byte_num, c0, c1);
520170754Sdelphij			}
521170754Sdelphij		      else
522170754Sdelphij			{
523170754Sdelphij			  char s0[5];
524170754Sdelphij			  char s1[5];
525170754Sdelphij			  sprintc (s0, c0);
526170754Sdelphij			  sprintc (s1, c1);
527170754Sdelphij			  printf ("%*s %3o %-4s %3o %s\n",
528170754Sdelphij				  offset_width, byte_num, c0, s0, c1, s1);
529170754Sdelphij			}
530170754Sdelphij		    }
531170754Sdelphij		  byte_number++;
532170754Sdelphij		  first_diff++;
533170754Sdelphij		}
534170754Sdelphij	      while (first_diff < smaller);
535170754Sdelphij	      ret = EXIT_FAILURE;
536170754Sdelphij	      break;
537170754Sdelphij	    }
538170754Sdelphij	}
539170754Sdelphij
540170754Sdelphij      if (read0 != read1)
541170754Sdelphij	{
542170754Sdelphij	  if (comparison_type != type_status)
543170754Sdelphij	    {
544170754Sdelphij	      /* See POSIX 1003.1-2001 for this format.  */
545170754Sdelphij	      fprintf (stderr, _("cmp: EOF on %s\n"), file[read1 < read0]);
546170754Sdelphij	    }
547170754Sdelphij
548170754Sdelphij	  return EXIT_FAILURE;
549170754Sdelphij	}
550170754Sdelphij    }
551170754Sdelphij  while (read0 == buf_size);
552170754Sdelphij
553170754Sdelphij  return ret;
554170754Sdelphij}
555170754Sdelphij
556170754Sdelphij/* Compare two blocks of memory P0 and P1 until they differ,
557170754Sdelphij   and count the number of '\n' occurrences in the common
558170754Sdelphij   part of P0 and P1.
559170754Sdelphij   If the blocks are not guaranteed to be different, put sentinels at the ends
560170754Sdelphij   of the blocks before calling this function.
561170754Sdelphij
562170754Sdelphij   Return the offset of the first byte that differs.
563170754Sdelphij   Increment *COUNT by the count of '\n' occurrences.  */
564170754Sdelphij
565170754Sdelphijstatic size_t
566170754Sdelphijblock_compare_and_count (word const *p0, word const *p1, off_t *count)
567170754Sdelphij{
568170754Sdelphij  word l;		/* One word from first buffer. */
569170754Sdelphij  word const *l0, *l1;	/* Pointers into each buffer. */
570170754Sdelphij  char const *c0, *c1;	/* Pointers for finding exact address. */
571170754Sdelphij  size_t cnt = 0;	/* Number of '\n' occurrences. */
572170754Sdelphij  word nnnn;		/* Newline, sizeof (word) times.  */
573170754Sdelphij  int i;
574170754Sdelphij
575170754Sdelphij  nnnn = 0;
576170754Sdelphij  for (i = 0; i < sizeof nnnn; i++)
577170754Sdelphij    nnnn = (nnnn << CHAR_BIT) | '\n';
578170754Sdelphij
579170754Sdelphij  /* Find the rough position of the first difference by reading words,
580170754Sdelphij     not bytes.  */
581170754Sdelphij
582170754Sdelphij  for (l0 = p0, l1 = p1;  (l = *l0) == *l1;  l0++, l1++)
583170754Sdelphij    {
584170754Sdelphij      l ^= nnnn;
585170754Sdelphij      for (i = 0; i < sizeof l; i++)
586170754Sdelphij	{
587170754Sdelphij	  unsigned char uc = l;
588170754Sdelphij	  cnt += ! uc;
589170754Sdelphij	  l >>= CHAR_BIT;
590170754Sdelphij	}
591170754Sdelphij    }
592170754Sdelphij
593170754Sdelphij  /* Find the exact differing position (endianness independent).  */
594170754Sdelphij
595170754Sdelphij  for (c0 = (char const *) l0, c1 = (char const *) l1;
596170754Sdelphij       *c0 == *c1;
597170754Sdelphij       c0++, c1++)
598170754Sdelphij    cnt += *c0 == '\n';
599170754Sdelphij
600170754Sdelphij  *count += cnt;
601170754Sdelphij  return c0 - (char const *) p0;
602170754Sdelphij}
603170754Sdelphij
604170754Sdelphij/* Compare two blocks of memory P0 and P1 until they differ.
605170754Sdelphij   If the blocks are not guaranteed to be different, put sentinels at the ends
606170754Sdelphij   of the blocks before calling this function.
607170754Sdelphij
608170754Sdelphij   Return the offset of the first byte that differs.  */
609170754Sdelphij
610170754Sdelphijstatic size_t
611170754Sdelphijblock_compare (word const *p0, word const *p1)
612170754Sdelphij{
613170754Sdelphij  word const *l0, *l1;
614170754Sdelphij  char const *c0, *c1;
615170754Sdelphij
616170754Sdelphij  /* Find the rough position of the first difference by reading words,
617170754Sdelphij     not bytes.  */
618170754Sdelphij
619170754Sdelphij  for (l0 = p0, l1 = p1;  *l0 == *l1;  l0++, l1++)
620170754Sdelphij    continue;
621170754Sdelphij
622170754Sdelphij  /* Find the exact differing position (endianness independent).  */
623170754Sdelphij
624170754Sdelphij  for (c0 = (char const *) l0, c1 = (char const *) l1;
625170754Sdelphij       *c0 == *c1;
626170754Sdelphij       c0++, c1++)
627170754Sdelphij    continue;
628170754Sdelphij
629170754Sdelphij  return c0 - (char const *) p0;
630170754Sdelphij}
631170754Sdelphij
632170754Sdelphij/* Put into BUF the unsigned char C, making unprintable bytes
633170754Sdelphij   visible by quoting like cat -t does.  */
634170754Sdelphij
635170754Sdelphijstatic void
636170754Sdelphijsprintc (char *buf, unsigned char c)
637170754Sdelphij{
638170754Sdelphij  if (! isprint (c))
639170754Sdelphij    {
640170754Sdelphij      if (c >= 128)
641170754Sdelphij	{
642170754Sdelphij	  *buf++ = 'M';
643170754Sdelphij	  *buf++ = '-';
644170754Sdelphij	  c -= 128;
645170754Sdelphij	}
646170754Sdelphij      if (c < 32)
647170754Sdelphij	{
648170754Sdelphij	  *buf++ = '^';
649170754Sdelphij	  c += 64;
650170754Sdelphij	}
651170754Sdelphij      else if (c == 127)
652170754Sdelphij	{
653170754Sdelphij	  *buf++ = '^';
654170754Sdelphij	  c = '?';
655170754Sdelphij	}
656170754Sdelphij    }
657170754Sdelphij
658170754Sdelphij  *buf++ = c;
659170754Sdelphij  *buf = 0;
660170754Sdelphij}
661170754Sdelphij
662170754Sdelphij/* Position file F to ignore_initial[F] bytes from its initial position,
663170754Sdelphij   and yield its new position.  Don't try more than once.  */
664170754Sdelphij
665170754Sdelphijstatic off_t
666170754Sdelphijfile_position (int f)
667170754Sdelphij{
668170754Sdelphij  static bool positioned[2];
669170754Sdelphij  static off_t position[2];
670170754Sdelphij
671170754Sdelphij  if (! positioned[f])
672170754Sdelphij    {
673170754Sdelphij      positioned[f] = true;
674170754Sdelphij      position[f] = lseek (file_desc[f], ignore_initial[f], SEEK_CUR);
675170754Sdelphij    }
676170754Sdelphij  return position[f];
677170754Sdelphij}
678