@@ 18,6 18,7 @@ oneshots = [
'chmod',
'chown',
'cksum',
+ 'cmp',
'true',
'false',
'uname',
@@ 0,0 1,183 @@
+#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;
+}