shithub: mafs

ref: 16f09290e03ec4fba68b6587dc01c13299c2a6f6
dir: /dentry.c/

View raw version
#include	"all.h"

u64
getindblk(u64 blkno, u64 reli, u16 tag, u64 path)
{
	Iobuf *buf;
	u64 n, b;

	if(chatty9p > 2)
	dprint("getindblk blkno %llud reli %llud tag %d path %llud\n",
			blkno, reli, tag, path);
	if(tag < Tind0 || tag > Tmaxind || reli > nperindunit(tag+1)){
		panic("devmafs: getindblock() wrong reli %llud for %s\n",
			reli, tagnames[tag]);
		return 0;
	}
	if(blkno == 0){
		return 0;
	}
	if((buf = getbufchk(blkno, Breadonly, tag, path)) == nil){
		dprint("%s",errstring[Ephase]);
		return 0;
	}
	if(tag > Tind0){
		n = nperindunit(tag);
		b = getindblk(buf->io->bufa[reli/n],
						reli%n, tag-1, path);
	}else{
		b = buf->io->bufa[reli];
	}
	putbuf(buf);
	return b;
}

/*
	get the Dentry at reliddr of d
	also locks all the indirect blocks while walking to get there
	using if's instead of a loop to keep it easy to debug

	get the block number, at the relitive location, reli, of the Dentry d.
	A block number of 0 is the end or beyond the end of the contents.
	A reli of 0 returns the value in d->dblocks[0].
	A reli of Ndblock-1 returns the value in d->dblocks[Ndblock-1].
	A reli of Ndblock returns the value in (blocks[d->iblocks[0]])[0].

	The Iobuf holding the d should be rlock'ed else it creates
	inconsistencies.
 */
u64
rel2abs(Dentry *d, u64 reli)
{
	u8 tag;

	if(reli < Ndblock)
		return d->dblocks[reli];

	tag = rel2tind(reli);
	return getindblk(d->iblocks[tag-Tind0], reli-tagstartreli(tag), tag, d->qid.path);
}

u64
updateindblock(u64 indblkno, u64 reli, u16 tag, u64 path, u64 blkno)
{
	Iobuf *buf;
	u64 n, childindblkno;

	if(tag < Tind0 || tag > Tmaxind || reli > nperindunit(tag+1)){
		panic("devmafs: updateindblock() wrong reli %llud for %s\n", reli, tagnames[tag]);
		return 0;
	}
	if(indblkno == 0){
		if((buf = allocblock(tag, path)) == nil){
			dprint("%s",errstring[Efull]);
			return 0;
		}
		indblkno = buf->blkno;
	}else{
		if((buf = getbufchk(indblkno, Bwritable, tag, path)) == nil){
			dprint("updateindblock: %s",errstring[Ephase]);
			return 0;
		}
		if(buf->blkno != indblkno){
			dprint("updateindblock: buf->blkno != indblkno"
					" buf->blkno %llud indblkno %llud\n",
					buf->blkno, indblkno);
			return 0;
		}
	}
	if(chatty9p > 2)
	dprint("updateindblock indblkno %llud reli %llud tag %s"
			" directblkno %llud nperindunit(tag) %llud\n",
			indblkno, reli, tagnames[tag], blkno, nperindunit(tag));
	if(tag > Tind0){
		n = nperindunit(tag);
		if(reli/n >= Nindperblock){
			panic("updateindblock invalid reli: indblkno %llud reli %llud tag %s"
					" directblkno %llud reli/n %llud nperindunit(tag) %llud\n",
					indblkno, reli, tagnames[tag], blkno, reli/n, Nindperblock);
			dprint("%s",errstring[Ephase]);
			return 0;
		}
		childindblkno = updateindblock(buf->io->bufa[reli/n],
								reli%n, tag-1, path,
								blkno);
		buf->io->bufa[reli/n] = childindblkno;
	}else{
		if(reli >= Nindperblock){
			panic("updateindblock invalid reli: indblkno %llud reli %llud tag %s"
					" directblkno %llud Nindperblock %llud\n",
					indblkno, reli, tagnames[tag], blkno, Nindperblock);
			dprint("%s",errstring[Ephase]);
			return 0;
		}
		buf->io->bufa[reli] = blkno;
	}
	putbuf(buf);
	return indblkno;
}

/*
	store the block number at the relitive location, reli, of the Dentry in dbuf.
	The contents buffer is already stored and we store that from the leaf
	to the root. If there is a crash during this process, there are no
	dangling unfilled slots below.

	dbuf should be wlock'ed.
 */
u64
addrelative(Dentry *d, u64 dblkno, u64 reli, u64 blkno)
{
	u64 path, nblkno;
	u16 tag;

	if(chatty9p > 2)
	dprint("addrelative %llud:%s reli %llud blkno %llud\n",
			dblkno, d->name, reli, blkno);

	path = d->qid.path;
	if(reli < Ndblock){
		d->dblocks[reli] = blkno;
		return blkno;
	}

	tag = rel2tind(reli);
	if(chatty9p > 2)
	dprint("	reli %llud tag %s d->iblocks[tag-Tind0] %llud blkno %llud\n",
			reli, tagnames[tag], d->iblocks[tag-Tind0], blkno);
	if((nblkno = updateindblock(d->iblocks[tag-Tind0], reli-tagstartreli(tag), tag, path, blkno)) == 0){
			dprint("%s",errstring[Ephase]);
			return 0;
		}
	d->iblocks[tag-Tind0] = nblkno;
	return nblkno;
}

/*
	directtag == Tdata for files and Tdentry for directories

	Could copy all the block numbers into memory, release the block
	and work on free'ing each block. But, this does not help
	as this would all be dangling stuff identifiable by the fsck
	and removed by it on a crash.
 */
void
freeindblock(u64 iblkno, u16 tag, u64 qpath, u16 directtag)
{
	Iobuf *ibuf;
	int i;
	u64 blkno;

	if(iblkno == 0)
		return;
	ibuf = getbufchk(iblkno, Bwritable, tag, qpath);
	if(ibuf == nil)
			dprint("%s",errstring[Ephase]);

	if(tag == Tind0)
		for(i=0; i < Nindperblock; i++){
			blkno = ibuf->io->bufa[i];
			if(blkno == 0)
				break;
			freeblock(blkno, directtag, qpath);
			ibuf->io->bufa[i] = 0;
		}
	else
		for(i=0; i < Nindperblock; i++){
			if(ibuf->io->bufa[i] == 0)
				break;
			freeindblock(ibuf->io->bufa[i], tag-1, qpath, directtag);
			ibuf->io->bufa[i] = 0;
		}
	freeblockbuf(ibuf);
}

/* dbuf should be wlock'ed */
void
truncatefilebuf(Iobuf *dbuf, s16 uid)
{
	Dentry *d, d1;
	int i;

	d = dbuf->d;
	memcpy(&d1, d, sizeof(Dentry));
	for(i=0; i<Ndblock; i++)
		d->dblocks[i] = 0;
	for(i=0;i<Niblock; i++)
		d->iblocks[i] = 0;
	d->size = 0;
	d->mtime = nsec();
	d->muid = uid;
	putbuf(dbuf);

	for(i=0; i<Ndblock; i++){
		if(d1.dblocks[i] == 0)
			return;
		freeblock(d1.dblocks[i], Tdata, d1.qid.path);
	}
	for(i=0;i<Niblock; i++){
		if(d1.iblocks[i] == 0)
			return;
		freeindblock(d1.iblocks[i], Tind0+i, d1.qid.path, Tdata);
	}
}

/*
	truncates the file's contents.

	Clear out the dentry data block numbers and store it to disk.
	If there is a crash, during the removal of the free
	blocks, the fsck can remove the dangling blocks
	without worrying about orphaned links in a dentry.
 */
void
truncatefile(u64 qpath, u64 dblkno, s16 uid)
{
	Iobuf *dbuf;

	if(qpath < Qpusers || dblkno == 0)
		return;
	dbuf = getbufchk(dblkno, Bwritable, Tdentry, qpath);
	if(dbuf == nil)
			dprint("%s",errstring[Ephase]);

	truncatefilebuf(dbuf, uid);
}

/*
	Removes the file contents and zero's the dentry

	The block will still be linked in the parent.
	When a file/directory is created in the parent,
	this zero'ed block will be reused for that dentry.

	Following this sequence to avoid being stuck with links
	to removed content on a crash.
	1. zero out the dentry
	2. free the direct and indirect blocks
 */
void
rmfile(u64 qpath, u64 dblkno)
{
	Iobuf *dbuf;
	Dentry d;
	int i;
	u64 size;

	if(qpath < Qpusers || dblkno == 0)
		return;
	dbuf = getbufchk(dblkno, Bwritable, Tdentry, qpath);
	if(dbuf == nil)
			dprint("%s",errstring[Ephase]);
	size = dbuf->d->size;
	memcpy(&d, dbuf->d, sizeof(Dentry));
	memset(dbuf->d, 0, Dentrysize);
	settag(dbuf, Tdentry, Qpnone);
	putbuf(dbuf);

	if(size < Ddatasize)
		return;

	for(i=0; i<Ndblock; i++){
		if(d.dblocks[i] == 0)
			return;
		freeblock(d.dblocks[i], Tdata, qpath);
	}
	for(i=0;i<Niblock; i++){
		if(d.iblocks[i] == 0)
			return;
		freeindblock(d.iblocks[i], Tind0+i, qpath, Tdata);
	}
}

void
rmdirectory(u64 qpath, u64 dblkno)
{
	Dentry d, *child;
	Iobuf *dbuf, *buf;
	u8 ct;
	u64 cqpath;
	u64 blkno, reli;
	int i;
	u16 mode;

	if(qpath < Qpusers || dblkno == 0)
		return;
	/* clear the dentry to avoid links to removed content */
	dbuf = getbufchk(dblkno, Bwritable, Tdentry, qpath);
	if(dbuf == nil)
			dprint("%s",errstring[Ephase]);
	memcpy(&d, dbuf->d, sizeof(Dentry));
	memset(dbuf->d, 0, Dentrysize);
	settag(dbuf, Tdentry, Qpnone);
	putbuf(dbuf);

	/*
		remove the children
		the linked dentries are still linked though zero'ed out
	 */
	for(reli = 0, blkno = 1; blkno > 0; reli++){

		if((blkno = rel2abs(&d, reli)) == 0)
			break;

		buf = getbuf(blkno, Dentryunits, Breadonly, Bused);
		child = buf->d;
		cqpath = child->qid.path;
		mode = child->mode;
		ct = buf->d->tag;
		putbuf(buf);

		/* nothing to do for already zero'ed out slots */
		if(cqpath == Qpnone || ct == Tnone)
			continue;

		if(mode & DMDIR)
			rmdirectory(cqpath, blkno);
		else
			rmfile(cqpath, blkno);
	}

	/*
		free the chain of blocks to zero'ed dentries and
		the zero'ed dentries
	 */
	for(i=0; i<Ndblock; i++){
		if(d.dblocks[i] == 0)
			return;
		freeblock(d.dblocks[i], Tdentry, Qpnone);
	}
	for(i=0;i<Niblock; i++){
		if(d.iblocks[i] == 0)
			return;
		freeindblock(d.iblocks[i], Tind0+i, qpath, Tdentry);
	}
}

/* the same as iaccess() of cwfs */
int
canaccess(s16 uid, Dentry *d, u32 mode)
{
	/* for not "none" users, check user or group permissions */
	if(uid != None){
		/* owner */
		if(uid == d->uid)
			if((mode<<6) & d->mode)
				return 1;
		/* group membership */
		if(ingroup(uid, d->gid, 0))
			if((mode<<3) & d->mode)
				return 1;
	}

	/*
		check other permissions for:
		1. none users
		2. for users that are not the owner or in the group
	 */

	/* from fs(4) and cwfs(4)
          The group numbered 9999, normally called noworld, is special
          on the file server.  Any user belonging to that group has
          attenuated access privileges.  Specifically, when checking
          such a user's access to files, the file's permission bits
          are first ANDed with 0770 for normal files or 0771 for
          directories.  The effect is to deny world access permissions
          to noworld users, except when walking directories.
	*/
	/* noworld users only have access to walk directories */
	if(ingroup(uid, Noworld, 0))
		if((d->mode & DMDIR) && mode == DMEXEC)
			return 1;
		else
			return 0;

	/* checking other access */
	if(d->mode & mode)
		return 1;

	/* not doing the du read access or allowing god that cwfs does for now */

	return 0;
}

/* relative block numbers and the index are the same as we have 1 direntry per block */
Dentry *
searchdir(u64 dblkno, u64 qpath, u16 uid, char *searchname, u64 searchidx, Iobuf **dbuf, Iobuf **buf)
{
	u64 reli, idx;
	Dentry *d, *ch;
	u64 blkno;

	*dbuf = getbufchk(dblkno, Breadonly, Tdentry, qpath);
	if(*dbuf == nil)
		return nil;
	d = (*dbuf)->d;

	if(canaccess(uid, d, DMEXEC) == 0){
		putbuf(*dbuf);
		*dbuf = nil;
		dprint("%s",errstring[Ephase]);
		return nil;
	}

	/* using idx to not include zero'ed out slots in our search */
	for(reli = 0, idx = 0; ; reli++){

		
		if((blkno = rel2abs(d, reli)) == 0){
			putbuf(*dbuf);
			*dbuf = nil;
			return nil;
		}
		if(chatty9p>2)
			dprint("searchdir reli %d s.blkno %llud s.len %d\n",
					reli, blkno);

		*buf = getbuf(blkno, Dentryunits, Breadonly, Bused);
		if(*buf == nil){
			putbuf(*dbuf);
			*dbuf = nil;
			dprint("%s",errstring[Ephase]);
			return nil;
		}

		ch = (*buf)->d;
		if(chatty9p > 2)
			dprint("searchdir: dblkno %llud qpath %llud searchname %s searchidx %d"
					" reli %llud blkno %llud ch->qid.path %llud\n",
					dblkno, qpath, searchname, searchidx, reli, blkno,
					ch->qid.path);
		if(checktag(*buf, Tdentry, ch->qid.path) == 0){
			putbuf(*buf);
			putbuf(*dbuf);
			*dbuf = *buf = nil;
			dprint("%s",errstring[Ephase]);
			return nil;
		}

		/* nothing to do for already zero'ed out slots */
		if(ch->tag == Tdentry && ch->path == Qpnone)
			goto Nextdentry;

		if(searchname != nil){
			if(strcmp(searchname, (char*)ch->name) == 0){
				if(chatty9p > 2)
				dprint("searchdir: found name %s\n", searchname);
				return ch;
			}
		}else if(idx == searchidx){
			if(chatty9p > 2)
			dprint("searchdir: found index %d\n", searchidx);
			return ch;
		}

		idx++; /* so that zero'ed slots do not match */
Nextdentry:
		putbuf(*buf);
	}
	/* should never be here */
}
void
freesearchstate(Iobuf **dbuf, Iobuf **buf)
{
	putbuf(*buf);
	putbuf(*dbuf);
	*dbuf = *buf = nil;
}

/*
	get the contents in the reli'th block number of the Dir, d.
	The buffer holding the d should be locked by the caller.
	only for file contents. not for directory contents.
 */
Iobuf *
getdatablkat(Dentry *d, u64 reli, int flags)
{
	Iobuf *buf;
	u64 blkno;

	/* rel2abs() rlock's while reading.
	   not necessary, as the dbuf is locked anyway.
	 */
	if((blkno = rel2abs(d, reli)) == 0)
		return nil;
	buf = getbufchk(blkno, flags, Tdata, d->qid.path);
	if(buf == nil){
		putbuf(buf);
		dprint("%s",errstring[Ephase]);
		return nil;
	}
	return buf;
}

/* the frees list of free blocks will include the blocks
	used to store the frees */
void
savefrees(u64 dblkno)
{
	s32 nbuf;
	s8 *buf;

	/* should not be necessary as we clear out the file
		in loadfrees() */
	truncatefile(Qpfrees, dblkno, -1);

	/* will not be accurate as we are allocating blocks below.
		But, will be more than what we will actually end up using.
	 */
	nbuf = sizeofextents(&frees)+1;
	buf = emalloc(nbuf);

	/* get the extents into buf */
	if(saveextents(&frees, buf, nbuf) == -1)
		panic("savefrees nbuf not enough");

	/* writing the actual extents now */
	writefile(dblkno, Qpfrees, -1, buf, nbuf, 0, nil);
	free(buf);
}

void
loadfrees(u64 dblkno)
{
	u64 size, n;
	s8 *buf;
	Iobuf *dbuf;
	Dentry *d;
	int i;

	size = readfilesize(dblkno, Qpfrees);
	if(size == 0)
		panic("loadfrees size == 0");
	buf = emalloc(size);
	if(buf == nil)
		panic("loadfrees: nil emalloc of %llud bytes", size);
	if(readfile(dblkno, Qpfrees, buf, size, 0, nil) != size)
		panic("loadfrees: could not load frees");
	n = loadextents(&frees, buf, size);
	if(chatty9p > 2)
	dprint("loadfrees: loaded free extents %llud\n", n);
	free(buf);

	/* clear out /adm/frees data contents */
	/* I cannot use truncatefile() below as it would
		bfree() the blocks (which are already in the Extents frees)
		and that would cause an inconsistency/panic */
	dbuf = getbufchk(dblkno, Bwritable, Tdentry, Qpfrees);
	if(dbuf == nil)
			dprint("%s",errstring[Ephase]);
	d = dbuf->d;
	d->size = 0;
	for(i=0; i<Ndblock; i++)
		d->dblocks[i] = 0;
	for(i=0;i<Niblock; i++)
		d->iblocks[i] = 0;
	d->mtime = nsec();
	d->muid = -1;
	putbuf(dbuf);
}