#include "system.h"
#include <stdio.h>
#include <cmpbuf.h>
#include <c-stack.h>
#include <error.h>
#include <exitfail.h>
#include <freesoft.h>
#include <getopt.h>
#include <hard-locale.h>
#include <inttostr.h>
#include <setmode.h>
#include <xalloc.h>
#include <xstrtol.h>
#if defined LC_MESSAGES && ENABLE_NLS
# define hard_locale_LC_MESSAGES hard_locale (LC_MESSAGES)
#else
# define hard_locale_LC_MESSAGES 0
#endif
#ifdef __APPLE__
#include "get_compat.h"
#endif
static char const authorship_msgid[] =
N_("Written by Torbjorn Granlund and David MacKenzie.");
static char const copyright_string[] =
"Copyright (C) 2002 Free Software Foundation, Inc.";
extern char const version_string[];
static int cmp (void);
static off_t file_position (int);
static size_t block_compare (word const *, word const *);
static size_t block_compare_and_count (word const *, word const *, off_t *);
static void sprintc (char *, unsigned char);
char *program_name;
static char const *file[2];
static int file_desc[2];
static struct stat stat_buf[2];
static word *buffer[2];
static size_t buf_size;
static off_t ignore_initial[2];
static uintmax_t bytes = UINTMAX_MAX;
static enum comparison_type
{
type_first_diff,
type_all_diffs,
type_status
} comparison_type;
static bool opt_print_bytes;
enum
{
HELP_OPTION = CHAR_MAX + 1
};
static struct option const long_options[] =
{
{"print-bytes", 0, 0, 'b'},
{"print-chars", 0, 0, 'c'},
{"ignore-initial", 1, 0, 'i'},
{"verbose", 0, 0, 'l'},
{"bytes", 1, 0, 'n'},
{"silent", 0, 0, 's'},
{"quiet", 0, 0, 's'},
{"version", 0, 0, 'v'},
{"help", 0, 0, HELP_OPTION},
{0, 0, 0, 0}
};
static void try_help (char const *, char const *) __attribute__((noreturn));
static void
try_help (char const *reason_msgid, char const *operand)
{
if (reason_msgid)
error (0, 0, _(reason_msgid), operand);
error (EXIT_TROUBLE, 0,
_("Try `%s --help' for more information."), program_name);
abort ();
}
static char const valid_suffixes[] = "kKMGTPEZY0";
static off_t
parse_ignore_initial (char **argptr, char delimiter)
{
uintmax_t val;
off_t o;
char const *arg = *argptr;
strtol_error e = xstrtoumax (arg, argptr, 0, &val, valid_suffixes);
if (! (e == LONGINT_OK
|| (e == LONGINT_INVALID_SUFFIX_CHAR && **argptr == delimiter))
|| (o = val) < 0 || o != val || val == UINTMAX_MAX)
try_help ("invalid --ignore-initial value `%s'", arg);
return o;
}
static void
specify_comparison_type (enum comparison_type t)
{
if (comparison_type)
try_help ("options -l and -s are incompatible", 0);
comparison_type = t;
}
static void
check_stdout (void)
{
if (ferror (stdout))
error (EXIT_TROUBLE, 0, "%s", _("write failed"));
else if (fclose (stdout) != 0)
error (EXIT_TROUBLE, errno, "%s", _("standard output"));
}
static char const * const option_help_msgid[] = {
N_("-b --print-bytes Print differing bytes."),
N_("-i SKIP --ignore-initial=SKIP Skip the first SKIP bytes of input."),
N_("-i SKIP1:SKIP2 --ignore-initial=SKIP1:SKIP2"),
N_(" Skip the first SKIP1 bytes of FILE1 and the first SKIP2 bytes of FILE2."),
N_("-l --verbose Output byte numbers and values of all differing bytes."),
N_("-n LIMIT --bytes=LIMIT Compare at most LIMIT bytes."),
N_("-s --quiet --silent Output nothing; yield exit status only."),
N_("-v --version Output version info."),
N_("--help Output this help."),
0
};
static void
usage (void)
{
char const * const *p;
printf (_("Usage: %s [OPTION]... FILE1 [FILE2 [SKIP1 [SKIP2]]]\n"),
program_name);
printf ("%s\n\n", _("Compare two files byte by byte."));
for (p = option_help_msgid; *p; p++)
printf (" %s\n", _(*p));
printf ("\n%s\n%s\n\n%s\n\n%s\n",
_("SKIP1 and SKIP2 are the number of bytes to skip in each file."),
_("SKIP values may be followed by the following multiplicative suffixes:\n\
kB 1000, K 1024, MB 1,000,000, M 1,048,576,\n\
GB 1,000,000,000, G 1,073,741,824, and so on for T, P, E, Z, Y."),
_("If a FILE is `-' or missing, read standard input."),
_("Report bugs to <bug-gnu-utils@gnu.org>."));
}
int
main (int argc, char **argv)
{
int c, f, exit_status;
size_t words_per_buffer;
exit_failure = EXIT_TROUBLE;
initialize_main (&argc, &argv);
program_name = argv[0];
setlocale (LC_ALL, "");
bindtextdomain (PACKAGE, LOCALEDIR);
textdomain (PACKAGE);
c_stack_action (c_stack_die);
while ((c = getopt_long (argc, argv, "bci:ln:sv", long_options, 0))
!= -1)
switch (c)
{
case 'b':
case 'c':
opt_print_bytes = 1;
break;
case 'i':
ignore_initial[0] = parse_ignore_initial (&optarg, ':');
ignore_initial[1] = (*optarg++ == ':'
? parse_ignore_initial (&optarg, 0)
: ignore_initial[0]);
break;
case 'l':
specify_comparison_type (type_all_diffs);
break;
case 'n':
{
uintmax_t n;
if (xstrtoumax (optarg, 0, 0, &n, valid_suffixes) != LONGINT_OK)
try_help ("invalid --bytes value `%s'", optarg);
if (n < bytes)
bytes = n;
}
break;
case 's':
specify_comparison_type (type_status);
break;
case 'v':
printf ("cmp %s\n%s\n\n%s\n\n%s\n",
version_string, copyright_string,
_(free_software_msgid), _(authorship_msgid));
check_stdout ();
return EXIT_SUCCESS;
case HELP_OPTION:
usage ();
check_stdout ();
return EXIT_SUCCESS;
default:
try_help (0, 0);
}
if (optind == argc)
try_help ("missing operand after `%s'", argv[argc - 1]);
file[0] = argv[optind++];
file[1] = optind < argc ? argv[optind++] : "-";
for (f = 0; f < 2 && optind < argc; f++)
{
char *arg = argv[optind++];
ignore_initial[f] = parse_ignore_initial (&arg, 0);
}
if (optind < argc)
try_help ("extra operand `%s'", argv[optind]);
for (f = 0; f < 2; f++)
{
int f1 = f ^ (strcmp (file[1], "-") == 0);
if (f && file_name_cmp (file[0], file[1]) == 0)
return EXIT_SUCCESS;
file_desc[f1] = (strcmp (file[f1], "-") == 0
? STDIN_FILENO
: open (file[f1], O_RDONLY, 0));
if (file_desc[f1] < 0 || fstat (file_desc[f1], stat_buf + f1) != 0)
{
if (file_desc[f1] < 0 && comparison_type == type_status)
exit (EXIT_TROUBLE);
else
error (EXIT_TROUBLE, errno, "%s", file[f1]);
}
set_binary_mode (file_desc[f1], 1);
}
#ifdef __APPLE__
if(!COMPAT_MODE("bin/cmp", "unix2003"))
#endif
if (0 < same_file (&stat_buf[0], &stat_buf[1])
&& same_file_attributes (&stat_buf[0], &stat_buf[1])
&& file_position (0) == file_position (1))
return EXIT_SUCCESS;
if (comparison_type != type_status)
{
struct stat outstat, nullstat;
if (fstat (STDOUT_FILENO, &outstat) == 0
&& stat (NULL_DEVICE, &nullstat) == 0
&& 0 < same_file (&outstat, &nullstat))
comparison_type = type_status;
}
if (comparison_type == type_status
&& S_ISREG (stat_buf[0].st_mode)
&& S_ISREG (stat_buf[1].st_mode))
{
off_t s0 = stat_buf[0].st_size - file_position (0);
off_t s1 = stat_buf[1].st_size - file_position (1);
if (s0 < 0)
s0 = 0;
if (s1 < 0)
s1 = 0;
if (s0 != s1 && MIN (s0, s1) < bytes)
exit (EXIT_FAILURE);
}
buf_size = buffer_lcm (STAT_BLOCKSIZE (stat_buf[0]),
STAT_BLOCKSIZE (stat_buf[1]),
PTRDIFF_MAX - sizeof (word));
words_per_buffer = (buf_size + 2 * sizeof (word) - 1) / sizeof (word);
buffer[0] = xmalloc (2 * sizeof (word) * words_per_buffer);
buffer[1] = buffer[0] + words_per_buffer;
exit_status = cmp ();
for (f = 0; f < 2; f++)
if (close (file_desc[f]) != 0)
error (EXIT_TROUBLE, errno, "%s", file[f]);
if (exit_status != 0 && comparison_type != type_status)
check_stdout ();
exit (exit_status);
return exit_status;
}
static int
cmp (void)
{
off_t line_number = 1;
off_t byte_number = 1;
uintmax_t remaining = bytes;
size_t read0, read1;
size_t first_diff;
size_t smaller;
word *buffer0 = buffer[0];
word *buffer1 = buffer[1];
char *buf0 = (char *) buffer0;
char *buf1 = (char *) buffer1;
int ret = EXIT_SUCCESS;
int f;
int offset_width;
if (comparison_type == type_all_diffs)
{
off_t byte_number_max = MIN (bytes, TYPE_MAXIMUM (off_t));
for (f = 0; f < 2; f++)
if (S_ISREG (stat_buf[f].st_mode))
{
off_t file_bytes = stat_buf[f].st_size - file_position (f);
if (file_bytes < byte_number_max)
byte_number_max = file_bytes;
}
for (offset_width = 1; (byte_number_max /= 10) != 0; offset_width++)
continue;
}
for (f = 0; f < 2; f++)
{
off_t ig = ignore_initial[f];
if (ig && file_position (f) == -1)
{
do
{
size_t bytes_to_read = MIN (ig, buf_size);
size_t r = block_read (file_desc[f], buf0, bytes_to_read);
if (r != bytes_to_read)
{
if (r == SIZE_MAX)
error (EXIT_TROUBLE, errno, "%s", file[f]);
break;
}
ig -= r;
}
while (ig);
}
}
do
{
size_t bytes_to_read = buf_size;
if (remaining != UINTMAX_MAX)
{
if (remaining < bytes_to_read)
bytes_to_read = remaining;
remaining -= bytes_to_read;
}
read0 = block_read (file_desc[0], buf0, bytes_to_read);
if (read0 == SIZE_MAX)
error (EXIT_TROUBLE, errno, "%s", file[0]);
read1 = block_read (file_desc[1], buf1, bytes_to_read);
if (read1 == SIZE_MAX)
error (EXIT_TROUBLE, errno, "%s", file[1]);
buf0[read0] = ~buf1[read0];
buf1[read1] = ~buf0[read1];
first_diff = (comparison_type == type_first_diff
? block_compare_and_count (buffer0, buffer1, &line_number)
: block_compare (buffer0, buffer1));
byte_number += first_diff;
smaller = MIN (read0, read1);
if (first_diff < smaller)
{
switch (comparison_type)
{
case type_first_diff:
{
char byte_buf[INT_BUFSIZE_BOUND (off_t)];
char line_buf[INT_BUFSIZE_BOUND (off_t)];
char const *byte_num = offtostr (byte_number, byte_buf);
char const *line_num = offtostr (line_number, line_buf);
if (!opt_print_bytes)
{
static char const char_message[] =
"%s %s differ: char %s, line %s\n";
static char const byte_msgid[] =
N_("%s %s differ: byte %s, line %s\n");
char const *byte_message = _(byte_msgid);
bool use_byte_message = (byte_message != byte_msgid
|| hard_locale_LC_MESSAGES);
printf ((use_byte_message
? byte_message
: "%s %s differ: char %s, line %s\n"),
file[0], file[1], byte_num, line_num);
}
else
{
unsigned char c0 = buf0[first_diff];
unsigned char c1 = buf1[first_diff];
char s0[5];
char s1[5];
sprintc (s0, c0);
sprintc (s1, c1);
printf (_("%s %s differ: byte %s, line %s is %3o %s %3o %s\n"),
file[0], file[1], byte_num, line_num,
c0, s0, c1, s1);
}
}
case type_status:
return EXIT_FAILURE;
case type_all_diffs:
do
{
unsigned char c0 = buf0[first_diff];
unsigned char c1 = buf1[first_diff];
if (c0 != c1)
{
char byte_buf[INT_BUFSIZE_BOUND (off_t)];
char const *byte_num = offtostr (byte_number, byte_buf);
if (!opt_print_bytes)
{
printf ("%*s %3o %3o\n",
offset_width, byte_num, c0, c1);
}
else
{
char s0[5];
char s1[5];
sprintc (s0, c0);
sprintc (s1, c1);
printf ("%*s %3o %-4s %3o %s\n",
offset_width, byte_num, c0, s0, c1, s1);
}
}
byte_number++;
first_diff++;
}
while (first_diff < smaller);
ret = EXIT_FAILURE;
break;
}
}
if (read0 != read1)
{
if (comparison_type != type_status)
{
fprintf (stderr, _("cmp: EOF on %s\n"), file[read1 < read0]);
}
return EXIT_FAILURE;
}
}
while (read0 == buf_size);
return ret;
}
static size_t
block_compare_and_count (word const *p0, word const *p1, off_t *count)
{
word l;
word const *l0, *l1;
char const *c0, *c1;
size_t cnt = 0;
word nnnn;
int i;
nnnn = 0;
for (i = 0; i < sizeof nnnn; i++)
nnnn = (nnnn << CHAR_BIT) | '\n';
for (l0 = p0, l1 = p1; (l = *l0) == *l1; l0++, l1++)
{
l ^= nnnn;
for (i = 0; i < sizeof l; i++)
{
cnt += ! (unsigned char) l;
l >>= CHAR_BIT;
}
}
for (c0 = (char const *) l0, c1 = (char const *) l1;
*c0 == *c1;
c0++, c1++)
cnt += *c0 == '\n';
*count += cnt;
return c0 - (char const *) p0;
}
static size_t
block_compare (word const *p0, word const *p1)
{
word const *l0, *l1;
char const *c0, *c1;
for (l0 = p0, l1 = p1; *l0 == *l1; l0++, l1++)
continue;
for (c0 = (char const *) l0, c1 = (char const *) l1;
*c0 == *c1;
c0++, c1++)
continue;
return c0 - (char const *) p0;
}
static void
sprintc (char *buf, unsigned char c)
{
if (! ISPRINT (c))
{
if (c >= 128)
{
*buf++ = 'M';
*buf++ = '-';
c -= 128;
}
if (c < 32)
{
*buf++ = '^';
c += 64;
}
else if (c == 127)
{
*buf++ = '^';
c = '?';
}
}
*buf++ = c;
*buf = 0;
}
static off_t
file_position (int f)
{
static bool positioned[2];
static off_t position[2];
if (! positioned[f])
{
positioned[f] = 1;
position[f] = lseek (file_desc[f], ignore_initial[f], SEEK_CUR);
}
return position[f];
}