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

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


/*
 * Volumes and mount points for volumes.
 * Support for the multiplexor.
 */
#include <u.h>
#include <libc.h>
#include <fcall.h>
#include <thread.h>
#include <bio.h>
#include "names.h"
#include "vols.h"

enum {
	Worst = 10000,	// any big number is a bad m->choice.
};

QLock	volslck;
Vol**	vols;
int	nvols;

QLock	mvolslck;
Mvol**	mvols;
int	nmvols;

Ref	epoch;

static int
diedvol(Vol* v)
{
	int	dead;

	if (v->disabled>0 || (v->fs && v->fs->fd < 0)){
		vdprint(2, "checkvols: dead vol: %s, %s\n", v->addr, v->name);
		dead = 1;
	} else if (v->fs && v->fs->epoch > v->epoch){
		vdprint(2, "checkvols: gone vol: %s, %s\n", v->addr, v->name);
		dead = 1;
	} else
		dead = 0;
	return dead;
}

static void
volunmount(Vol* v, Frpc* fop, int dead)
{
	if (dead && v->dead)
		return;

assert(v->ref == 0 || !canqlock(&volslck));

	if (dead && !diedvol(v)){
		// the vol resurrected while we were waiting
		// for the lock. In any case, it's no longer dead.
		return;
	}
	v->dead |= dead;
	if (v->slash){
		fidfree(v->slash, fop);
		v->slash = nil;
	}
	if (!v->dead){
		putfs(v->fs);
		v->fs = nil;
	}
	incref(&epoch);
	v->epoch = epoch.ref;
	vdprint(2, "unmounted %s %s e=%ld\n", v->addr, v->name, epoch.ref);
}

void
deadvol(Vol* v, Frpc* fop)
{
	volunmount(v, fop, 1);
}

static void
putvol(Vol* v, Frpc* fop)
{
	if (v && decref(v) == 0)
		volunmount(v, fop, 0);
}

static void
addmvol(Mvol* v)
{
	int	i;

	qlock(&mvolslck);
	for (i = 0; i < nmvols; i++)
		if (mvols[i] == nil)
			break;
	if (i == nmvols){
		if (!(nmvols%16))
			mvols = erealloc(mvols, sizeof(Mvol*)*(nmvols+16));
		nmvols++;
	}
	mvols[i] = v;
	qunlock(&mvolslck);
}

static void
delmvol(Mvol* v)
{
	int	i;

	qlock(&mvolslck);
	for (i = 0; i < nmvols; i++)
		if (mvols[i] == v){
			mvols[i] = nil;
			qunlock(&mvolslck);
			return;
		}
	fprint(2, "bad mvol\n");
	abort();
	qunlock(&mvolslck);
}

void
tracemvol(char* name, int dbg)
{
	Fid*	f;
	int	i;

	qlock(&mvolslck);
	for (i = 0; i < nmvols; i++)
		if (name == nil || !strcmp(mvols[i]->name, name)){
			mvols[i]->debug = dbg;
			for (f = mvols[i]->fids; f ; f = f->mnext)
				f->debug = dbg;
		}
	qunlock(&mvolslck);
}

Mvol*
newmvol(char* spec)
{
	Mvol*	m;
	char*	c;

	if (!*spec)		// ctl file system. No mvols there
		return nil;	// this is not an error.

	if (!strchr("*/!-", spec[0]))
		return nil;
	m = emalloc(sizeof(Mvol));
	memset(m, 0, sizeof(Mvol));
	m->spec = estrdup(spec);
	switch(spec[0]){
	case '!':
		m->musthave = 1;
		spec++;
		break;
	case '*':
		m->isunion = 1;
		spec++;
		break;
	case '-':
		c = ++spec;
		spec = strchr(spec, ' ');
		if (spec == nil){
			free(m->spec);
			free(m);
			return nil;
		}
		*spec++ = 0;
		if (strchr(c, 'M'))
			m->musthave = 1;
		if (strchr(c, 'U'))
			m->isunion = 1;
		if (strchr(c, 'T'))
			m->notimeout = 1;
		break;
	}
	c = strpbrk(spec, " \t");
	parseexpr(c, &m->cnstr);
	if (c)
		*c = 0;
	m->name = estrdup(spec);
	if (!m->isunion)
		m->choice = Worst;
	m->ref = 0;	// Nobody *using* us. Caller will bindfid
	addmvol(m);
	return m;
}

static void
mvolfree(Mvol* m, Frpc* fop)
{
	int	i;
	Fid*	f;

	delmvol(m);
	free(m->name);
	free(m->spec);
	for (i = 0; i < m->nvols; i++)
		putvol(m->vols[i], fop);
	free(m->vols);
	for (f = m->fids; f; f = f->mnext)
		fprint(2, "mvolfree: %p has linked fid %X\n", m, f);
	free(m);
}

Fid*
mvolgetfid(Mvol* m, Name* sname, Fid* excl)
{
	Fid*	f;

	if (!m)
		return nil;
	qlock(m);
	for (f = m->fids; f ; f = f->mnext)
		if (f != excl && n_eq(f->sname, sname))
			break;
	qunlock(m);
	return f;
}

void
mvoldelotherfids(Mvol* m, Fid* fid)
{
	Fid**	l;
	Fid*	f;

	if (!m || !fid)
		return;
	qlock(m);
again:
	for (l = &m->fids; f = *l ; l = &f->mnext){
		if (f != fid && n_eq(f->sname, fid->sname)){
			*l = f->mnext;
			f->mnext = nil;
			f->linked = 0;
			goto again;
		}
	}
	qunlock(m);
}

void
mvoladdfid(Mvol* m, Fid* fid)
{
	Fid**	l;
	Fid*	f;

	assert(!fid->linked);
	assert(fid->mnext == nil);
	qlock(m);
	for (l = &m->fids; f = *l ; l = &f->mnext){
		if (f == fid)
			fprint(2, "dup: pc = %lx: %X\n", getcallerpc(&m), f);
		assert(f != fid);
		if (n_eq(f->sname, fid->sname)){
			fid->qid = f->qid;
		}
	}
	assert(l);
	*l = fid;
	fid->linked = 1;
	qunlock(m);
}

void
mvoldelfid(Mvol* m, Fid* fid)
{
	Fid**	l;
	Fid*	f;

	if (!fid->linked)
		return;
	qlock(m);
	for (l = &m->fids; f = *l ; l = &f->mnext){
		if (f == fid){
			*l = f->mnext;
			f->mnext = nil;
			fid->linked = 0;
			break;
		}
	}
	qunlock(m);
	assert(f != nil);
}

void
putmvol(Mvol* m, Frpc* fop)
{
	if (m && decref(m) == 0)
		mvolfree(m, fop);
}

static int
volmatch(Vol* v, Mvol* m)
{
	if (strcmp(v->name, m->name))
		return -1;
	return exprmatch(&v->ccnstr, &m->cnstr);
}

static int
hasvol(Mvol* m, Vol* v)
{
	int	i;

	for (i = 0; i < m->nvols; i++)
		if (m->vols[i] == v)
			return 1;
	return 0;
}

static int
mvolmount(Mvol* m, Vol* v, Frpc* fop)
{
	if (!v->fs)
		if (!volmount(v, fop))
			return 0;
	assert(v->fs);
	if (!(m->nvols%16))
		m->vols = erealloc(m->vols, (m->nvols+16)*sizeof(Vol*));
	incref(v);
	m->vols[m->nvols] = v;
	m->nvols++;
	return 1;
}

static void
unbindfids(Mvol* m, Fs* fs)
{
	Fid*	f;

	for (f = m->fids; f ; f = f->mnext){
		if (f->fs == fs)
			f->stale = 1;
	}
}

void
mvolunmount(Mvol* m, Vol* v, Frpc* fop)
{
	int	i;
	int	newi;

	unbindfids(m, v->fs);

	newi = -1;
	for (i = 0; i < m->nvols; i++)
		if (newi >= 0)
			m->vols[newi++] = m->vols[i];
		else if (m->vols[i] == v){
			putvol(v, fop);
			newi = i;
		}
	if (newi >= 0)
		m->nvols--;
}

Vol*
getmvolvol(Mvol* m, int i)
{
	Vol*	v;

	v = nil;
	if (m != nil){
		qlock(m);
		if (i < m->nvols)
			v = m->vols[i];
		qunlock(m);
	}
	return v;
}

int
volmount(Vol* v, Frpc* fop)
{ 
	int	isrecover;
	Fid*	vfid;

	if (v->disabled > 0)
		return 0;

assert(!canqlock(&volslck));

	if (!v->dead && v->fs && v->fs->fd >= 0 && v->fs->epoch <= v->epoch){
		// already mounted.
		return 1;
	}
	isrecover = (v->fs != nil);
	assert(v->slash == nil);
	if (v->fs == nil)
		v->fs = getfs(v->addr, v->spec);
	if (v->fs && v->fs->lat < Badlatency)
		cnstrcat(&v->ccnstr, &netokcnstr);
	else
		cnstrcat(&v->ccnstr, &netbadcnstr);
	vfid = fidalloc(0);
	incref(v->fs);
	vfid->fs = v->fs;
	assert(v->fs->fid);
	if (walkfid(v->fs->fid, vfid, v->sname->elems, v->sname->nelems, fop)<=0){
		v->dead = 1;
		incref(&epoch);
		v->epoch = epoch.ref;
		fidfree(vfid, fop);
		vdprint(2, "vol %s!%s: not there\n", v->addr, v->name);
	} else {
		n_reset(vfid->sname);	// it is "/", by convention.
		v->slash = vfid;
		v->dead = 0;
		if (isrecover)
			incref(&epoch);
		v->epoch = epoch.ref;
		vdprint(2, "vol %s!%s mounted e=%ld fse=%ld\n", v->addr, v->name,
			v->epoch, v->fs->epoch);
	}
	return !v->dead;
}


/* Checks out volumes for death and resurection.
 */
int
checkvols(Frpc* fop)
{
	static long	lepoch;
	int	i;
	Vol*	v;
	int	some;

	if (0 && lepoch == epoch.ref)
		return 0;
	some = 0;
	qlock(&volslck);
	for(i =0; i < nvols; i++){
		v = vols[i];
		// Vols with dead a fs are dead.
		// Same for disabled vols (although their fss are kept)
		if (!v->dead){
			if (diedvol(v)){
				some++;
				vdprint(2, "checkvols: dead vol: "
					"%s %s\n", v->addr, v->name);
				deadvol(v, fop);
			} 
		}
		// Vols with recovered fs are no longer dead
		if (v->dead && !v->disabled){
			if (v->fs && v->fs->fd >= 0){
				some++;
				vdprint(2, "checkvols: came vol: "
					"%s %s\n", v->addr, v->name);
				volmount(v, fop);
			}
		}
	}
	lepoch = epoch.ref;
	qunlock(&volslck);
	return some;
}


/* vols are locked. m is locked
 */
static void
unmountdeadvols(Mvol* m, Frpc* fop)
{
	int	i;
	Vol*	v;

again:
	for (i = 0; i < m->nvols; i++){
		v = m->vols[i];
		if (v->dead || v->epoch > m->epoch){
			vdprint(2, "\t%p: unmounted: %V\n", m, v);
			mvolunmount(m, v, fop);
			if (!m->isunion){
				/* If we loose a vol, and have many ones we
				 * must try any other one that suffices.
				 * This ensures we try all volumes.
				 */
				m->choice = Worst; // Any big number
				m->epoch = 0;
			}
			goto again;
		}
	}
}

/* vols are locked.
 * m is locked
 */
static void
mountnewvols(Mvol* m, Frpc* fop)
{
	int	i;
	int	nr;
	Vol*	v;

	if (!m->isunion && m->nvols > 0 && m->choice == 0){
		// Not a union and already got the preferred one.
		return;
	}
	for(i = 0; i < nvols; i++){
		if (vols[i]->dead || vols[i]->epoch <= m->epoch)
			continue;
		nr = volmatch(vols[i], m);
		if (nr >= 0)
			vdprint(2, "\t%p: match: %s %d\n", m,vols[i]->addr, nr);
		if (nr < 0 || (!m->isunion && nr >= m->choice))
			continue;
		if (hasvol(m, vols[i]))
			continue;
		if (m->nvols)
			v = m->vols[0];
		else
			v = nil;
		if (mvolmount(m, vols[i], fop)){
			vdprint(2, "\t%p: mounted: %V\n", m, vols[i]);
			if (!m->isunion){
				if (v != nil)
					mvolunmount(m, v, fop); // old preferred
				m->choice = nr;
			}
		}
		if (!m->isunion && m->choice == 0)
			break;
	}
}

void
updatemvol(Mvol* m, Frpc* fop)
{
	if (m == nil || m->epoch == epoch.ref)
		return;

	vdprint(2, "update mvol %p %s me=%ld e=%ld\n",
		m, m->name, m->epoch, epoch.ref);
	qlock(&volslck);
	qlock(m);
	unmountdeadvols(m, fop);
	mountnewvols(m, fop);
	m->epoch = epoch.ref;
	qunlock(m);
	qunlock(&volslck);
}

static char*
fstr(char* s)
{
	if (s == nil)
		return "-";
	else
		return s;
}

int
Vfmt(Fmt* f)
{
	Vol*	v;
	char*	addr;
	char*	spec;
	v = va_arg(f->args, Vol*);
	if (!strcmp(v->addr, "#s/boot"))
		addr = "-";
	else
		addr = v->addr;
	if (v->spec == nil || *v->spec == 0)
		spec = "-";
	else
		spec = v->spec;
	return fmtprint(f, "%s\t%s\t%N\t%s\t%k",
			addr, fstr(spec), v->sname, v->name, &v->ccnstr);
}

int
Wfmt(Fmt* f)
{
	Mvol*	v;
	char buf[120];
	char*	s;
	int	i;
	char*	u;

	v = va_arg(f->args, Mvol*);
	if (v == nil)
		return fmtprint(f, "nil");
	s = buf;
	buf[0] = 0;
	s = seprint(s, buf+120, " ");
	for (i = 0; i < v->nvols; i++)
		s= seprint(s, buf+120, "%s!%s ", v->vols[i]->addr, v->vols[i]->name);
	if (v->isunion)
		u = "union";
	else if (v->musthave)
		u = "musthave";
	else
		u = "";
	return fmtprint(f, "%s\t%K\t[%s] %s", v->name, &v->cnstr, buf, u);
}

void
dumpvols(void)
{
	int	i;

	fprint(2, "vols: epoch=%ld\n", epoch.ref);
	qlock(&volslck);
	for (i = 0; i < nvols; i++){
		fprint(2, "%p %V ref=%ld e=%ld fs=%p",
			vols[i], vols[i],
			vols[i]->ref, vols[i]->epoch, vols[i]->fs);
		if (vols[i]->disabled>0 && !vols[i]->dead)
			fprint(2, " dying");
		else if (vols[i]->dead)
			fprint(2, " dead");
		fprint(2, "\n");
		if (vols[i]->slash)
			fprint(2, "\t\tslash=%X\n", vols[i]->slash);
		sleep(300);	// give time to kprint drain queue
	}
	qunlock(&volslck);
	fprint(2, "\n");
}

void
dumpmvols(void)
{
	int	i;
	Fid*	f;

	fprint(2, "mvols:\n");
	qlock(&mvolslck);
	for (i = 0; i < nmvols; i++)
		if (mvols[i]){
			qlock(mvols[i]);
			fprint(2, "   %p %W\te=%ld ref=%ld choice=%d\n",
				mvols[i], mvols[i],
				mvols[i]->epoch, mvols[i]->ref, mvols[i]->choice);
			for (f = mvols[i]->fids; f; f = f->mnext)
				fprint(2, "      %X\n", f);
			qunlock(mvols[i]);
			/* Give time to /dev/kprint so it could make
			 * more room and we see it all
			 */
			sleep(200);
		}
	qunlock(&mvolslck);
			
}


static char*
addrhost(char* addr)
{
	char* na;
	char*	p;

	na = netmkaddr(addr, "tcp", "9p");
	// na is now xxx!xxx!xxx
	na = strchr(na, '!') + 1;
	p = strchr(na, '!');
	*p = 0;
	if (!strcmp(na, "localhost") || !strcmp(na, "*"))
		na = sysname();
	return estrdup(na);
}

/*
 * Volumes are allocated but not freed.
 * A gone volume has v->dead set.
 */
static Vol*
newvol(char* cfname, char* addr, char* spec, char* path, char* name, char* cnstr)
{
	Vol*	v;
	char*	c;
	char*	s;
	char*	elems[16];
	int	nelems, i;
	int	local;

	v = emalloc(sizeof(Vol));
	memset(v, 0, sizeof(Vol));
	v->cfname = estrdup(cfname);
	v->addr = estrdup(addr);
	local = (v->addr[0] == '/' || v->addr[0] == '#');
	if (local)
		v->host = estrdup(sysname());
	else {
		v->host = addrhost(addr);
	}
	local = !strcmp(v->host, sysname());
	if (!strcmp(spec, "-"))
		spec = "";
	v->spec = estrdup(spec);
	s =  estrdup(path);
	nelems = gettokens(s, elems, nelem(elems), "/");
	v->sname = n_new();
	for (i = 0; i < nelems; i++)
		n_append(v->sname, elems[i]);
	free(s);
	v->name = estrdup(name);
	v->cnstr = unquotestrdup(cnstr ? cnstr : "type=dir");
	v->epoch = epoch.ref;
	if (!strstr(v->cnstr, "sys=")){
		c = smprint("%s sys=%s", v->cnstr, v->host);
		assert(c);
		free(v->cnstr);
		v->cnstr = c;
	}
	if (local && !strstr(v->cnstr, "user=")){
		c = smprint("%s user=%s", v->cnstr, getuser());
		assert(c);
		free(v->cnstr);
		v->cnstr = c;
	}
	c = smprint("%s net=ok", v->cnstr);
	assert(c);
	free(v->cnstr);
	v->cnstr = c;
	parsecnstr(v->cnstr, &v->ccnstr);

	/* This counts how many mvols are using it.
	 * none so far.
	 */
	v->ref = 0;
	return v;
}

static void
addvol(char* cfname, char* addr, char* spec, char* path, char* name, char* cnstr)
{
	Vol*	v;
	int	i;

	qlock(&volslck);
	// ignore what we already know
	for (i = 0; i < nvols; i++){
		if (!strcmp(vols[i]->addr, addr) && !strcmp(vols[i]->name, name)){
			if (vols[i]->disabled){
				vols[i]->disabled = 0;
				// BUG: should do this:
				// parsecnstr(ncnstr, &cncnstr);
				// cnstrcat(&vols[i]->ccnstr, &cncnstr);
			}
			goto done;
		}
	}
	incref(&epoch);
	v = newvol(cfname, addr, spec, path, name, cnstr);
	vdprint(2, "new vol %s %s\n", v->addr, v->name);
	if (v != nil){
		if (!(nvols%16))
			vols = erealloc(vols, (nvols+16)*sizeof(Vol*));
		vols[nvols++] = v;
	}
done:
	qunlock(&volslck);
}


/* add/del name
 * add/del name 'cnstr'
 * add/del * 'cnstr'
 * set name 'cnstr' 'new cnstr' (used to update attributes)
 */
static void
volcmd(char* cmd, char* name, char* cnstr, char* ncnstr)
{
	static Cnstr	ccnstr;
	static Cnstr	cncnstr;
	int	i;
	Vol*	v;

	if (!cnstr)
		cnstr = "";
	if (!ncnstr)
		ncnstr = "";
	if (!strcmp(name, "*") && !strcmp(cnstr, "")){
		fprint(2, "%s: won't cmd all vols at once\n", argv0);
		return;
	}
	vdprint(2, "%s %s!%s\n", cmd, name, cnstr);
	parsecnstr(cnstr, &ccnstr);

	qlock(&volslck);
	for (i = 0; i < nvols; i++){
		v = vols[i];
		if (!strcmp(name, "*") || !strcmp(vols[i]->name, name))
		if (cnstrmatch(&v->ccnstr, &ccnstr)){
			if (!strcmp(cmd, "add"))
				vols[i]->disabled--;
			else if (!strcmp(cmd, "del"))
				vols[i]->disabled++;
			else if (!strcmp(cmd, "set")){
				parsecnstr(ncnstr, &cncnstr);
				cnstrcat(&vols[i]->ccnstr, &cncnstr);
			}
			vdprint(2, "%s %V\n", cmd, vols[i]);
			incref(&epoch);
			vols[i]->epoch = epoch.ref;
		}
	}
	qunlock(&volslck);
}

/* add name [ cnstr  ]
 * del name [ cnstr  ]
 * set name cnstr ncnstr
 * config line:
 * 	addr spec path name [cnstr]
 *	addr name [cnstr]
 * defaults:
 *	spec:main/active path:/ cnstr: 'user=$user host=$sysname loc=$location'
 */

void
cmdline(char* ln, char* fname, int lno)
{
	char*	p;
	int	nargs;
	char*	args[10];

	if (ln[0] == '#')
		return;
	p = strchr(ln, '\n');
	if (p)
		*p = 0;
	memset(args, 0, sizeof(args));
	nargs = tokenize(ln, args, nelem(args));
	if (!nargs)
		return;
	if (!strcmp(args[0], "del") || !strcmp(args[0], "add")){
		if (nargs == 2 || nargs == 3){
			volcmd(args[0], args[1], args[2], nil);
			return;
		}
	} else if (!strcmp(args[0], "set")){
		if (nargs == 4){
			volcmd(args[0], args[1], args[2], args[3]);
			return;
		}
	} else if (nargs == 2 || nargs == 3){
		// short config: addr name [cnstr]
		addvol(fname, args[0], "", "/", args[1], args[2]);
		return;
	} else if (nargs == 4 || nargs == 5){
		// long config: addr spec path name [cnstr]
		addvol(fname, args[0], args[1], args[2], args[3], args[4]);
		return;
	}
	fprint(2, "%s:%d: bad number of fields\n", fname, lno);
}

static Biobuf	bcfg;

void
fdconfig(int fd, char* name)
{
	char*	ln;
	int	lno;

	/* BUG: Gone volumes.
	 * when called from discover(), this should look out for
	 * vols not mentioned in the lines obtained below.
	 * Those volumes are gone as far as the adsrv can tell.
	 * For vols known from this adsrv, that are no longer known
	 * by such adsrv, issue a "del" to disable them.
	 * Also, beware that if they are later seen, addvol() must
	 * reuse the old volume, but reset its parameters.
	 */
	Binit(&bcfg, fd, OREAD);
	for(lno = 1; ln = Brdstr(&bcfg, '\n', 1); lno++){
		if (ln[0] == 0)
			break;
		cmdline(ln, name, lno);
		free(ln);
	}
	Bterm(&bcfg);
}

static void
stdconfig(void)
{
	char*	fs;
	char*	spec;
	char*	uspec;
	char	cfg[128];

	fs = getenv("fs");
	if (fs == nil)
		fs = strdup("193.147.71.86");
	spec = getenv("rootspec");
	if (spec == nil)
		spec = strdup("main/active");
	seprint(cfg, cfg+128, "tcp!%s!564	%s	/	/	'sys=fs'", fs, spec);
	cmdline(cfg, "std", 1);
	uspec = getenv("usrspec");
	if (uspec != nil)
		seprint(cfg, cfg+128, "tcp!%s!564	%s	/usr	/usr	'sys=fs'", fs, uspec);
	else
		seprint(cfg, cfg+128, "tcp!%s!564	%s	/usr	/usr	'sys=fs'", fs, spec);
	cmdline(cfg, "std", 2);
	if (access("#s/vfossil", AEXIST) == 0){
		seprint(cfg, cfg+128, "/srv/vfossil	main/active	/	/	'sys=%s'", getenv("sysname"));
		cmdline(cfg, "std", 3);
		seprint(cfg, cfg+128, "/srv/vfossil	main/active	/usr	/usr	'sys=%s'", getenv("sysname"));
		cmdline(cfg, "std", 4);
	}
}

void
config(char* fname)
{
	int	fd;

	if (fname == nil)
		stdconfig();
	else {
		fd = open(fname, OREAD);
		if (fd < 0){
			fprint(2, "%s: %s: %r\n", argv0, fname);
			return;
		}
		fdconfig(fd, fname);
	}
}

Bell Labs OSI certified Powered by Plan 9

(Return to Plan 9 Home Page)

Copyright © 2021 Plan 9 Foundation. All Rights Reserved.
Comments to [email protected].