Plan 9 from Bell Labs’s /usr/web/sources/contrib/nemo/sys/src/cmd/repl/syncdb.c

Copyright © 2021 Plan 9 Foundation.
Distributed under the MIT License.
Download the Plan 9 distribution.


#include <u.h>
#include <libc.h>
#include <bio.h>
#include <ctype.h>
#include <error.h>
#include <b.h>
#include <avl.h>
#include "repl.h"

#define vprint	if(verbose)print
#define dprint	if(debug)print

int pri;
int verbose;
int dryrun;
int dont;
int debug;
int force;

char**	dirdels;
int	ndirdels;

char**	syncdirs;
int	nsyncdirs;


static void
adddirdel(char* d)
{
	if ((ndirdels%Incr) == 0)
		dirdels = erealloc(dirdels, (ndirdels+Incr)*sizeof(char*));
	dirdels[ndirdels++] = estrdup(d);
}

static int
contained(char* fname, char* top)
{
	int	l;

	l = strlen(top);
	if (strncmp(fname, top, l) == 0)
	if (fname[l] == '/' || fname[l] == 0)
		return 1;
	return 0;
}

static void
addsyncdir(char* d)
{
	if (d[0] == '/' || (d[0] == '.' && (d[1] == '/' || d[1] == 0))){
		fprint(2, "%s: not relative from repl root.\n", d);
		exits("baddir");
	}

	if ((nsyncdirs%Incr) == 0)
		syncdirs = erealloc(syncdirs, (nsyncdirs+Incr)*sizeof(char*));
	syncdirs[nsyncdirs++] = smprint("/%s", d);
}


static int
mustsync(char* d)
{
	int	i;

	if (nsyncdirs == 0)	// no dirs: sync it all.
		return 1;

	for (i = 0; i < nsyncdirs; i++)
		if (contained(d, syncdirs[i]))
			return 1;
	return 0;
}

static void
deldirs(void)
{
	int	i;

	for (i = ndirdels - 1; i >= 0; i--){
		dprint("remove %s\n", dirdels[i]);
		remove(dirdels[i]);
	}
}

/* Keep in sync with scandb.c:/^cmpfile
 * But be very careful not to compare qid.vers here,
 * because qids come from different servers.
 */
static int
cmpfile(Dir* op, Dir* np)
{
	if ((op->qid.type&QTDIR)==0){
		if (op->length != np->length || op->mtime != np->mtime)
			return Datachg;
		if (op->mode != np->mode)
			return Metachg;
	}
	if (op->mode != np->mode ||
	    strcmp(op->uid, np->uid) || strcmp(op->gid, np->gid))
		return Metachg;
	return Eq;
}

static int
deletefile(char* to, char* fname, ulong dmtime)
{
	char* dfile;
	char* e;
	Dir*  dd;

	dfile = smprint("%s%s", to, fname);
	dd = dirstat(dfile);
	if (dd != nil){
		if (dd->mode&DMDIR){
			adddirdel(dfile);
			return 1;
		}
		if (!force && dmtime && dmtime != dd->mtime){
			free(dd);
			error("locally modified: %s: won't delete", dfile);
		}
		free(dd);
	}
	dprint("remove %s\n", fname);
	if (remove(dfile)<0){
		e = smprint("remove %s: %r\n", dfile);
		if (!strstr(e, "does not exist")){
			warn(e);
			free(e);
			return 0;
		}
		free(e);
	}
	return 1;
}

static void
copytofd(int fd, char* from)
{
	static char buf[64*1024];
	int	ffd;
	int	n;

	ffd = open(from, OREAD);
	if (ffd < 0)
		error("open %s: %r", from);
	while(n = read(ffd, buf, sizeof(buf))){
		if (n < 0)
			error("read %s: %r", from);
		if (write(fd, buf, n) != n)
			error("copying %s: %r", from);
	}
	close(ffd);
}

/*
 * This synchronizes the files according to their real FS.
 * The metadata within the DB is ignored.
 * The worst that can happen is that a more recent file is
 * copied, but according to the DB the file has changed anyway.
 * Perhaps relying just on the DB to see what to do would simplify
 * this looong routine.
 */

static int
syncfile(char* to, char* from, char* fname, ulong dmtime, ulong* vers)
{
	char*	dfile;
	char*	tfile;
	char*	sfile;
	Dir*	sd;
	Dir*	dd;
	Dir*	td;
	Dir	wd;
	int	fd;
	int	what;
	int	omode;
	char*	newfile;
	int	isnew;

	dfile = smprint("%s%s", to, fname);
	tfile = smprint("%s%s!tmp", to, fname);
	sfile = smprint("%s%s", from, fname);
	dprint("syncfile: %s: %s -> %s \n", fname, from, to);
	newfile = nil;
	sd = dirstat(sfile);
	dd = nil;
	fd = -1;
	if (catcherror()){
		free(dfile);
		free(tfile);
		free(sfile);
		free(sd);
		free(dd);
		if (newfile)
			remove(newfile);
		if (fd != -1)
			close(fd);
		fprint(2, "%r\n");
		return 0;
	}

	if (sd == nil)
		error("can't stat %s: %r", sfile);

	dd = dirstat(dfile);
	if (dd == nil)
		what = Datachg;
	else {
		if (!force && !(dd->mode&DMDIR) && dmtime && dd->mtime != dmtime)
			error("locally modified: %s: wont update", dfile);
		what = cmpfile(dd, sd);
		if (what == Datachg && (dd->mode&DMAPPEND))
			error("append-only file: %s: wont update", dfile);
	}
	if (sd->qid.type&QTDIR)
		omode = OREAD;
	else
		omode = OWRITE|OTRUNC;
	nulldir(&wd);
	isnew = 0;
	switch(what){
	case Eq:
		warn("same file: %s", dfile);
		td = dirstat(dfile);
		if (td != nil){
			*vers = td->qid.vers;
			free(td);
		}
		break;
	case Datachg:
		if (dd == nil){
			fd = create(tfile, omode, 0755|(sd->mode&DMDIR));
			if (fd < 0)
				error("can't create %s: %r", tfile);
			newfile = tfile;
			isnew = 1;
		} else {
			fd = open(dfile, omode);
			if (fd < 0)
				error("open %s: %r", dfile);
		}
		if (!(sd->qid.type&QTDIR)){
			copytofd(fd, sfile);
			if (!isnew)
				dprint("c %s\n", fname);
		}
		td = dirfstat(fd);
		if (td != nil){
			*vers = td->qid.vers;
			free(td);
		}
		if (close(fd)<0)
			error("close %s: %r", dfile);
		if (isnew){
			wd.name = sd->name;
			if (dirwstat(tfile, &wd) < 0)
				error("can't rename %s: %r", tfile);
			nulldir(&wd);
			newfile = nil;
			dprint("a %s\n", fname);
		}
		// fall through...
	case Metachg:
		if (dd == nil || dd->mode != sd->mode){
			wd.mode = sd->mode;
			if (dirwstat(dfile, &wd) < 0)
				error("can't wstat %s: %r", dfile);
		}
		if (!(sd->qid.type&QTDIR))
		if (dd == nil || dd->mtime != sd->mtime)
			wd.mtime = sd->mtime;
		if (dd == nil || strcmp(dd->uid, sd->uid))
			wd.uid = sd->uid;
		if (dd == nil || strcmp(dd->gid, sd->gid))
			wd.gid = sd->gid;
		if (dirwstat(dfile, &wd) < 0)
			warn("can't set mtime/uids: %s: %r", dfile);
		if (!isnew)
			dprint("m %s\n", fname);
		break;
	}

	noerror();
	free(dfile);
	free(tfile);
	free(sfile);
	free(sd);
	free(dd);
	if (newfile)
		remove(newfile);
	return 1;
}

static void
syncdbent(Db* to, Dbent* tdp, Dbent* fdp, ulong vers)
{

	to->changed = 1;
	if (tdp == nil){
		tdp = emalloc(sizeof(Dbent));
		memset(tdp, 0, sizeof(Dbent));
		tdp->fname = estrdup(fdp->fname);
		tdp->next = to->new;
		to->new = tdp;
	}
	tdp->hist = estrdup(fdp->hist);
	tdp->hlen = fdp->hlen;
	tdp->Meta = fdp->Meta;
	tdp->vers = vers;
}

static void
addnewents(Db* db)
{
	Dbent*	dp;

	while(db->new != nil){
		dp = db->new;
		db->new = dp->next;
		// dp->next = nil; not needed.
		insertdb(db, dp);
	}
}

static void
change(Db* to, Db* from, Dbent* tdp, Dbent* fdp)
{
	int	isdel;
	ulong	dmtime;
	ulong	vers;

	assert(fdp != nil);
	if (dont == to->id)
		return;
	if (0)
		vprint("%c %s %s\n", from->id, fdp->fname, fdp->hist);

	isdel = isupper(fdp->hist[fdp->hlen-1]);
	dmtime = tdp ? tdp->mtime : 0L;
	if (isdel){
		if (tdp){
			vprint("d %s%s\n", to->dir, tdp->fname);
			if (!dryrun && deletefile(to->dir, tdp->fname, dmtime))
				syncdbent(to, tdp, fdp, 0);
		} else
			syncdbent(to, nil, fdp, 0);
	} else {
		vprint("c %s%s\n", to->dir, fdp->fname);
		if (!dryrun)
		if (syncfile(to->dir, from->dir, fdp->fname, dmtime, &vers))
			syncdbent(to, tdp, fdp, vers);
	}
	// Directories deleted in reverse order: they must be empty.
	deldirs();
}

static void
conflict(char* fname, char* h1, char* h2)
{
	int	i;

	print("! %s:  after [", fname);
	for (i = 0; h1[i] && h2[i] && h1[i] == h2[i]; i++)
		print("%c", h1[i]);
	print("] either [%s] or [%s].\n", h1+i, h2+i);
}

static void
changes(Db* db1, Db* db2)
{
	Avlwalk*w;
	Dbent*	dp1;
	Dbent*	dp2;
	int	l;

	w = avlwalk(db1->tree);
	while(dp1 = (Dbent*)avlnext(w)){
		if (!mustsync(dp1->fname))
			continue;
		dp2 = (Dbent*)lookupavl(db2->tree, (Avl*)dp1);
		if (dp2 == nil){
			change(db2, db1, nil, dp1);
			continue;
		}
		dp2->visited = 1;
		l = dp1->hlen;
		if (dp2->hlen < l)
			l = dp2->hlen;
		if (strncmp(dp1->hist, dp2->hist, l) != 0){
			if (pri == db1->id)
				change(db2, db1, dp2, dp1);
			else if (pri == db2->id)
				change(db1, db2, dp1, dp2);
			else
				conflict(dp1->fname, dp1->hist, dp2->hist);
		} else if (dp1->hlen < dp2->hlen){
			change(db1, db2, dp1, dp2);
		} else if (dp1->hlen > dp2->hlen){
			change(db2, db1, dp2, dp1);
		}
	}
	endwalk(w);

	w = avlwalk(db2->tree);
	while(dp2 = (Dbent*)avlnext(w)){
		if (!mustsync(dp2->fname))
			continue;
		if (!dp2->visited)
			change(db1, db2, nil, dp2);
	}
	endwalk(w);

	// Done with avlwalks. Add any new node.
	addnewents(db1);
	addnewents(db2);
}

static void
usage(void)
{
	fprint(2, "usage: %s [-12Dflrnv] dir1 dir2 db1 db2 [syncdir...]\n", argv0);
	exits("usage");
}

void
main(int argc, char **argv)
{
	Db*	db1;
	Db*	db2;
	char*	nfname;
	int	i;
	Error	e;

	ARGBEGIN {
	case '1':
		pri=1;
		break;
	case '2':
		pri=2;
		break;
	case 'D':
		debug=1;
		break;
	case 'f':
		force=1;
		break;
	case 'l':
		dont=2;
		break;
	case 'r':
		dont=1;
		break;
	case 'v':
		verbose=1;
		break;
	case 'n':
		dryrun=verbose=1;
		break;
	default:
		usage();
	} ARGEND;

	if (argc < 4)
		usage();
	errinit(&e);
	if (catcherror()){
		sysfatal("%r");
	}

	for (i = 4; i < argc; i++)
		addsyncdir(argv[i]);

	db1 = readdbfile(argv[2]);
	db2 = readdbfile(argv[3]);
	if (*argv[0] != '/' || *argv[1] != '/')
		sysfatal("replica dirs must be absolute");
	db1->dir = cleanname(estrdup(argv[0]));
	db2->dir = cleanname(estrdup(argv[1]));
	if (!strcmp(db1->dir, db2->dir) || db1->id == db2->id)
		sysfatal("same replica");

	if (pri == 1)
		pri = db1->id;
	else if (pri == 2)
		pri = db2->id;
	if (dont == 1)
		dont = db1->id;
	else if (dont == 2)
		dont = db2->id;

	changes(db1, db2);

	if (db1->changed){
		nfname = smprint("%s.new", argv[2]);
		writedbfile(nfname, db1);
		rename(argv[2], nfname);
	}
	if (db2->changed){
		nfname = smprint("%s.new", argv[3]);
		writedbfile(nfname, db2);
		rename(argv[3], nfname);
	}
	noerror();
	exits(nil);
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to webmaster@9p.io.