/*
 * generate a list of files and their metadata
 * using a given proto file.
 */
#include "all.h"

int changesonly;
char *uid;
Db *db;
Biobuf blog;
ulong now;
int n;
char **x;
int nx;
int justlog;
char *root=".";
char **match;
int nmatch;

int
ismatch(char *s)
{
	int i, len;

	if(nmatch == 0)
		return 1;
	for(i=0; i<nmatch; i++){
		if(strcmp(s, match[i]) == 0)
			return 1;
		len = strlen(match[i]);
		if(strncmp(s, match[i], len) == 0 && s[len]=='/')
			return 1;
	}
	return 0;
}

void
xlog(int c, char *name, Dir *d)
{
	char *dname;

	dname = d->name;
	if(strcmp(dname, name) == 0)
		dname = "-";
	if(!justlog)
		Bprint(&blog, "%lud %d ", now, n++);
	Bprint(&blog, "%c %q %q %luo %q %q %lud %lld\n",
		c, name, dname, d->mode, uid ? uid : d->uid, d->gid, d->mtime, d->length);
}

void
walk(char *new, char *old, Dir *xd, void*)
{
	int i, change, len;
	Dir od, d;

	new = unroot(new, "/");
	old = unroot(old, root);

	if(!ismatch(new))
		return;
	for(i=0; i<nx; i++){
		if(strcmp(new, x[i]) == 0)
			return;
		len = strlen(x[i]);
		if(strncmp(new, x[i], len)==0 && new[len]=='/')
			return;
	}

	d = *xd;
	d.name = old;
	memset(&od, 0, sizeof od);
	change = 0;
	if(markdb(db, new, &od) < 0){
		if(!changesonly){
			xlog('a', new, &d);
			change = 1;
		}
	}else{
		if((d.mode&DMDIR)==0 && (od.mtime!=d.mtime || od.length!=d.length)){
			xlog('c', new, &d);
			change = 1;
		}
		if((!uid&&strcmp(od.uid,d.uid)!=0)
		|| strcmp(od.gid,d.gid)!=0 
		|| od.mode!=d.mode){
			xlog('m', new, &d);
			change = 1;
		}
	}
	if(!justlog && change){
		if(uid)
			d.uid = uid;
		d.muid = "mark";	/* mark bit */
		insertdb(db, new, &d);
	}
}

void
warn(char *msg, void*)
{
	char *p;

	fprint(2, "warning: %s\n", msg);

	/* find the %r in "can't open foo: %r" */
	p = strstr(msg, ": ");
	if(p)
		p += 2;

	/*
	 * if the error is about a remote server failing,
	 * then there's no point in continuing to look
	 * for changes -- we'll think everything got deleted!
	 *
	 * actual errors i see are:
	 *	"i/o on hungup channel" for a local hangup
	 *	"i/o on hungup channel" for a timeout (yank the network wire)
	 *	"'/n/sources/plan9' Hangup" for a remote hangup
	 * the rest is paranoia.
	 */
	if(p){
		if(cistrstr(p, "hungup") || cistrstr(p, "Hangup")
		|| cistrstr(p, "rpc error")
		|| cistrstr(p, "shut down")
		|| cistrstr(p, "i/o")
		|| cistrstr(p, "connection"))
			sysfatal("suspected network or i/o error - bailing out");
	}
}

void
usage(void)
{
	fprint(2, "usage: replica/updatedb [-c] [-p proto] [-r root] [-t now n] [-u uid] [-x path]... db [paths]\n");
	exits("usage");
}

void
main(int argc, char **argv)
{
	char *proto;
	Dir d;
	Entry *e;

	quotefmtinstall();
	proto = "/sys/lib/sysconfig/proto/allproto";
	now = time(0);
	Binit(&blog, 1, OWRITE);
	ARGBEGIN{
	case 'c':
		changesonly = 1;
		break;
	case 'l':
		justlog = 1;
		break;
	case 'p':
		proto = EARGF(usage());
		break;
	case 'r':
		root = EARGF(usage());
		break;
	case 't':
		now = strtoul(EARGF(usage()), 0, 0);
		n = atoi(EARGF(usage()));
		break;
	case 'u':
		uid = EARGF(usage());
		break;
	case 'x':
		if(nx%16 == 0)
			x = erealloc(x, (nx+16)*sizeof(x[0]));
		x[nx++] = EARGF(usage());
		break;
	default:
		usage();
	}ARGEND

	if(argc <1)
		usage();

	match = argv+1;
	nmatch = argc-1;

	db = opendb(argv[0]);
	if(rdproto(proto, root, walk, warn, nil) < 0)
		sysfatal("rdproto: %r");

	if(!changesonly){
		for(e = (Entry*)avlmax(db->avl); e != nil; e = (Entry*)avlprev(e)){
			if(!ismatch(e->name))
				continue;
			if(!e->d.mark){		/* not visited during walk */
				memset(&d, 0, sizeof d);
				d.name = e->d.name;
				d.uid = e->d.uid;
				d.gid = e->d.gid;
				d.mtime = e->d.mtime;
				d.mode = e->d.mode;
				xlog('d', e->name, &d);
				if(!justlog)
					removedb(db, e->name);
			}
		}
	}

	if(Bterm(&blog) < 0)
		sysfatal("writing output: %r");

	exits(nil);
}

