#include <u.h>
#include <libc.h>
#include <bio.h>
#include <disk.h>
#include <libsec.h>
#include "iso9660.h"

ulong now;
int chatty;
int doabort;
int docolon;
int mk9660;
vlong dataoffset;
int blocksize;
Conform *map;

static void addprotofile(char *new, char *old, Dir *d, void *a);
void usage(void);

char *argv0;

void
usage(void)
{
	if(mk9660)
		fprint(2, "usage: disk/mk9660 [-D:] [-9cjr] [-[EBb] bootfile] [-o offset blocksize] [-p proto] [-s src] cdimage\n");
	else
		fprint(2, "usage: disk/dump9660 [-D:] [-9cjr] [-m maxsize] [-n now] [-p proto] [-s src] cdimage\n");
	exits("usage");
}

void
main(int argc, char **argv)
{
	int fix;
	ulong block, newnull, cblock;
	vlong maxsize;
	uvlong length, clength;
	char buf[256], *dumpname, *proto, *s, *src, *status;
	Cdimg *cd;
	Cdinfo info;
	XDir dir;
	Direc *iconform, idumproot, iroot, *jconform, jdumproot, jroot, *r;
	Dump *dump;

	fix = 0;
	status = nil;
	memset(&info, 0, sizeof info);
	proto = "/sys/lib/sysconfig/proto/allproto";
	src = "./";

	info.volumename = atom("9CD");
	info.volumeset = atom("9VolumeSet");
	info.publisher = atom("9Publisher");
	info.preparer = atom("dump9660");
	info.application = atom("dump9660");
	info.flags = CDdump;
	maxsize = 0;
	mk9660 = 0;
	fmtinstall('H', encodefmt);

	ARGBEGIN{
	case 'D':
		chatty++;
		break;
	case 'M':
		mk9660 = 1;
		argv0 = "disk/mk9660";
		info.flags &= ~CDdump;
		break;
	case '9':
		info.flags |= CDplan9;
		break;
	case ':':
		docolon = 1;
		break;
	case 'a':
		doabort = 1;
		break;
	case 'B':
		info.flags |= CDbootnoemu;
		/* fall through */
	case 'b':
		if(!mk9660)
			usage();
		info.flags |= CDbootable;
		info.bootimage = EARGF(usage());
		break;
	case 'E':
		/* efi fat image */
		if(!mk9660)
			usage();
		info.flags |= CDbootable;
		info.flags |= CDbootnoemu;
		info.efibootimage = EARGF(usage());
		break;
	case 'c':
		info.flags |= CDconform;
		break;
	case 'f':
		fix = 1;
		break;
	case 'j':
		info.flags |= CDjoliet;
		break;
	case 'n':
		now = atoi(EARGF(usage()));
		break;
	case 'm':
		maxsize = strtoull(EARGF(usage()), 0, 0);
		break;
	case 'o':
		dataoffset = atoll(EARGF(usage()));
		blocksize = atoi(EARGF(usage()));
		if(blocksize%Blocksize)
			sysfatal("bad block size %d -- must be multiple of 2048", blocksize);
		blocksize /= Blocksize;
		break;
	case 'p':
		proto = EARGF(usage());
		break;
	case 'r':
		info.flags |= CDrockridge;
		break;
	case 's':
		src = EARGF(usage());
		break;
	case 'v':
		info.volumename = atom(EARGF(usage()));
		break;
	default:
		usage();
	}ARGEND

	if(mk9660 && (fix || now || maxsize))
		usage();

	if(argc != 1)
		usage();

	if(now == 0)
		now = (ulong)time(0);
	if(mk9660){
		if((cd = createcd(argv[0], info)) == nil)
			sysfatal("cannot create '%s': %r", argv[0]);
	}else{
		if((cd = opencd(argv[0], info)) == nil)
			sysfatal("cannot open '%s': %r", argv[0]);
		if(!(cd->flags & CDdump))
			sysfatal("not a dump cd");
	}

	/* create ISO9660/Plan 9 tree in memory */
	memset(&dir, 0, sizeof dir);
	dir.name = atom("");
	dir.uid = atom("sys");
	dir.gid = atom("sys");
	dir.uidno = 0;
	dir.gidno = 0;
	dir.mode = DMDIR | 0755;
	dir.mtime = now;
	dir.atime = now;
	dir.ctime = now;

	mkdirec(&iroot, &dir);
	iroot.srcfile = src;

	/*
	 * Read new files into memory
	 */
	if(rdproto(proto, src, addprotofile, nil, &iroot) < 0)
		sysfatal("rdproto: %r");

	if(mk9660){
		dump = emalloc(sizeof *dump);
		dumpname = nil;
	}else{
		/*
		 * Read current dump tree and _conform.map.
		 */
		idumproot = readdumpdirs(cd, &dir, isostring);
		readdumpconform(cd);
		if(cd->flags & CDjoliet)
			jdumproot = readdumpdirs(cd, &dir, jolietstring);

		if(fix){
			dumpname = nil;
			cd->nextblock = cd->nulldump+1;
			cd->nulldump = 0;
			Cwseek(cd, (vlong)cd->nextblock * Blocksize);
			goto Dofix;
		}
	
		dumpname = adddumpdir(&idumproot, now, &dir);
		/* note that we assume all names are conforming and thus sorted */
		if(cd->flags & CDjoliet) {
			s = adddumpdir(&jdumproot, now, &dir);
			if(s != dumpname)
				sysfatal("dumpnames don't match %s %s", dumpname, s);
		}
		dump = dumpcd(cd, &idumproot);
		cd->nextblock = cd->nulldump+1;
	}

	/*
	 * Write new files, starting where the dump tree was.
 	 * Must be done before creation of the Joliet tree so that
 	 * blocks and lengths are correct.
	 */
	if(dataoffset > (vlong)cd->nextblock * Blocksize)
		cd->nextblock = (dataoffset+Blocksize-1)/Blocksize;
	Cwseek(cd, (vlong)cd->nextblock * Blocksize);
	writefiles(dump, cd, &iroot);

	if(cd->bootimage || cd->efibootimage)
		Cupdatebootcat(cd, &iroot);
		
	/* create Joliet tree */
	if(cd->flags & CDjoliet)
		copydirec(&jroot, &iroot);

	if(info.flags & CDconform) {
		checknames(&iroot, isbadiso9660);
		convertnames(&iroot, struprcpy);
	} else
		convertnames(&iroot, (void *) strcpy);

//	isoabstract = findconform(&iroot, abstract);
//	isobiblio = findconform(&iroot, biblio);
//	isonotice = findconform(&iroot, notice);

	dsort(&iroot, isocmp);

	if(cd->flags & CDjoliet) {
	//	jabstract = findconform(&jroot, abstract);
	//	jbiblio = findconform(&jroot, biblio);
	//	jnotice = findconform(&jroot, notice);

		checknames(&jroot, isbadjoliet);
		convertnames(&jroot, (void *) strcpy);
		dsort(&jroot, jolietcmp);
	}

	/*
	 * Write directories.
	 */
	writedirs(cd, &iroot, Cputisodir);
	if(cd->flags & CDjoliet)
		writedirs(cd, &jroot, Cputjolietdir);

	if(mk9660){
		cblock = 0;
		clength = 0;
		newnull = 0;
	}else{
		/*
		 * Write incremental _conform.map block.
		 */
		wrconform(cd, cd->nconform, &cblock, &clength);
	
		/* jump here if we're just fixing up the cd */
Dofix:
		/*
		 * Write null dump header block; everything after this will be 
		 * overwritten at the next dump.  Because of this, it needs to be
		 * reconstructable.  We reconstruct the _conform.map and dump trees
		 * from the header blocks in dump.c, and we reconstruct the path 
		 * tables by walking the cd.
		 */
		newnull = Cputdumpblock(cd);
	}

	/*
	 * Write _conform.map.
	 */
	dir.mode = 0444;
	if(cd->flags & (CDconform|CDjoliet)) {
		if(!mk9660 && cd->nconform == 0){
			block = cblock;	
			length = clength;
		}else
			wrconform(cd, 0, &block, &length);

		if(mk9660) 
{
			idumproot = iroot;
			jdumproot = jroot;
		}
		if(length) {
			/* The ISO9660 name will get turned into uppercase when written. */
			if((iconform = walkdirec(&idumproot, "_conform.map")) == nil)
				iconform = adddirec(&idumproot, "_conform.map", &dir);
			jconform = nil;
			if(cd->flags & CDjoliet) {
				if((jconform = walkdirec(&jdumproot, "_conform.map")) == nil)
					jconform = adddirec(&jdumproot, "_conform.map", &dir);
			}
			iconform->block = block;
			iconform->length = length;
			if(cd->flags & CDjoliet) {
				jconform->block = block;
				jconform->length = length;
			}
		}
		if(mk9660) {
			iroot = idumproot;
			jroot = jdumproot;
		}
	}

	if(mk9660){
		/*
		 * Patch in root directories.
		 */
		setroot(cd, cd->iso9660pvd, iroot.block, iroot.length);
		setvolsize(cd, cd->iso9660pvd, cd->nextblock);
		if(cd->flags & CDjoliet){
			setroot(cd, cd->jolietsvd, jroot.block, jroot.length);
			setvolsize(cd, cd->jolietsvd, cd->nextblock);
		}
	}else{
		/*
		 * Write dump tree at end.  We assume the name characters
		 * are all conforming, so everything is already sorted properly.
		 */
		convertnames(&idumproot, (info.flags & CDconform) ? (void *) struprcpy : (void *) strcpy);
		if(cd->nulldump) {
			r = walkdirec(&idumproot, dumpname);
			assert(r != nil);
			copybutname(r, &iroot);
		}
		if(cd->flags & CDjoliet) {
			convertnames(&jdumproot, (void *) strcpy);
			if(cd->nulldump) {
				r = walkdirec(&jdumproot, dumpname);
				assert(r != nil);
				copybutname(r, &jroot);
			}
		}
	
		writedumpdirs(cd, &idumproot, Cputisodir);
		if(cd->flags & CDjoliet)
			writedumpdirs(cd, &jdumproot, Cputjolietdir);
	
		/*
		 * Patch in new root directory entry.
		 */
		setroot(cd, cd->iso9660pvd, idumproot.block, idumproot.length);
		setvolsize(cd, cd->iso9660pvd, cd->nextblock);
		if(cd->flags & CDjoliet){
			setroot(cd, cd->jolietsvd, jdumproot.block, jdumproot.length);
			setvolsize(cd, cd->jolietsvd, cd->nextblock);
		}
	}
	writepathtables(cd);	

	if(!mk9660){
		/*
		 * If we've gotten too big, truncate back to what we started with,
		 * fix up the cd, and exit with a non-zero status.
		 */
		Cwflush(cd);
		if(cd->nulldump && maxsize && Cwoffset(cd) > maxsize){
			fprint(2, "too big; writing old tree back\n");
			status = "cd too big; aborted";
	
			rmdumpdir(&idumproot, dumpname);
			rmdumpdir(&jdumproot, dumpname);
	
			cd->nextblock = cd->nulldump+1;
			cd->nulldump = 0;
			Cwseek(cd, (vlong)cd->nextblock * Blocksize);
			goto Dofix;
		}
	
		/*
		 * Write old null header block; this commits all our changes.
		 */
		if(cd->nulldump){
			Cwseek(cd, (vlong)cd->nulldump * Blocksize);
			sprint(buf, "plan 9 dump cd\n");
			sprint(buf+strlen(buf), "%s %lud %lud %lud %llud %lud %lud",
				dumpname, now, newnull, cblock, clength,
				iroot.block, iroot.length);
			if(cd->flags & CDjoliet)
				sprint(buf+strlen(buf), " %lud %lud",
					jroot.block, jroot.length);
			strcat(buf, "\n");
			Cwrite(cd, buf, strlen(buf));
			Cpadblock(cd);
			Cwflush(cd);
		}
	}
	fdtruncate(cd->fd, (vlong)cd->nextblock * Blocksize);
	exits(status);
}

static void
addprotofile(char *new, char *old, Dir *d, void *a)
{
	char *name, *p;
	Direc *direc;
	XDir xd;

	dirtoxdir(&xd, d);
	name = nil;
	if(docolon && strchr(new, ':')) {
		name = emalloc(strlen(new)+1);
		strcpy(name, new);
		while((p=strchr(name, ':')))
			*p=' ';
		new = name;
	}
	if((direc = adddirec((Direc*)a, new, &xd))) {
		direc->srcfile = atom(old);

		// BUG: abstract, biblio, notice
	}
	if(name)
		free(name);
}
