#include <u.h>
#include <libc.h>
#include <disk.h>
#include <auth.h>
#include <bio.h>

enum{
	LEN = 4096,

	/*
	 * types of destination file sytems
	 */
	Fs = 0,
	Archive,
};

void	protowarn(char *msg, void *);
void	protoenum(char *new, char *old, Dir *d, void *);

void	arch(Dir*);
void	copy(Dir*);
void	error(char *, ...);
void	mkdir(Dir*);
int	uptodate(Dir*, char*);
void	usage(void);
void	warn(char *, ...);

Biobufhdr bout;			/* stdout when writing archive */
uchar	boutbuf[2*LEN];
char	newfile[LEN];
char	oldfile[LEN];
char	*proto;
char	*oldroot;
char	*newroot;
char	*prog = "mkfs";
char	*buf;
char	*zbuf;
int	buflen = 1024-8;
int	verb;
int	modes;
int	ream;
int	debug;
int	xflag;
int	oflag;
int	sfd;
int	fskind;			/* Fs, Archive */
int	setuid;			/* on Fs: set uid and gid? */
char	*user;

void
main(int argc, char **argv)
{
	int i, errs;

	quotefmtinstall();
	user = getuser();
	oldroot = "";
	newroot = "/n/newfs";
	fskind = Fs;
	ARGBEGIN{
	case 'a':
		fskind = Archive;
		newroot = "";
		Binits(&bout, 1, OWRITE, boutbuf, sizeof boutbuf);
		break;
	case 'd':
		if(fskind != Fs) {
			fprint(2, "cannot use -d with -a\n");
			usage();
		}
		fskind = Fs;
		newroot = EARGF(usage());
		break;
	case 'D':
		debug = 1;
		break;
	case 'p':
		modes = 1;
		break;
	case 'r':
		ream = 1;
		break;
	case 's':
		oldroot = EARGF(usage());
		break;
	case 'U':
		setuid = 1;
		break;
	case 'v':
		verb = 1;
		break;
	case 'o':
		oflag = 1;
		break;
	case 'x':
		xflag = 1;
		break;
	case 'z':
		buflen = atoi(EARGF(usage()))-8;
		break;
	default:
		usage();
	}ARGEND

	if(!argc)
		usage();

	if((xflag || oflag) && fskind != Archive){
		fprint(2, "cannot use -x and -o without -a\n");
		usage();
	}

	buf = malloc(buflen);
	zbuf = malloc(buflen);
	memset(zbuf, 0, buflen);

	errs = 0;
	for(i = 0; i < argc; i++){
		proto = argv[i];
		fprint(2, "processing %q\n", proto);
		if(rdproto(proto, oldroot, protoenum, protowarn, nil) < 0){
			fprint(2, "%q: can't open %q: skipping\n", prog, proto);
			errs++;
			continue;
		}
	}
	fprint(2, "file system made\n");
	if(errs)
		exits("skipped protos");
	if(fskind == Archive){
		if(!xflag && !oflag)
			Bprint(&bout, "end of archive\n");
		Bterm(&bout);
	}
	exits(0);
}

/*
 * check if file to is up to date with
 * respect to the file represented by df
 */
int
uptodate(Dir *df, char *to)
{
	int ret;
	Dir *dt;

	if(fskind == Archive || ream || (dt = dirstat(to)) == nil)
		return 0;
	ret = dt->mtime >= df->mtime;
	free(dt);
	return ret;
}

void
copy(Dir *d)
{
	char cptmp[LEN], *p;
	int f, t, n, needwrite, nowarnyet = 1;
	vlong tot, len;
	Dir nd;

	f = open(oldfile, OREAD);
	if(f < 0){
		warn("can't open %q: %r", oldfile);
		return;
	}
	t = -1;
	if(fskind == Archive)
		arch(d);
	else{
		strcpy(cptmp, newfile);
		p = utfrrune(cptmp, L'/');
		if(!p)
			error("internal temporary file error");
		strcpy(p+1, "__mkfstmp");
		t = create(cptmp, OWRITE, 0666);
		if(t < 0){
			warn("can't create %q: %r", newfile);
			close(f);
			return;
		}
	}

	needwrite = 0;
	for(tot = 0; tot < d->length; tot += n){
		len = d->length - tot;
		/* don't read beyond d->length */
		if (len > buflen)
			len = buflen;
		n = read(f, buf, len);
		if(n <= 0) {
			if(n < 0 && nowarnyet) {
				warn("can't read %q: %r", oldfile);
				nowarnyet = 0;
			}
			/*
			 * don't quit: pad to d->length (in pieces) to agree
			 * with the length in the header, already emitted.
			 */
			memset(buf, 0, len);
			n = len;
		}
		if(fskind == Archive){
			if(Bwrite(&bout, buf, n) != n)
				error("write error: %r");
		}else if(memcmp(buf, zbuf, n) == 0){
			if(seek(t, n, 1) < 0)
				error("can't write zeros to %q: %r", newfile);
			needwrite = 1;
		}else{
			if(write(t, buf, n) < n)
				error("can't write %q: %r", newfile);
			needwrite = 0;
		}
	}
	close(f);
	if(needwrite){
		if(seek(t, -1, 1) < 0 || write(t, zbuf, 1) != 1)
			error("can't write zero at end of %q: %r", newfile);
	}
	if(tot != d->length){
		/* this should no longer happen */
		warn("wrong number of bytes written to %q (was %lld should be %lld)\n",
			newfile, tot, d->length);
		if(fskind == Archive){
			warn("seeking to proper position\n");
			/* does no good if stdout is a pipe */
			Bseek(&bout, d->length - tot, 1);
		}
	}
	if(fskind == Archive)
		return;
	remove(newfile);
	nulldir(&nd);
	nd.mode = d->mode;
	nd.gid = d->gid;
	nd.mtime = d->mtime;
	nd.name = d->name;
	if(dirfwstat(t, &nd) < 0)
		error("can't move tmp file to %q: %r", newfile);
	nulldir(&nd);
	nd.uid = d->uid;
	dirfwstat(t, &nd);
	close(t);
}

void
mkdir(Dir *d)
{
	Dir *d1;
	Dir nd;
	int fd;

	if(fskind == Archive){
		arch(d);
		return;
	}
	fd = create(newfile, OREAD, d->mode);
	nulldir(&nd);
	nd.mode = d->mode;
	nd.gid = d->gid;
	nd.mtime = d->mtime;
	if(fd < 0){
		if((d1 = dirstat(newfile)) == nil || !(d1->mode & DMDIR)){
			free(d1);
			error("can't create %q", newfile);
		}
		free(d1);
		if(dirwstat(newfile, &nd) < 0)
			warn("can't set modes for %q: %r", newfile);
		nulldir(&nd);
		nd.uid = d->uid;
		dirwstat(newfile, &nd);
		return;
	}
	if(dirfwstat(fd, &nd) < 0)
		warn("can't set modes for %q: %r", newfile);
	nulldir(&nd);
	nd.uid = d->uid;
	dirfwstat(fd, &nd);
	close(fd);
}

void
arch(Dir *d)
{
	Bprint(&bout, "%q %luo %q %q %lud %lld\n",
		newfile, d->mode, d->uid, d->gid, d->mtime, d->length);
}

void
protowarn(char *msg, void *)
{
	warn("%s", msg);
}

void
protoenum(char *new, char *old, Dir *d, void *)
{
	Dir nd;

	sprint(newfile, "%s%s", newroot, new);
	sprint(oldfile, "%s", old);

	if(oflag){
		if(!(d->mode & DMDIR))
			Bprint(&bout, "%q\n", cleanname(oldfile));
		return;
	}
	if(xflag){
		Bprint(&bout, "%q\t%ld\t%lld\n", new, d->mtime, d->length);
		return;
	}
	if(verb && (fskind == Archive || ream))
		fprint(2, "%q\n", new);
	if(fskind == Fs && !setuid){
		d->uid = "";
		d->gid = "";
	}
	if(!uptodate(d, newfile)){
		if(verb && (fskind != Archive && ream == 0))
			fprint(2, "%q\n", new);
		if(d->mode & DMDIR)
			mkdir(d);
		else
			copy(d);
	}else if(modes){
		nulldir(&nd);
		nd.mode = d->mode;
		nd.gid = d->gid;
		nd.mtime = d->mtime;
		if(verb && (fskind != Archive && ream == 0))
			fprint(2, "%q\n", new);
		if(dirwstat(newfile, &nd) < 0)
			warn("can't set modes for %q: %r", new);
		nulldir(&nd);
		nd.uid = d->uid;
		dirwstat(newfile, &nd);
	}
}

void
error(char *fmt, ...)
{
	char buf[1024];
	va_list arg;

	sprint(buf, "%q: %q: ", prog, proto);
	va_start(arg, fmt);
	vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg);
	va_end(arg);
	fprint(2, "%s\n", buf);
	exits(0);
}

void
warn(char *fmt, ...)
{
	char buf[1024];
	va_list arg;

	sprint(buf, "%q: %q: ", prog, proto);
	va_start(arg, fmt);
	vseprint(buf+strlen(buf), buf+sizeof(buf), fmt, arg);
	va_end(arg);
	fprint(2, "%s\n", buf);
}

void
usage(void)
{
	fprint(2, "usage: %q [-adprvoxUD] [-d root] [-s source] [-z n] proto ...\n", prog);
	exits("usage");
}
