#include "stdinc.h"
#include "dat.h"
#include "fns.h"
#include "flfmt9660.h"

#define blockWrite _blockWrite	/* hack */

static void usage(void);
static u64int fdsize(int fd);
static void partition(int fd, int bsize, Header *h);
static u64int unittoull(char *s);
static u32int blockAlloc(int type, u32int tag);
static void blockRead(int part, u32int addr);
static void blockWrite(int part, u32int addr);
static void superInit(char *label, u32int root, uchar[VtScoreSize]);
static void rootMetaInit(Entry *e);
static u32int rootInit(Entry *e);
static void topLevel(char *name);
static int parseScore(uchar[VtScoreSize], char*);
static u32int ventiRoot(char*, char*);
static VtConn *z;

#define TWID64	((u64int)~(u64int)0)

Disk *disk;
Fs *fs;
uchar *buf;
int bsize = 8*1024;
u64int qid = 1;
int iso9660off;
char *iso9660file;

int
confirm(char *msg)
{
	char buf[100];
	int n;

	fprint(2, "%s [y/n]: ", msg);
	n = read(0, buf, sizeof buf - 1);
	if(n <= 0)
		return 0;
	if(buf[0] == 'y')
		return 1;
	return 0;
}

void
threadmain(int argc, char *argv[])
{
	int fd, force;
	Header h;
	ulong bn;
	Entry e;
	char *label = "vfs";
	char *host = nil;
	char *score = nil;
	u32int root;
	Dir *d;

	force = 0;
	ARGBEGIN{
	default:
		usage();
	case 'b':
		bsize = unittoull(EARGF(usage()));
		if(bsize == ~0)
			usage();
		break;
	case 'h':
		host = EARGF(usage());
		break;
	case 'i':
		iso9660file = EARGF(usage());
		iso9660off = atoi(EARGF(usage()));
		break;
	case 'l':
		label = EARGF(usage());
		break;
	case 'v':
		score = EARGF(usage());
		break;

	/*
	 * This is -y instead of -f because flchk has a
	 * (frequently used) -f option.  I type flfmt instead
	 * of flchk all the time, and want to make it hard
	 * to reformat my file system accidentally.
	 */
	case 'y':
		force = 1;
		break;
	}ARGEND

	if(argc != 1)
		usage();

	if(iso9660file && score)
		sysfatal("cannot use -i with -v");

	fmtinstall('V', scoreFmt);
	fmtinstall('L', labelFmt);

	fd = open(argv[0], ORDWR);
	if(fd < 0)
		sysfatal("could not open file: %s: %r", argv[0]);

	buf = vtmallocz(bsize);
	if(pread(fd, buf, bsize, HeaderOffset) != bsize)
		sysfatal("could not read fs header block: %r");

	if(headerUnpack(&h, buf) && !force
	&& !confirm("fs header block already exists; are you sure?"))
		goto Out;

	if((d = dirfstat(fd)) == nil)
		sysfatal("dirfstat: %r");

	if(d->type == 'M' && !force
	&& !confirm("fs file is mounted via devmnt (is not a kernel device); are you sure?"))
		goto Out;

	partition(fd, bsize, &h);
	headerPack(&h, buf);
	if(pwrite(fd, buf, bsize, HeaderOffset) < bsize)
		sysfatal("could not write fs header: %r");

	disk = diskAlloc(fd);
	if(disk == nil)
		sysfatal("could not open disk: %r");

	if(iso9660file)
		iso9660init(fd, &h, iso9660file, iso9660off);

	/* zero labels */
	memset(buf, 0, bsize);
	for(bn = 0; bn < diskSize(disk, PartLabel); bn++)
		blockWrite(PartLabel, bn);

	if(iso9660file)
		iso9660labels(disk, buf, blockWrite);

	if(score)
		root = ventiRoot(host, score);
	else{
		rootMetaInit(&e);
		root = rootInit(&e);
	}

	superInit(label, root, vtzeroscore);
	diskFree(disk);

	if(score == nil)
		topLevel(argv[0]);

Out:
	threadexitsall(0);
}

static u64int
fdsize(int fd)
{
	Dir *dir;
	u64int size;

	dir = dirfstat(fd);
	if(dir == nil)
		sysfatal("could not stat file: %r");
	size = dir->length;
	free(dir);
	return size;
}

static void
usage(void)
{
	fprint(2, "usage: %s [-b blocksize] [-h host] [-i file offset] "
		"[-l label] [-v score] [-y] file\n", argv0);
	threadexitsall("usage");
}

static void
partition(int fd, int bsize, Header *h)
{
	ulong nblock, ndata, nlabel;
	ulong lpb;

	if(bsize % 512 != 0)
		sysfatal("block size must be a multiple of 512 bytes");
	if(bsize > VtMaxLumpSize)
		sysfatal("block size must be less than %d", VtMaxLumpSize);

	memset(h, 0, sizeof(*h));
	h->blockSize = bsize;

	lpb = bsize/LabelSize;

	nblock = fdsize(fd)/bsize;

	/* sanity check */
	if(nblock < (HeaderOffset*10)/bsize)
		sysfatal("file too small");

	h->super = (HeaderOffset + 2*bsize)/bsize;
	h->label = h->super + 1;
	ndata = ((u64int)lpb)*(nblock - h->label)/(lpb+1);
	nlabel = (ndata + lpb - 1)/lpb;
	h->data = h->label + nlabel;
	h->end = h->data + ndata;

}

static u32int
tagGen(void)
{
	u32int tag;

	for(;;){
		tag = lrand();
		if(tag > RootTag)
			break;
	}
	return tag;
}

static void
entryInit(Entry *e)
{
	e->gen = 0;
	e->dsize = bsize;
	e->psize = bsize/VtEntrySize*VtEntrySize;
	e->flags = VtEntryActive;
	e->depth = 0;
	e->size = 0;
	memmove(e->score, vtzeroscore, VtScoreSize);
	e->tag = tagGen();
	e->snap = 0;
	e->archive = 0;
}

static void
rootMetaInit(Entry *e)
{
	u32int addr;
	u32int tag;
	DirEntry de;
	MetaBlock mb;
	MetaEntry me;

	memset(&de, 0, sizeof(de));
	de.elem = vtstrdup("root");
	de.entry = 0;
	de.gen = 0;
	de.mentry = 1;
	de.mgen = 0;
	de.size = 0;
	de.qid = qid++;
	de.uid = vtstrdup("adm");
	de.gid = vtstrdup("adm");
	de.mid = vtstrdup("adm");
	de.mtime = time(0);
	de.mcount = 0;
	de.ctime = time(0);
	de.atime = time(0);
	de.mode = ModeDir | 0555;

	tag = tagGen();
	addr = blockAlloc(BtData, tag);

	/* build up meta block */
	memset(buf, 0, bsize);
	mbInit(&mb, buf, bsize, bsize/100);
	me.size = deSize(&de);
	me.p = mbAlloc(&mb, me.size);
	assert(me.p != nil);
	dePack(&de, &me);
	mbInsert(&mb, 0, &me);
	mbPack(&mb);
	blockWrite(PartData, addr);
	deCleanup(&de);

	/* build up entry for meta block */
	entryInit(e);
	e->flags |= VtEntryLocal;
 	e->size = bsize;
	e->tag = tag;
	localToGlobal(addr, e->score);
}

static u32int
rootInit(Entry *e)
{
	ulong addr;
	u32int tag;

	tag = tagGen();

	addr = blockAlloc(BtDir, tag);
	memset(buf, 0, bsize);

	/* root meta data is in the third entry */
	entryPack(e, buf, 2);

	entryInit(e);
	e->flags |= _VtEntryDir;
	entryPack(e, buf, 0);

	entryInit(e);
	entryPack(e, buf, 1);

	blockWrite(PartData, addr);

	entryInit(e);
	e->flags |= VtEntryLocal|_VtEntryDir;
 	e->size = VtEntrySize*3;
	e->tag = tag;
	localToGlobal(addr, e->score);

	addr = blockAlloc(BtDir, RootTag);
	memset(buf, 0, bsize);
	entryPack(e, buf, 0);

	blockWrite(PartData, addr);

	return addr;
}


static u32int
blockAlloc(int type, u32int tag)
{
	static u32int addr;
	Label l;
	int lpb;

	lpb = bsize/LabelSize;

	blockRead(PartLabel, addr/lpb);
	if(!labelUnpack(&l, buf, addr % lpb))
		sysfatal("bad label: %r");
	if(l.state != BsFree)
		sysfatal("want to allocate block already in use");
	l.epoch = 1;
	l.epochClose = ~(u32int)0;
	l.type = type;
	l.state = BsAlloc;
	l.tag = tag;
	labelPack(&l, buf, addr % lpb);
	blockWrite(PartLabel, addr/lpb);
	return addr++;
}

static void
superInit(char *label, u32int root, uchar score[VtScoreSize])
{
	Super s;

	memset(buf, 0, bsize);
	memset(&s, 0, sizeof(s));
	s.version = SuperVersion;
	s.epochLow = 1;
	s.epochHigh = 1;
	s.qid = qid;
	s.active = root;
	s.next = NilBlock;
	s.current = NilBlock;
	strecpy(s.name, s.name+sizeof(s.name), label);
	memmove(s.last, score, VtScoreSize);

	superPack(&s, buf);
	blockWrite(PartSuper, 0);
}

static u64int
unittoull(char *s)
{
	char *es;
	u64int n;

	if(s == nil)
		return TWID64;
	n = strtoul(s, &es, 0);
	if(*es == 'k' || *es == 'K'){
		n *= 1024;
		es++;
	}else if(*es == 'm' || *es == 'M'){
		n *= 1024*1024;
		es++;
	}else if(*es == 'g' || *es == 'G'){
		n *= 1024*1024*1024;
		es++;
	}
	if(*es != '\0')
		return TWID64;
	return n;
}

static void
blockRead(int part, u32int addr)
{
	if(!diskReadRaw(disk, part, addr, buf))
		sysfatal("read failed: %r");
}

static void
blockWrite(int part, u32int addr)
{
	if(!diskWriteRaw(disk, part, addr, buf))
		sysfatal("write failed: %r");
}

static void
addFile(File *root, char *name, uint mode)
{
	File *f;

	f = fileCreate(root, name, mode | ModeDir, "adm");
	if(f == nil)
		sysfatal("could not create file: %s: %r", name);
	fileDecRef(f);
}

static void
topLevel(char *name)
{
	Fs *fs;
	File *root;

	/* ok, now we can open as a fs */
	fs = fsOpen(name, z, 100, OReadWrite);
	if(fs == nil)
		sysfatal("could not open file system: %r");
	rlock(&fs->elk);
	root = fsGetRoot(fs);
	if(root == nil)
		sysfatal("could not open root: %r");
	addFile(root, "active", 0555);
	addFile(root, "archive", 0555);
	addFile(root, "snapshot", 0555);
	fileDecRef(root);
	if(iso9660file)
		iso9660copy(fs);
	runlock(&fs->elk);
	fsClose(fs);
}

static int
ventiRead(uchar score[VtScoreSize], int type)
{
	int n;

	n = vtread(z, score, type, buf, bsize);
	if(n < 0)
		sysfatal("ventiRead %V (%d) failed: %r", score, type);
	vtzeroextend(type, buf, n, bsize);
	return n;
}

static u32int
ventiRoot(char *host, char *s)
{
	int i, n;
	uchar score[VtScoreSize];
	u32int addr, tag;
	DirEntry de;
	MetaBlock mb;
	MetaEntry me;
	Entry e;
	VtRoot root;

	if(!parseScore(score, s))
		sysfatal("bad score '%s'", s);

	if((z = vtdial(host)) == nil
	|| vtconnect(z) < 0)
		sysfatal("connect to venti: %r");

	tag = tagGen();
	addr = blockAlloc(BtDir, tag);

	ventiRead(score, VtRootType);
	if(vtrootunpack(&root, buf) < 0)
		sysfatal("corrupted root: vtrootunpack");
	n = ventiRead(root.score, VtDirType);

	/*
	 * Fossil's vac archives start with an extra layer of source,
	 * but vac's don't.
	 */
	if(n <= 2*VtEntrySize){
		if(!entryUnpack(&e, buf, 0))
			sysfatal("bad root: top entry");
		n = ventiRead(e.score, VtDirType);
	}

	/*
	 * There should be three root sources (and nothing else) here.
	 */
	for(i=0; i<3; i++){
		if(!entryUnpack(&e, buf, i)
		|| !(e.flags&VtEntryActive)
		|| e.psize < 256
		|| e.dsize < 256)
			sysfatal("bad root: entry %d", i);
		fprint(2, "%V\n", e.score);
	}
	if(n > 3*VtEntrySize)
		sysfatal("bad root: entry count");

	blockWrite(PartData, addr);

	/*
	 * Maximum qid is recorded in root's msource, entry #2 (conveniently in e).
	 */
	ventiRead(e.score, VtDataType);
	if(!mbUnpack(&mb, buf, bsize))
		sysfatal("bad root: mbUnpack");
	meUnpack(&me, &mb, 0);
	if(!deUnpack(&de, &me))
		sysfatal("bad root: dirUnpack");
	if(!de.qidSpace)
		sysfatal("bad root: no qidSpace");
	qid = de.qidMax;

	/*
	 * Recreate the top layer of source.
	 */
	entryInit(&e);
	e.flags |= VtEntryLocal|_VtEntryDir;
	e.size = VtEntrySize*3;
	e.tag = tag;
	localToGlobal(addr, e.score);

	addr = blockAlloc(BtDir, RootTag);
	memset(buf, 0, bsize);
	entryPack(&e, buf, 0);
	blockWrite(PartData, addr);

	return addr;
}

static int
parseScore(uchar *score, char *buf)
{
	int i, c;

	memset(score, 0, VtScoreSize);

	if(strlen(buf) < VtScoreSize*2)
		return 0;
	for(i=0; i<VtScoreSize*2; i++){
		if(buf[i] >= '0' && buf[i] <= '9')
			c = buf[i] - '0';
		else if(buf[i] >= 'a' && buf[i] <= 'f')
			c = buf[i] - 'a' + 10;
		else if(buf[i] >= 'A' && buf[i] <= 'F')
			c = buf[i] - 'A' + 10;
		else
			return 0;

		if((i & 1) == 0)
			c <<= 4;

		score[i>>1] |= c;
	}
	return 1;
}
