/*
 * cryptsetup file differ check (rewritten Clemens' fileDiffer in Python)
 *
 * Copyright (C) 2010-2021 Red Hat, Inc. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/mman.h>

struct ffile {
	char *name;
	int fd;
	unsigned char *addr;
	size_t size;
};

enum df { OK , FAIL };

static void print_diff(off_t from, int max,
		       const unsigned char *o,
		       const unsigned char *n)
{
	int i, len = max;

	if (len > 16)
		len = 16;

	printf("OLD:");
	for (i = 0; i < len; i++)
		printf(" %02x", o[from + i]);
	printf("%s\n    ", max != len ? " ..." : "");
	for (i = 0; i < len; i++)
		printf(" %2c", o[from + i] > ' ' ? o[from + i]: '.');
	printf("\nNEW:");
	for (i = 0; i < len; i++)
		printf(" %02x", n[from + i]);
	printf("%s\n    ", max != len ? " ..." : "");
	for (i = 0; i < len; i++)
		printf(" %2c", n[from + i] > ' ' ? n[from + i]: '.');
	printf("\n");
}

/*
 * Xfrom-to (e.g. R10-15)
 * A - change allowed
 * S - change required, semantic
 * R - change required, random
 * F - change forbidden
 */
static enum df check(const char *range, unsigned char *o, unsigned char *n)
{
	char strict;
	unsigned long long from, to;
	enum df ret;

	if (sscanf(range, "%c%llu-%llu", &strict, &from, &to) != 3) {
		printf("Unknown range format %s.\n", range);
		return FAIL;
	}

	switch (toupper(strict)) {
	case 'A':
		ret = OK;
		break;
	case 'S':
		ret = memcmp(&o[from], &n[from], to - from + 1) != 0 ? OK : FAIL;
		break;
	case 'R': /* FIXME - random test */
		ret = memcmp(&o[from], &n[from], to - from + 1) != 0 ? OK : FAIL;
		break;
	case 'F':
		ret = memcmp(&o[from], &n[from], to - from + 1) == 0 ? OK : FAIL;
		break;
	default:
		ret = FAIL;
		break;
	}

	if (ret == FAIL)
		print_diff(from,  to - from + 1, o, n);

	return ret;
}

static int open_mmap(struct ffile *f)
{
	struct stat st;

	f->fd = open(f->name, O_RDONLY);
	if (f->fd == -1 || fstat(f->fd, &st) == -1)
		return 0;

	f->size = st.st_size;
	f->addr = mmap(NULL, f->size, PROT_READ, MAP_PRIVATE, f->fd, 0);

	return (f->addr == MAP_FAILED) ? 0 : 1;
}

static void close_mmap(struct ffile *f)
{
	if (f->addr != MAP_FAILED && !munmap(f->addr, f->size))
		f->addr = MAP_FAILED;

	if (f->fd != -1 && !close(f->fd))
		f->fd = -1;
}

int main(int argc, char *argv[])
{
	int i, r = 1;
	struct ffile file_old = {
		.fd = -1,
		.addr = MAP_FAILED,
	};
	struct ffile file_new = {
		.fd = -1,
		.addr = MAP_FAILED,
	};

	if (argc < 3) {
		printf("Use: differ old_file new_file change_list.\n");
		goto bad;
	}

	file_old.name = argv[1];
	if (!open_mmap(&file_old))
		goto bad;

	file_new.name = argv[2];
	if (!open_mmap(&file_new))
		goto bad;

	for (i = 3; i < argc; i++)
		if (check(argv[i], file_old.addr, file_new.addr) == FAIL) {
			printf ("FAILED for %s\n", argv[i]);
			r = 1;
			goto bad;
		}

	r = 0;
bad:
	close_mmap(&file_new);
	close_mmap(&file_old);

	return r;
}
