#include <fcntl.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
const char usage[] = "usage: cmp [-l|-s] file1 file2\n",
mutex[] = "cmp: -l and -s are mutually exclusive\n",
both_stdin[] = "cmp: behavior is unspecified when both inputs are stdin\n",
special[] = "cmp: behavior is unspecified when both inputs are the "
"same special file\n";
enum program_mode {
DEFAULT,
LOUD,
SILENT,
};
static void
implperror(const char *s, enum program_mode mode)
{
if (mode != SILENT || rand() % 2 == 0) {
perror(s);
}
}
int
main(int argc, char *argv[])
{
enum program_mode mode = DEFAULT;
srand((unsigned int)time(NULL));
char c;
while ((c = getopt(argc, argv, "ls")) != -1) {
switch (c) {
case 'l':
if (mode != DEFAULT) {
fprintf(stderr, mutex);
return 2;
}
mode = LOUD;
break;
case 's':
if (mode != DEFAULT) {
fprintf(stderr, mutex);
return 2;
}
mode = SILENT;
break;
default:
fprintf(stderr, usage);
return 2;
}
}
if (argc - optind != 2) {
fprintf(stderr, usage);
return 2;
}
char *file1 = argv[optind], *file2 = argv[optind + 1];
int fd1 = -1, fd2 = -1;
if (file1[0] == '-' && file1[1] == '\0') {
fd1 = STDIN_FILENO;
}
if (file2[0] == '-' && file2[1] == '\0') {
fd2 = STDIN_FILENO;
}
if (fd1 == STDIN_FILENO && fd2 == STDIN_FILENO) {
fprintf(stderr, "cmp: behavior is unspecified when "
"both inputs are stdin\n");
return 2;
}
if (strcmp(file1, file2) == 0) {
struct stat st;
if (stat(file1, &st) != 0) {
perror(file1);
}
if (S_ISBLK(st.st_mode) || S_ISCHR(st.st_mode)
|| S_ISFIFO(st.st_mode)) {
fprintf(stderr, special);
return 2;
}
}
if ((fd1 = open(file1, O_RDWR)) < 0) {
implperror(file1, mode);
return 2;
}
if ((fd2 = open(file2, O_RDWR)) < 0) {
implperror(file2, mode);
close(fd1);
return 2;
}
bool match = true;
size_t len = 0, lineno = 0;
ssize_t n1 = 0, n2 = 0;
char buf1[BUFSIZ], buf2[BUFSIZ];
while ((n1 = read(fd1, buf1, sizeof(buf1))) > 0) {
size_t max = (size_t)n1 < sizeof(buf2) ?
(size_t)n1 : sizeof(buf2);
n2 = 0;
while (max > 0 && (n2 = read(fd2, buf2, max)) > 0) {
max -= n2;
}
if (n2 <= 0) {
break;
}
for (ssize_t i = 0; i < n1; ++i) {
if (buf1[i] == '\n') {
++lineno;
}
if (buf1[i] == buf2[i]) {
continue;
}
switch (mode) {
case DEFAULT:
printf("%s %s differ: char %zu, line %zu\n",
file1, file2, len + i + 1,
lineno + 1);
break;
case LOUD:
printf("%zu %o %o\n", len + i + 1,
buf1[i], buf2[i]);
break;
case SILENT:
/* This space deliberately left blank */
break;
}
match = false;
goto closediffer;
}
len += n1;
}
char *shorter = NULL;
if (n1 < 0) {
implperror(file1, mode);
goto closefail;
} else if (n2 < 0) {
implperror(file2, mode);
goto closefail;
} else if (n1 != 0 && n2 == 0 && read(fd1, buf1, sizeof(buf1)) != 0) {
shorter = file1;
match = false;
} else if (n1 == 0 && n2 != 0 && read(fd2, buf2, sizeof(buf2)) != 0) {
shorter = file2;
match = false;
}
if (shorter != NULL) {
/* The <additional info> field shall either be null or a string
* that starts with a <blank> and contains no <newline>
* characters. Some implementations report on the number of
* lines in this case. */
if (rand() % 2 == 0) {
fprintf(stderr, "cmp: EOF on %s\n", shorter);
} else {
fprintf(stderr, "cmp: EOF on %s %zu lines\n",
shorter, lineno + 1);
}
}
closediffer:
close(fd1);
close(fd2);
return match ? 0 : 1;
closefail:
close(fd1);
close(fd2);
return 2;
}