code: mafs

ref: 390e726c64ee2aca967617ac7f7fd2a44f27646b
dir: /dentry.c/

View raw version
#include	"all.h"

/* b shoud be wlock'ed */
void
flush(Iobuf *b)
{
	u8 dowrite = 0;
	Iobuf *oldbuf;

	if(b == nil ||
		b->xiobuf == nil ||
		b->xiobuf[0] != Tdentry)
		panic("flush called on buf with tag %s\n", tagnames[b->tag]);
	oldbuf = nil;
	if(b->append != nil){
		if(b->appendsize > 0){
			writeallappend(b, b->blkno, &oldbuf);
			dowrite = 1;
		}
		freememunits(b->append, Maxdatablockunits);
		b->append = nil;
	}
	putbuf(b, dowrite);	/* to wunlock b */
	if(oldbuf)
		freeblockbuf(oldbuf);
}

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;
	}
	buf = egetmetachk(blkno, Breadonly, tag, path);
	if(tag > Tind0){
		n = nperindunit(tag);
		b = getindblk(buf->i->bufa[reli/n],
						reli%n, tag-1, path);
	}else{
		b = buf->i->bufa[reli];
	}
	putbuf(buf, 0);
	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->path);
}

u64
updateindblock(u64 dblkno, 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){
		buf = allocmeta(tag, path);
		buf->i->dblkno = dblkno;
		indblkno = buf->blkno;
	}else{
		buf = egetmetachk(indblkno, Bwritable, tag, path);
		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(dblkno, buf->i->bufa[reli/n],
								reli%n, tag-1, path,
								blkno);
		buf->i->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->i->bufa[reli] = blkno;
	}
	putbuf(buf, 1);
	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;
	u8 tag;

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

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

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

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

	Could copy all the block numbers into memory, ufree 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.
 */
u64
freeindblock(u64 iblkno, u16 tag, u64 qpath, u16 directtag, u64 reli, u64 lastreli, u64 lastunits)
{
	Iobuf *ibuf;
	int i;
	u64 blkno;

	if(iblkno == 0)
		return reli;
	ibuf = egetmetachk(iblkno, Bwritable, tag, qpath);
	if(waserror()){
		putbuf(ibuf, 0);
		nexterror();
	}

	if(tag == Tind0)
		for(i=0; i < Nindperblock; i++){
			blkno = ibuf->i->bufa[i];
			if(blkno == 0)
				break;
			if(directtag == Tdentry)
				freeblocks(blkno, 1, directtag, qpath);
			else if(reli < lastreli)
				freeblocks(blkno, Maxdatablockunits, directtag, qpath);
			else
				freeblocks(blkno, lastunits, directtag, qpath);
			ibuf->i->bufa[i] = 0;
			reli++;
		}
	else
		for(i=0; i < Nindperblock; i++){
			if(ibuf->i->bufa[i] == 0)
				break;
			reli = freeindblock(ibuf->i->bufa[i], tag-1, qpath, directtag,
								reli, lastreli, lastunits);
			ibuf->i->bufa[i] = 0;
		}
	poperror();
	freeblockbuf(ibuf);
	return reli;
}

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

	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, 1);

	if(d1.size <= Ddatasize)
		return;

	lastreli = d1.size/Maxdatablocksize;
	lastblks = nlastdatablocks(d1.size);
	for(i=0; i<Ndblock; i++){
		if(d1.dblocks[i] == 0)
			return;
		if(i < lastreli)
			freeblocks(d1.dblocks[i], Maxdatablockunits, Tdata, d1.path);
		else
			freeblocks(d1.dblocks[i], lastblks, Tdata, d1.path);
	}
	for(i=0, reli=Ndblock;i<Niblock; i++){
		if(d1.iblocks[i] == 0)
			return;
		reli = freeindblock(d1.iblocks[i], Tind0+i, d1.path, Tdata,
						reli, lastreli, lastblks);
	}
}

/*
	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 = egetmetachk(dblkno, Bwritable, Tdentry, qpath);

	truncatefilebuf(dbuf, uid);
}

/* name is removed by the caller */
/*
	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, reli, lastreli, lastblks;

	if(qpath < Qpusers || dblkno == 0)
		return;
	dbuf = egetmetachk(dblkno, Bwritable, Tdentry, qpath);
	size = dbuf->d->size;
	memcpy(&d, dbuf->d, sizeof(Dentry));
	memset(dbuf->d, 0, Blocksize);
	settag(dbuf, Tdentry, Qpnone);
	if(dbuf->append){ /* data not yet written */
		freememunits((u8*)dbuf->append, Maxdatablockunits);
		dbuf->append = nil;
		dbuf->appendsize = 0;
	}
	putbuf(dbuf, 1);

	if(size <= Ddatasize)
		return;

	lastreli = d.size/Maxdatablocksize;
	lastblks = nlastdatablocks(d.size);
	for(i=0; i<Ndblock; i++){
		if(d.dblocks[i] == 0)
			return;
		if(i < lastreli)
			freeblocks(d.dblocks[i], Maxdatablockunits, Tdata, qpath);
		else
			freeblocks(d.dblocks[i], lastblks, Tdata, qpath);
	}
	for(i=0, reli=Ndblock;i<Niblock; i++){
		if(d.iblocks[i] == 0)
			return;
		reli = freeindblock(d.iblocks[i], Tind0+i, qpath, Tdata,
							reli, lastreli, lastblks);
	}
}

/* name is removed by the caller */
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 = egetmetachk(dblkno, Bwritable, Tdentry, qpath);
	memcpy(&d, dbuf->d, sizeof(Dentry));
	memset(dbuf->d, 0, Blocksize);
	settag(dbuf, Tdentry, Qpnone);

	putbuf(dbuf, 1);

	/*
		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 = egetmeta(blkno, Breadonly, Bused);
		child = buf->d;
		cqpath = child->path;
		mode = child->mode;
		ct = child->tag;
		putbuf(buf, 0);

		/* 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;
		freeblocks(d.dblocks[i], 1, Tdentry, Qpnone);
	}
	for(i=0, reli=Ndblock;i<Niblock; i++){
		if(d.iblocks[i] == 0)
			return;
		reli = freeindblock(d.iblocks[i], Tind0+i, qpath, Tdentry,
							reli, 0, 1);
	}
}

/* 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;
}

/*
	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)
{
	u64 blkno;

	/* rel2abs() rlock's while reading.
	   not necessary, as the dbuf is locked anyway.
	 */
	if((blkno = rel2abs(d, reli)) == 0)
		return nil;

	if(reli < d->size/Maxdatablocksize)
		return egetbufchk(blkno, Maxdatablockunits, Breadonly, Tdata, d->path, getcallerpc(&d));
	else
		return egetbufchk(blkno, nlastdatablocks(d->size), Breadonly, Tdata, d->path, getcallerpc(&d));
}

void
saveextentstofile(u64 blkno, u64 qpath, u16 uid, Extents *es)
{
	s32 nbuf;
	s8 *buf;

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

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

	/* writing the actual extents now */
	truncatefile(qpath, blkno, uid);
	writefile(blkno, qpath, uid, buf, nbuf, 0);
	free(buf);
}

/* the frees list of free blocks will include the blocks
	used to store the frees */
void
savefrees(void)
{
	Iobuf *dbuf, *oldbuf;

	/* should not be necessary as we clear out the file
		in loadfrees() */
	clearfrees();

	saveextentstofile(Bdfrees, Qpfrees, -1, &frees);

	/* flush to the disk if append has stuff */
	dbuf = egetmetachk(Bdfrees, Bwritable, Tdentry, Qpfrees);
	if(dbuf->append == nil)
		putbuf(dbuf, 0);
	else{
		oldbuf = nil;
		writeallappend(dbuf, Bdfrees, &oldbuf);
		putbuf(dbuf, 1);
		/* if(oldbuf) not needed as the old blocks would already be free blocks
			freeblockbuf(oldbuf); */
	}
}

u64
loadextentsfile(u64 blkno, u64 qpath, Extents *es)
{
	u64 size;
	s8 *buf;

	size = readfilesize(blkno, qpath);
	if(size == 0)
		return size;

	buf = emalloc9p(size);
	if(buf == nil)
		error("loadfrees: nil emalloc of %llud bytes", size);
	if(readfile(blkno, qpath, buf, size, 0) != size)
		error("loadextentsfile: could not load extents");
	loadextents(es, buf, size);
	free(buf);
	return size;
}

void
loadfrees(void)
{
	u64 size;

	size = loadextentsfile(Bdfrees, Qpfrees, &frees);
	if(size == 0)
		panic("There are no free blocks.\n"
				"If there was an unsafe shutdown,"
				" use \'disk/fsck %s\' to correct the disk state\n", devfile);
}

/* clear out /a/frees data contents after starting up
	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 */
void
clearfrees(void)
{
	Iobuf *dbuf;
	Dentry *d;

	dbuf = egetmetachk(Bdfrees, Bwritable, Tdentry, Qpfrees);
	d = dbuf->d;
	d->size = 0;
	memset(d->buf, 0, Ddatasize);
	d->mtime = nsec();
	d->muid = -1;
	putbuf(dbuf, 1);
}