/*
 * cowdancer -- a Copy-on-write data-access; No-cow-easy-replacement
 *
 * Copyright 2007-2000 Junichi Uekawa
 * GPL v2 or later.
 */
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "ilist.h"
#include "log.h"

/* Output error message. You need to process the error result. */
void ilist_outofmemory(const char *msg) {
	log_printf(log_error, "%s: %s", ilist_PRGNAME, msg);
}

/* return 1 on error, 0 on success */
int ilistcreate(const char *ilistpath, const char *findcommandline) {
	int i = 0;
	long dev, ino;
	FILE *inf;
	FILE *outf;
	struct ilist_struct *ilist = NULL;
	struct ilist_header header = {
		ILISTSIG, ILISTREVISION, sizeof(struct ilist_struct), 0};
	long ilist_len = 0;
	if (!findcommandline) {
		findcommandline =
			"find . -xdev \\( -type l -o -type f \\) -a -links +1 -print0 | xargs -0 stat --format '%d %i '";
	}

	if (!(ilist = calloc(2000, sizeof(struct ilist_struct)))) {
		ilist_outofmemory("memory allocation failed");
		return 1;
	}
	ilist_len = 2000;
	if (NULL == (inf = popen(findcommandline, "r"))) {
		ilist_outofmemory("popen find failed");
		free(ilist);
		return 1;
	}

	while (fscanf(inf, "%li %li", &dev, &ino) > 0) {
		(ilist + i)->dev = (dev_t)dev;
		(ilist + i)->inode = (ino_t)ino;

		if (getenv("COWDANCER_DEBUG")) {
			printf("%li %li \n ", (long int)dev, (long int)ino);
		}

		i++;
		if (i >= ilist_len) {
			ilist =
				realloc(ilist, (ilist_len *= 2) * sizeof(struct ilist_struct));
			if (!ilist) {
				ilist_outofmemory("realloc failed");
				pclose(inf);
				free(ilist);
				return 1;
			}
		}
	}
	ilist_len = i;
	if (pclose(inf)) {
		ilist_outofmemory(
			"pclose returned non-zero, probably the directory contains no hardlinked file, don't bother using cow-shell here.");
		free(ilist);
		return 1;
	}

	/* sort the ilist */
	qsort(ilist, ilist_len, sizeof(struct ilist_struct), compare_ilist);

	/* write out the ilist file */
	if (NULL == (outf = fopen(ilistpath, "w"))) {
		ilist_outofmemory("cannot open .ilist file");
		free(ilist);
		return 1;
	}

	if (chmod(ilistpath, 0644)) {
		ilist_outofmemory("failed to chmod .ilist file");
		free(ilist);
		return 1;
	}

	if (1 != fwrite(&header, sizeof(struct ilist_header), 1, outf)) {
		ilist_outofmemory("failed writing header to .ilist file");
		free(ilist);
		return 1;
	}

	if (ilist_len !=
		fwrite(ilist, sizeof(struct ilist_struct), ilist_len, outf)) {
		ilist_outofmemory("failed writing to .ilist file");
		free(ilist);
		return 1;
	}

	if (fclose(outf)) {
		ilist_outofmemory("error flushing to .ilist file");
		free(ilist);
		return 1;
	}
	return 0;
}

/* comparison function for qsort/bsearch of ilist
 */
int compare_ilist(const void *a, const void *b) {
	const struct ilist_struct *ilista = (const struct ilist_struct *)a;
	const struct ilist_struct *ilistb = (const struct ilist_struct *)b;
	int ret;

	ret = ilista->inode - ilistb->inode;
	if (!ret) {
		ret = ilista->dev - ilistb->dev;
	}
	return ret;
}

#ifdef ILISTCREATEBENCH
/* test code for performance tuning */
const char *ilist_PRGNAME = "testbench";

int main() {
	int i;

	if (-1 == chdir("/home/dancer/shared/git/linux-2.6/"))
		exit(1);

	for (i = 0; i < 100; ++i)
		ilistcreate("/home/dancer/shared/git/linux-2.6/.ilist", NULL);

	exit(0);
}

#endif
