
// Project: webCDwriter - Network CD Writing
//
// Compare a directory or an image file to the contents of a CD
//
// Author: Jörg Haeger, 28.02.2001

#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>
#include "Error.h"
#include "Version.h"

typedef unsigned short uint16;
typedef unsigned long uint32;
typedef unsigned long long uint64;

class Verify
{
	char *buf1, *buf2;
	uint64 bytesCounter, bytesMax;
	int checkTooLong;
	int MBytes0;
	int newLine;
	int numOfFiles, numOfErrors;

public:
	Verify()
	{
		bytesMax = 1024 * 1024;
		buf1 = new char[bytesMax];
		buf2 = new char[bytesMax];
		bytesCounter = 0;
		checkTooLong = 1;
		MBytes0 = 0;
		newLine = 1;
		numOfFiles = numOfErrors = 0;
	}

	~Verify()
	{
		delete[] buf1;
		delete[] buf2;
	}

	void compare(const char *orig, const char *copy);
	void compareFile(const char *orig, const char *copy, uint64 size = 0);
	void compareTracks(const char *orig, const char *copy);

	int getNumOfErrors() { return numOfErrors; }
	int getNumOfFiles() { return numOfFiles; }
	static uint64 readISOsize(const char *imageFile);
};

void Verify::compare(const char *orig, const char *copy)
{
	struct stat statBuf;
	if (stat(orig, &statBuf) != 0)
		return;

	if (!S_ISDIR(statBuf.st_mode))
	{
		compareFile(orig, copy);
		return;
	}

	DIR *dir = opendir(orig);
	if (dir == NULL)
		return;

	struct dirent *dirEntry;
	while ((dirEntry = readdir(dir)) != NULL)
	{
		if (strcmp(dirEntry->d_name, ".") == 0
			|| strcmp(dirEntry->d_name, "..") == 0)
			continue;

		char tmpOrig[strlen(orig) + 1
			+ strlen(dirEntry->d_name) + 1];
		sprintf(tmpOrig, "%s/%s",
			orig, dirEntry->d_name);

		char tmpCopy[strlen(copy) + 1
			+ strlen(dirEntry->d_name) + 1];
		sprintf(tmpCopy, "%s/%s",
			copy, dirEntry->d_name);

		compare(tmpOrig, tmpCopy);
	}

	closedir(dir);
}

void Verify::compareFile(const char *orig, const char *copy, uint64 size)
{
	FILE *in1 = NULL, *in2 = NULL;

	try {
		in1 = fopen(orig, "r");
		if (in1 == NULL)
			throw new Error("Cannot open %s", orig);

		in2 = fopen(copy, "r");
		if (in2 == NULL)
			throw new Error("Cannot open %s", copy);

		uint64 perFileBytesCounter = 0;
		for (int i = 0; ; )
		{
			int MBytes = bytesCounter / 1024 / 1024;
			if (MBytes > MBytes0)
			{
				if (!newLine)
					printf("\r");
				printf("%d MB", MBytes);
				fflush(stdout);
				MBytes0 = MBytes;
				newLine = 0;
			}

			int nMax = bytesMax;
			if (size > 0
				&& bytesCounter + nMax > size)
				nMax = size - bytesCounter;
			int n = fread(buf1, 1, nMax, in1);
			if (n == 0)
				if (checkTooLong
					&& fread(buf2, 1, 1, in2) > 0)
					throw new Error("too long");
				else
					break;

			int m = fread(buf2, 1, n, in2);

			if (memcmp(buf1, buf2, m) != 0)
			{
				int i = 0;
				while (buf1[i] == buf2[i])
					i++;
				throw new Error("byte %d differs",
					perFileBytesCounter + i + 1);
			}

			if (m < n)
				throw new Error("too short");

			bytesCounter += n;
			perFileBytesCounter += n;
		}
	}
	catch (Error *err)
	{
		if (!newLine)
			printf("\n");
		printf("error: %s: %s\n", copy, err->toString());
		delete err;
		numOfErrors++;
	}
	numOfFiles++;

	if (in2 != NULL)
		fclose(in2);
	if (in1 != NULL)
		fclose(in1);
}

void Verify::compareTracks(const char *orig, const char *copy)
{
	checkTooLong = 0;
	compareFile(orig, copy, readISOsize(orig));
}

uint64 Verify::readISOsize(const char *imageFile)
{
	uint64 size = 0;
	FILE *in = fopen(imageFile, "r");
	if (in != NULL)
	{
		unsigned char buf[2048];
		for (int i = 0; i < 32; i++)
		{
			int n = fread(buf, 1, sizeof buf, in);
			if (n < sizeof buf || buf[0] == 255)
				break;
			if (i < 16)
				continue;
			if (buf[0] == 1)
			{
				size = *(uint32 *)&buf[80];
				size *= *(uint16 *)&buf[128];
				printf("ISO image size = %lu\n", size);
			}
		}
		fclose(in);
	}
	return size;
}

int main(int argc, char *args[])
{
	printf("\nCDWverify %s (part of %s)\n", version, project);
	fflush(stdout);

	if (argc != 3)
	{
		printf("%s compares a directory "
			"or an image file to the contents of a CD\n\n"
			"Usage:\n"
			"\t\t%s directory rawDevice\n"
			"\t\t%s imageFile rawDevice\n\n",
			args[0], args[0], args[0]);
		return 1;
	}

	struct stat statBuf;
	if (stat(args[1], &statBuf) != 0)
	{
		printf("Cannot stat %s\n", args[1]);
		return 1;
	}

	Verify verify;
	if (S_ISDIR(statBuf.st_mode))
	{
		char dir[1024];
		sprintf(dir, "/tmp/cdrom%d", getpid());
		mkdir(dir, S_IRWXU);
		setuid(0);
		char cmdStr[1024];
		sprintf(cmdStr, "mount -r -t iso9660 %s %s 2> /dev/null",
			args[2], dir);
		if (system(cmdStr) != 0)
		{
			printf("Cannot mount the CD\n");
			return 1;
		}
		verify.compare(args[1], dir);
		sprintf(cmdStr, "umount %s", dir);
		system(cmdStr);
		printf("\n%d files are identical, %d files differ\n",
			verify.getNumOfFiles() - verify.getNumOfErrors(),
			verify.getNumOfErrors());
		if (verify.getNumOfErrors() == 0)
			return 0;
		else
			return 1;
	}
	else
	{
		verify.compareTracks(args[1], args[2]);
		printf("\n");
		if (verify.getNumOfErrors() == 0)
		{
			printf("OK\n");
			return 0;
		}
		else
			return 1;
	}
}
