#include "system.h"
#include <stdio.h>
#include "getopt.h"
#include "cmpbuf.h"
extern char const version_string[];
#if __STDC__ && defined (HAVE_VPRINTF)
void error (int, int, char const *, ...);
#else
void error ();
#endif
VOID *xmalloc PARAMS((size_t));
static int cmp PARAMS((void));
static off_t file_position PARAMS((int));
static size_t block_compare PARAMS((char const *, char const *));
static size_t block_compare_and_count PARAMS((char const *, char const *, long *));
static size_t block_read PARAMS((int, char *, size_t));
static void printc PARAMS((int, unsigned));
static void try_help PARAMS((char const *));
static void check_stdout PARAMS((void));
static void usage PARAMS((void));
char const *program_name;
static char const *file[2];
static int file_desc[2];
static char *buffer[2];
static size_t buf_size;
static off_t ignore_initial;
static enum
{
type_first_diff, type_all_diffs, type_status
} comparison_type;
#ifndef word
#define word int
#endif
static int opt_print_chars;
static struct option const long_options[] =
{
{"print-chars", 0, 0, 'c'},
{"ignore-initial", 1, 0, 'i'},
{"verbose", 0, 0, 'l'},
{"silent", 0, 0, 's'},
{"quiet", 0, 0, 's'},
{"version", 0, 0, 'v'},
{"help", 0, 0, 129},
{0, 0, 0, 0}
};
static void
try_help (reason)
char const *reason;
{
if (reason)
error (0, 0, "%s", reason);
error (2, 0, "Try `%s --help' for more information.", program_name);
}
static void
check_stdout ()
{
if (ferror (stdout))
error (2, 0, "write error");
else if (fclose (stdout) != 0)
error (2, errno, "write error");
}
static void
usage ()
{
printf ("Usage: %s [OPTION]... FILE1 [FILE2]\n", program_name);
printf ("%s", "\
-c --print-chars Output differing bytes as characters.\n\
-i N --ignore-initial=N Ignore differences in the first N bytes of input.\n\
-l --verbose Output offsets and codes of all differing bytes.\n\
-s --quiet --silent Output nothing; yield exit status only.\n\
-v --version Output version info.\n\
--help Output this help.\n");
printf ("If a FILE is `-' or missing, read standard input.\n");
}
int
main (argc, argv)
int argc;
char *argv[];
{
int c, i, exit_status;
struct stat stat_buf[2];
initialize_main (&argc, &argv);
program_name = argv[0];
while ((c = getopt_long (argc, argv, "ci:lsv", long_options, 0))
!= EOF)
switch (c)
{
case 'c':
opt_print_chars = 1;
break;
case 'i':
ignore_initial = 0;
while (*optarg)
{
unsigned digit = *optarg++ - '0';
if (9 < digit)
try_help ("non-digit in --ignore-initial value");
ignore_initial = 10 * ignore_initial + digit;
}
break;
case 'l':
comparison_type = type_all_diffs;
break;
case 's':
comparison_type = type_status;
break;
case 'v':
printf ("cmp - GNU diffutils version %s\n", version_string);
exit (0);
case 129:
usage ();
check_stdout ();
exit (0);
default:
try_help (0);
}
if (optind == argc)
try_help ("missing operand");
file[0] = argv[optind++];
file[1] = optind < argc ? argv[optind++] : "-";
if (optind < argc)
try_help ("extra operands");
for (i = 0; i < 2; i++)
{
int i1 = i ^ (strcmp (file[1], "-") == 0);
if (i && filename_cmp (file[0], file[1]) == 0)
exit (0);
file_desc[i1] = (strcmp (file[i1], "-") == 0
? STDIN_FILENO
: open (file[i1], O_RDONLY, 0));
if (file_desc[i1] < 0 || fstat (file_desc[i1], &stat_buf[i1]) != 0)
{
if (file_desc[i1] < 0 && comparison_type == type_status)
exit (2);
else
error (2, errno, "%s", file[i1]);
}
#if HAVE_SETMODE
setmode (file_desc[i1], O_BINARY);
#endif
}
if (0 < same_file (&stat_buf[0], &stat_buf[1])
&& file_position (0) == file_position (1))
exit (0);
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 (max (0, s0) != max (0, s1))
exit (1);
}
buf_size = buffer_lcm (STAT_BLOCKSIZE (stat_buf[0]),
STAT_BLOCKSIZE (stat_buf[1]));
for (i = 0; i < 2; i++)
buffer[i] = xmalloc (buf_size + sizeof (word));
exit_status = cmp ();
for (i = 0; i < 2; i++)
if (close (file_desc[i]) != 0)
error (2, errno, "%s", file[i]);
if (exit_status != 0 && comparison_type != type_status)
check_stdout ();
exit (exit_status);
return exit_status;
}
static int
cmp ()
{
long line_number = 1;
long char_number = ignore_initial + 1;
size_t read0, read1;
size_t first_diff;
size_t smaller;
char *buf0 = buffer[0];
char *buf1 = buffer[1];
int ret = 0;
int i;
if (ignore_initial)
for (i = 0; i < 2; i++)
if (file_position (i) == -1)
{
off_t ig = ignore_initial;
do
{
size_t r = read (file_desc[i], buf0, (size_t) min (ig, buf_size));
if (!r)
break;
if (r == -1)
error (2, errno, "%s", file[i]);
ig -= r;
}
while (ig);
}
do
{
read0 = block_read (file_desc[0], buf0, buf_size);
if (read0 == -1)
error (2, errno, "%s", file[0]);
read1 = block_read (file_desc[1], buf1, buf_size);
if (read1 == -1)
error (2, errno, "%s", file[1]);
buf0[read0] = ~buf1[read0];
buf1[read1] = ~buf0[read1];
first_diff = (comparison_type == type_first_diff
? block_compare_and_count (buf0, buf1, &line_number)
: block_compare (buf0, buf1));
char_number += first_diff;
smaller = min (read0, read1);
if (first_diff < smaller)
{
switch (comparison_type)
{
case type_first_diff:
printf ("%s %s differ: char %lu, line %lu",
file[0], file[1], char_number, line_number);
if (opt_print_chars)
{
unsigned char c0 = buf0[first_diff];
unsigned char c1 = buf1[first_diff];
printf (" is %3o ", c0);
printc (0, c0);
printf (" %3o ", c1);
printc (0, c1);
}
putchar ('\n');
case type_status:
return 1;
case type_all_diffs:
do
{
unsigned char c0 = buf0[first_diff];
unsigned char c1 = buf1[first_diff];
if (c0 != c1)
{
if (opt_print_chars)
{
printf ("%6lu %3o ", char_number, c0);
printc (4, c0);
printf (" %3o ", c1);
printc (0, c1);
putchar ('\n');
}
else
printf ("%6lu %3o %3o\n", char_number, c0, c1);
}
char_number++;
first_diff++;
}
while (first_diff < smaller);
ret = 1;
break;
}
}
if (read0 != read1)
{
if (comparison_type != type_status)
fprintf (stderr, "cmp: EOF on %s\n", file[read1 < read0]);
return 1;
}
}
while (read0 == buf_size);
return ret;
}
static size_t
block_compare_and_count (p0, p1, count)
char const *p0, *p1;
long *count;
{
word l;
word const *l0, *l1;
char const *c0, *c1;
long cnt = 0;
word nnnn;
int i;
l0 = (word const *) p0;
l1 = (word const *) p1;
nnnn = 0;
for (i = 0; i < sizeof (word); i++)
nnnn = (nnnn << CHAR_BIT) | '\n';
while ((l = *l0++) == *l1++)
{
l ^= nnnn;
for (i = 0; i < sizeof (word); i++)
{
cnt += ! (unsigned char) l;
l >>= CHAR_BIT;
}
}
c0 = (char const *) (l0 - 1);
c1 = (char const *) (l1 - 1);
while (*c0 == *c1)
{
cnt += *c0 == '\n';
c0++;
c1++;
}
*count += cnt;
return c0 - p0;
}
static size_t
block_compare (p0, p1)
char const *p0, *p1;
{
word const *l0, *l1;
char const *c0, *c1;
l0 = (word const *) p0;
l1 = (word const *) p1;
while (*l0++ == *l1++)
;
c0 = (char const *) (l0 - 1);
c1 = (char const *) (l1 - 1);
while (*c0 == *c1)
{
c0++;
c1++;
}
return c0 - p0;
}
static size_t
block_read (fd, buf, nchars)
int fd;
char *buf;
size_t nchars;
{
char *bp = buf;
do
{
size_t nread = read (fd, bp, nchars);
if (nread == -1)
return -1;
if (nread == 0)
break;
bp += nread;
nchars -= nread;
}
while (nchars != 0);
return bp - buf;
}
static void
printc (width, c)
int width;
unsigned c;
{
register FILE *fs = stdout;
if (! ISPRINT (c))
{
if (c >= 128)
{
putc ('M', fs);
putc ('-', fs);
c -= 128;
width -= 2;
}
if (c < 32)
{
putc ('^', fs);
c += 64;
--width;
}
else if (c == 127)
{
putc ('^', fs);
c = '?';
--width;
}
}
putc (c, fs);
while (--width > 0)
putc (' ', fs);
}
static off_t
file_position (i)
int i;
{
static int positioned[2];
static off_t position[2];
if (! positioned[i])
{
positioned[i] = 1;
position[i] = lseek (file_desc[i], ignore_initial, SEEK_CUR);
}
return position[i];
}