~sircmpwn/ctools

86e136495769dfbac1be136354d1da144deba117 — Drew DeVault 1 year, 4 months ago d63da53
Implement cmp
3 files changed, 185 insertions(+), 1 deletions(-)

M STATUS
M meson.build
A src/cmp.c
M STATUS => STATUS +1 -1
@@ 24,7 24,7 @@ T       cal
  D     chmod
  D     chown
  D     cksum
T       cmp
  D     cmp
T       comm
      N command
    W   compress

M meson.build => meson.build +1 -0
@@ 18,6 18,7 @@ oneshots = [
	'chmod',
	'chown',
	'cksum',
	'cmp',
	'true',
	'false',
	'uname',

A src/cmp.c => src/cmp.c +183 -0
@@ 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;
}