#include "spadfs.h"
#include "dir.h"

void REALLY_DO_FNODE_BLOCK_CHECKSUM(struct fnode_block *fnode_block)
{
	fnode_block->flags |= FNODE_BLOCK_CHECKSUM_VALID;
	fnode_block->checksum ^= CHECKSUM_BASE ^ __byte_sum(fnode_block, FNODE_BLOCK_SIZE);
}

static __finline__ int SPADFS_ACCESS_DNODE(SPADFS *fs, __d_off secno, unsigned version, unsigned i, __d_off *result, int wr)
{
	struct buffer_head *bh;
	char *data;
	i = (i * DNODE_PAGE_ENTRY_SIZE) + DNODE_ENTRY_OFFSET;
	if (version) i += fs->dnode_data_size;
	data = SPADFS_READ_SECTOR(fs, secno + (i >> 9), &bh, "SPADFS_ACCESS_DNODE 1");
	if (__unlikely(IS_ERR(data))) return PTR_ERR(data);
	data += i & 511;
	if (__likely((i & 511) <= 512 - DNODE_PAGE_ENTRY_SIZE)) {
		if (!wr) {
			*result = MAKE_D_OFF3(((struct dnode_page_entry *)data)->b0, ((struct dnode_page_entry *)data)->b1, ((struct dnode_page_entry *)data)->b2);
		} else {
			((struct dnode_page_entry *)data)->b0 = MAKE_PART_30(*result);
			((struct dnode_page_entry *)data)->b1 = MAKE_PART_31(*result);
			((struct dnode_page_entry *)data)->b2 = MAKE_PART_32(*result);
		}
	} else {
		struct dnode_page_entry dp;
		if (!wr) {
			memcpy(&dp, data, 512 - (i & 511));
		} else {
			dp.b0 = MAKE_PART_30(*result);
			dp.b1 = MAKE_PART_31(*result);
			dp.b2 = MAKE_PART_32(*result);
			memcpy(data, &dp, 512 - (i & 511));
			mark_buffer_dirty(bh);
		}
		__brelse(bh);
		data = SPADFS_READ_SECTOR(fs, secno + (i >> 9) + 1, &bh, "SPADFS_ACCESS_DNODE 2");
		if (__unlikely(IS_ERR(data))) return PTR_ERR(data);
		if (!wr) {
			memcpy((char *)&dp + (512 - (i & 511)), data, DNODE_PAGE_ENTRY_SIZE - (512 - (i & 511)));
			*result = MAKE_D_OFF3(dp.b0, dp.b1, dp.b2);
		} else {
			memcpy(data, (char *)&dp + (512 - (i & 511)), DNODE_PAGE_ENTRY_SIZE - (512 - (i & 511)));
		}
	}
	if (wr) mark_buffer_dirty(bh);
	__brelse(bh);
	return 0;
}

static int SPADFS_READ_DNODE(SPADFS *fs, __d_off secno, unsigned version, unsigned i, __d_off *result)
{
	return SPADFS_ACCESS_DNODE(fs, secno, version, i, result, 0);
}

static int SPADFS_WRITE_DNODE(SPADFS *fs, __d_off secno, unsigned version, unsigned i, __d_off result)
{
	return SPADFS_ACCESS_DNODE(fs, secno, version, i, &result, 1);
}

static __finline__ int DNODE_VERSION(SPADFS *fs, struct dnode_page *d)
{
	return !CC_VALID(fs, d->cc, d->txc);
}

static int SPADFS_BEGIN_MODIFY_DNODE(SPADFS *fs, __d_off dno)
{
	int version;
	struct buffer_head *bh;
	struct dnode_page *d;
	d = (struct dnode_page *)SPADFS_READ_FNODE_BLOCK(fs, dno, &bh, SRFB_DNODE, "SPADFS_BEGIN_MODIFY_DNODE 1");
	if (__unlikely(IS_ERR(d))) return PTR_ERR(d);
	version = DNODE_VERSION(fs, d);
	if (CC_CURRENT(fs, d->cc, d->txc)) {
		d->flags[version] &= ~DNODE_CHECKSUM_VALID;
		mark_buffer_dirty(bh);
		__brelse(bh);
		return version;
	} else {
		unsigned from, to;
		char *datafrom, *datato;
		struct buffer_head *bhfrom, *bhto;
		unsigned remaining, len;
		start_atomic_buffer_modify(bh);
		d->flags[version ^ 1] &= ~DNODE_CHECKSUM_VALID;
		CC_SET_CURRENT(fs, &d->cc, &d->txc);
		end_atomic_buffer_modify(bh);
		__brelse(bh);
		from = DNODE_ENTRY_OFFSET + version * fs->dnode_data_size;
		to = DNODE_ENTRY_OFFSET + (version ^ 1) * fs->dnode_data_size;
		remaining = fs->dnode_data_size;
		datafrom = SPADFS_READ_SECTOR(fs, dno + (from >> 9), &bhfrom, "SPADFS_BEGIN_MODIFY_DNODE 2");
		if (__unlikely(IS_ERR(datafrom))) {
			return PTR_ERR(datafrom);
		}
		datafrom += from & 511;
		reread_to:
		datato = SPADFS_READ_SECTOR(fs, dno + (to >> 9), &bhto, "SPADFS_BEGIN_MODIFY_DNODE 3");
		if (__unlikely(IS_ERR(datato))) {
			__brelse(bhfrom);
			return PTR_ERR(datato);
		}
		datato += to & 511;
		copy_again:
		len = 512 - (from & 511);
		if (512 - (to & 511) < len) len = 512 - (to & 511);
		if (__unlikely(remaining < len)) len = remaining;
		memcpy(datato, datafrom, len);
		datafrom += len;
		datato += len;
		from += len;
		to += len;
		remaining -= len;
		if (remaining) {
			if (!(from & 511)) {
				__brelse(bhfrom);
				datafrom = SPADFS_READ_SECTOR(fs, dno + (from >> 9), &bhfrom, "SPADFS_BEGIN_MODIFY_DNODE 4");
				if (__unlikely(IS_ERR(datafrom))) {
					__brelse(bhto);
					return PTR_ERR(datafrom);
				}
				datafrom += from & 511;
			}
			if (!(to & 511)) {
				mark_buffer_dirty(bhto);
				__brelse(bhto);
				goto reread_to;
			}
			if (__unlikely(from & 511)) panic("I am very lame");
			goto copy_again;
		}
		__brelse(bhfrom);
		mark_buffer_dirty(bhto);
		__brelse(bhto);
		return version ^ 1;
	}
}

int SPADFS_ALLOC_FIXED_FNODE_BLOCK(SPADFNODE *fn, __d_off hint, unsigned size, __u16 hint_small, __u16 hint_big, __d_off *result)
{
	SPADFS *fs = fn->fs;
	int r;
	struct fixed_fnode_block *fx;
	struct alloc al;
	struct buffer_head *bh;
	al.sector = hint;
	al.n_sectors = 1 << fs->sectors_per_block_bits;
	al.flags = ALLOC_METADATA;
	r = SPADFS_ALLOC_BLOCKS(fn, &al);
	if (__unlikely(r)) return r;
	*result = al.sector;
	fx = SPADFS_GET_NEW_SECTOR(fs, al.sector, &bh, "SPADFS_ALLOC_FIXED_FNODE_BLOCK");
	if (__unlikely(IS_ERR(fx))) return PTR_ERR(fx);
	memset(fx, 0, FIXED_FNODE_BLOCK_SIZE);
	fx->magic = FIXED_FNODE_BLOCK_MAGIC;
	/*fx->flags = 0;*/
	/* doesn't matter because this block won't be used in case of crash.
	   set to current (cc, txc) to prevent copy on next update. */
	fx->cc = fs->cc;
	fx->txc = fs->txc;
	fx->hint_small = hint_small;
	fx->hint_large = hint_big;
	fx->nlink0 = 1;
#define fnode	((struct fnode *)fx->fnode0)
	fnode->next = size;
#undef fnode
	DO_FNODE_BLOCK_CHECKSUM(fs, (struct fnode_block *)fx);
	mark_buffer_dirty(bh);
	__brelse(bh);
	return 0;
}

int SPADFS_ALLOC_LEAF_PAGE(SPADFNODE *fn, __d_off hint, unsigned sectors, __d_off prev, __d_off *result)
{
	SPADFS *fs = fn->fs;
	int r;
	unsigned i, j;
	struct alloc al;
	al.sector = hint;
	al.n_sectors = sectors;
	al.flags = ALLOC_METADATA;
	r = SPADFS_ALLOC_BLOCKS(fn, &al);
	if (__unlikely(r)) return r;
	*result = al.sector;
	for (i = 0; i < sectors; i += 1 << fs->sectors_per_block_bits) {
		struct buffer_head *bh;
		struct fnode_block *b = SPADFS_GET_NEW_SECTOR(fs, al.sector + i, &bh, "SPADFS_ALLOC_LEAF_PAGE");
		if (__unlikely(IS_ERR(b))) return PTR_ERR(b);
		for (j = 0; j < 1 << fs->sectors_per_block_bits; j++, b = (struct fnode_block *)((char *)b + FNODE_BLOCK_SIZE)) {
			struct fnode *fn;
			memset(b, 0, FNODE_BLOCK_SIZE);
			if (!i && !j) {
				b->prev0 = MAKE_PART_0(prev);
				b->prev1 = MAKE_PART_1(prev);
			}
			b->magic = FNODE_BLOCK_MAGIC;
			b->flags = (FNODE_BLOCK_FIRST * (!i && !j)) | (FNODE_BLOCK_LAST * (i + j == sectors - 1));
			/*b->prev0 = b->prev1 = 0;*/
			b->txc = TXC_INVALID(0);
			/*b->cc = 0;*/
			fn = b->fnodes;
			fn->next = (FNODE_MAX_SIZE & FNODE_NEXT_SIZE) | FNODE_NEXT_FREE;
			/*fn->cc = 0;
			fn->txc = 0;*/
			DO_FNODE_BLOCK_CHECKSUM(fs, b);
		}
		mark_buffer_dirty(bh);
		__brelse(bh);
	}
	return 0;
}

static int SPADFS_ALLOC_DNODE_PAGE(SPADFNODE *fn, __d_off hint, __d_off *result, __d_off parent, __d_off ptr_init)
{
	SPADFS *fs = fn->fs;
	int r;
	unsigned i;
	struct alloc al;
	al.sector = hint;
	al.n_sectors = fs->dnode_page_sectors;
	al.flags = ALLOC_METADATA;
	r = SPADFS_ALLOC_BLOCKS(fn, &al);
	if (__unlikely(r)) return r;
	*result = al.sector;
	for (i = 0; i < fs->dnode_page_sectors; i += 1 << fs->sectors_per_block_bits) {
		struct buffer_head *bh;
		struct dnode_page *d = SPADFS_GET_NEW_SECTOR(fs, al.sector + i, &bh, "SPADFS_ALLOC_DNODE_PAGE");
		if (__unlikely(IS_ERR(d))) return PTR_ERR(d);
		memset(d, 0, fs->blocksize);
		if (!i) {
			d->magic = DNODE_PAGE_MAGIC;
			d->gflags = (fs->sectors_per_page_bits - 1) << DNODE_GFLAGS_PAGE_SIZE_BITS_MINUS_1_SHIFT;;
			d->up_dnode = parent;
			d->txc = fs->txc;
			d->cc = fs->cc;
		}
		mark_buffer_dirty(bh);
		__brelse(bh);
	}
	for (i = 0; i < 1 << fs->dnode_hash_bits; i++) {
		r = SPADFS_WRITE_DNODE(fs, al.sector, 0, i, ptr_init);
		if (__unlikely(r)) return r;
	}
	return 0;
}

struct fnode_block *SPADFS_FIND_HASH_BLOCK(SPADFNODE *f, hash_t hash, struct buffer_head **bh, __d_off *secno, hash_t *next_hash)
{
	__d_off old_secno;
	unsigned hash_bits;
	unsigned hpos;
	unsigned version;
	struct fnode_block *fnode_block;
	int r;
	__d_off c[2];
	c[1] = 0;
	*secno = f->root;
	hash_bits = 0;

	if (__unlikely(next_hash != NULL)) *next_hash = 0;
	again:
	if (__unlikely(SPADFS_STOP_CYCLES(f->fs, *secno, &c, "SPADFS_FIND_HASH_BLOCK"))) return ERR_PTR(-EFSERROR);
	fnode_block = SPADFS_READ_FNODE_BLOCK(f->fs, *secno, bh, SRFB_FNODE | SRFB_DNODE, "SPADFS_FIND_HASH_BLOCK");
	if (__unlikely(IS_ERR(fnode_block))) return fnode_block;
	if (__likely(fnode_block->magic == FNODE_BLOCK_MAGIC)) return fnode_block;

	version = DNODE_VERSION(f->fs, (struct dnode_page *)fnode_block);
	__brelse(*bh);
	if (__unlikely(hash_bits >= HASH_BITS)) {
		SPADFS_ERROR(f->fs, TXFLAGS_FS_ERROR, "TOO DEEP DNODE TREE, STARTED AT %LX, LOOKED UP HASH %08LX", (unsigned long long)f->root, (unsigned long long)hash);
		return ERR_PTR(-EFSERROR);
	}
	hpos = (hash >> hash_bits) & ((1 << f->fs->dnode_hash_bits) - 1);
	old_secno = *secno;
	r = SPADFS_READ_DNODE(f->fs, *secno, version, hpos, secno);
	if (__unlikely(r)) return ERR_PTR(r);
	if (__unlikely(next_hash != NULL)) {
		int i;
		for (i = f->fs->dnode_hash_bits - 1; i >= 0; i--) if (!(hpos & (1 << i))) {
			__d_off test_secno;
			r = SPADFS_READ_DNODE(f->fs, old_secno, version, (hpos & ((1 << i) - 1)) | (1 << i), &test_secno);
			if (__unlikely(r)) return ERR_PTR(r);
			if (__unlikely(!test_secno) || test_secno != *secno) {
				*next_hash = (hash & (((hash_t)1 << (hash_bits + i)) - 1)) | (hash_t)1 << (hash_bits + i);
				break;
			}
		}
	}
	hash_bits += f->fs->dnode_hash_bits;
	if (__unlikely(!*secno)) return NULL;
	goto again;
}

/* directory entry sector/position is output in ino
   if for_delete is set, ino is input argument too (pointer to fixed fnode
	whose entry to delete) --- the reason for it is that during rename
	there are temporarily two entries with the same name in the directory.
*/

int SPADFS_LOOKUP_INO(SPADFNODE *f, struct qstr *qstr, spadfs_ino_t *ino, int for_delete)
{
	struct buffer_head *bh;
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	__d_off secno;
	unsigned size, namelen;
	__d_off c[2];
	c[1] = 0;
	fnode_block = SPADFS_FIND_HASH_BLOCK(f, name_len_hash(qstr->name, qstr->len), &bh, &secno, NULL);
	if (__unlikely(!fnode_block)) return 1;
	next_fnode_block:
	if (__unlikely(IS_ERR(fnode_block))) return PTR_ERR(fnode_block);
	fnode = fnode_block->fnodes;
	next_fnode:
	size = fnode->next & FNODE_NEXT_SIZE;
	namelen = fnode->namelen;
	VALIDATE_FNODE(f->fs, fnode_block, fnode, size, namelen, ok, skip, bad_fnode);
	ok:
	if (!SPADFS_COMPARE_NAMES(f->fs, qstr->name, qstr->len, FNODE_NAME(fnode), namelen)) {
		if (__unlikely(for_delete)) {
			if (!(fnode->flags & FNODE_FLAGS_HARDLINK)) goto skip;
			if (__unlikely(*ino != make_fixed_spadfs_ino_t(MAKE_D_OFF(fnode->anode0, fnode->anode1)))) goto skip;
			goto set_ino_to_entry;
		}
		if (__likely(!(fnode->flags & FNODE_FLAGS_HARDLINK))) {
			set_ino_to_entry:
			*ino = make_spadfs_ino_t(secno, (char *)fnode - (char *)fnode_block);
		} else {
			*ino = make_fixed_spadfs_ino_t(MAKE_D_OFF(fnode->anode0, fnode->anode1));
		}
		__brelse(bh);
		return 0;
	}
	skip:
	fnode = (struct fnode *)((char *)fnode + size);
	if (__likely(((unsigned long)fnode & (FNODE_BLOCK_SIZE - 1)) != 0)) goto next_fnode;
	if (!(fnode_block->flags & FNODE_BLOCK_LAST)) {
		__brelse(bh);
		secno++;
		read_next_fnode_block:
		if (__unlikely(SPADFS_STOP_CYCLES(f->fs, secno, &c, "SPADFS_LOOKUP_INO"))) return -EFSERROR;
		fnode_block = SPADFS_READ_FNODE_BLOCK(f->fs, secno, &bh, SRFB_FNODE, "SPADFS_LOOKUP_INO");
		goto next_fnode_block;
	}
	if (__unlikely(CC_VALID(f->fs, fnode_block->cc, fnode_block->txc))) {
		secno = MAKE_D_OFF(fnode_block->next0, fnode_block->next1);
		__brelse(bh);
		goto read_next_fnode_block;
	}
	__brelse(bh);
	return 1;

	bad_fnode:
	__brelse(bh);
	SPADFS_ERROR(f->fs, TXFLAGS_FS_ERROR, "LOOKUP: BAD FNODE ON BLOCK %LX", (unsigned long long)secno);
	return -EFSERROR;
}

int SPADFS_CHECK_DIR_EMPTY(SPADFNODE *f)
{
	struct fnode_block *fnode_block;
	struct buffer_head *bh;
	__d_off secno;
	struct fnode *fnode;
	unsigned size, namelen;
	hash_t hash = 0;
	hash_t next_hash;
	__d_off c[2];
	c[1] = 0;
	new_hash_lookup:
	fnode_block = SPADFS_FIND_HASH_BLOCK(f, hash, &bh, &secno, &next_hash);
	if (__unlikely(!fnode_block)) goto use_next_hash;
	next_fnode_block:
	if (__unlikely(IS_ERR(fnode_block))) return PTR_ERR(fnode_block);
	fnode = fnode_block->fnodes;
	next_fnode:
	size = fnode->next & FNODE_NEXT_SIZE;
	namelen = fnode->namelen;
	VALIDATE_FNODE(f->fs, fnode_block, fnode, size, namelen, ok, skip, bad_fnode);
	ok:
	__brelse(bh);
	return -ENOTEMPTY;
	skip:
	fnode = (struct fnode *)((char *)fnode + size);
	if (__likely(((unsigned long)fnode & (FNODE_BLOCK_SIZE - 1)) != 0)) goto next_fnode;
	if (!(fnode_block->flags & FNODE_BLOCK_LAST)) {
		__brelse(bh);
		secno++;
		read_next_fnode_block:
		if (__unlikely(SPADFS_STOP_CYCLES(f->fs, secno, &c, "SPADFS_CHECK_DIR_EMPTY"))) {
			return -EFSERROR;
		}
		fnode_block = SPADFS_READ_FNODE_BLOCK(f->fs, secno, &bh, SRFB_FNODE, "SPADFS_CHECK_DIR_EMPTY");
		goto next_fnode_block;
	}
	if (__unlikely(CC_VALID(f->fs, fnode_block->cc, fnode_block->txc))) {
		secno = MAKE_D_OFF(fnode_block->next0, fnode_block->next1);
		__brelse(bh);
		goto read_next_fnode_block;
	}
	__brelse(bh);
	use_next_hash:
	if (__unlikely(next_hash != 0)) {
		hash = next_hash;
		goto new_hash_lookup;
	}
	return 0;

	bad_fnode:
	__brelse(bh);
	SPADFS_ERROR(f->fs, TXFLAGS_FS_ERROR, "SPADFS_CHECK_DIR_EMPTY: BAD FNODE ON BLOCK %LX", (unsigned long long)secno);
	return -EFSERROR;
}

int SPADFS_REMOVE_RECURSIVE(SPADFNODE *fn, __d_off root, int depth)
{
	SPADFS *fs = fn->fs;
	struct fnode_block *fnode_block;
	struct buffer_head *bh;
	__d_off c[2];
	int r;
	c[1] = 0;
	next_in_chain:
	fnode_block = SPADFS_READ_FNODE_BLOCK(fs, root, &bh, SRFB_FNODE | SRFB_DNODE, "SPADFS_REMOVE_RECURSIVE 1");
	if (__unlikely(IS_ERR(fnode_block))) return PTR_ERR(fnode_block);
	if (__likely(fnode_block->magic == FNODE_BLOCK_MAGIC)) {
		__d_off next_root;
		unsigned nsec = 1;
		while (!(fnode_block->flags & FNODE_BLOCK_LAST)) {
			__brelse(bh);
			fnode_block = SPADFS_READ_FNODE_BLOCK(fs, root + nsec, &bh, SRFB_FNODE, "SPADFS_REMOVE_RECURSIVE 2");
			if (__unlikely(IS_ERR(fnode_block))) return PTR_ERR(fnode_block);
			nsec++;
			if (__unlikely(nsec > 1 << fs->sectors_per_page_bits)) {
				__brelse(bh);
				SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "TOO LONG FNODE BLOCK RUN AT %LX", (unsigned long long)root);
				return -EFSERROR;
			}
		}
		next_root = 0;
		if (CC_VALID(fs, fnode_block->cc, fnode_block->txc)) {
			next_root = MAKE_D_OFF(fnode_block->next0, fnode_block->next1);
		}
		__brelse(bh);
		if (__unlikely(r = SPADFS_FREE_BLOCKS(fn, root, nsec))) return r;
		if (next_root) {
			if (__unlikely(SPADFS_STOP_CYCLES(fs, next_root, &c, "SPADFS_REMOVE_RECURSIVE"))) return -EFSERROR;
			root = next_root;
			goto next_in_chain;
		}
		return 0;
	} else {
		unsigned hp, i;
		__d_off ln;
		int version;
		if (__unlikely(depth >= HASH_BITS)) {
			__brelse(bh);
			SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "RECURSE OVERFLOW ON BLOCK %LX", (unsigned long long)root);
			return -EFSERROR;
		}
		version = DNODE_VERSION(fs, (struct dnode_page *)fnode_block);
		__brelse(bh);
		ln = 0;
		hp = 0;
		while (1) {
			__d_off n;
			int r = SPADFS_READ_DNODE(fs, root, version, hp, &n);
			if (__unlikely(r)) return r;
			if (n && n != ln) {
				ln = n;
				r = SPADFS_REMOVE_RECURSIVE(fn, n, depth + fs->dnode_hash_bits);
				if (__unlikely(r)) return r;
			}
			i = 1 << (fs->dnode_hash_bits - 1);
			while (!((hp ^= i) & i)) if (__unlikely(!(i >>= 1))) goto brk;
		}
		brk:
		return SPADFS_FREE_BLOCKS(fn, root, fs->dnode_page_sectors);
	}
}

__d_off SPADFS_ALLOC_HINT(SPADFNODE *f, int hint)
{
	SPADFS *fs = f->fs;
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	struct buffer_head *bh;
	unsigned group;
	if (__unlikely(!inode(f)->i_nlink)) {
		return_default:
		if (__unlikely(hint == HINT_META)) return 0;
		return (__d_off)fs->zones[1 + (hint == HINT_BIG)].grp_start << fs->sectors_per_group_bits;
	}
	if (__unlikely(hint == HINT_META)) return f->fnode_block;
	if (__unlikely(is_fnode_fixed(f))) {
		fnode_block = SPADFS_READ_FNODE_BLOCK(fs, f->fnode_block, &bh, SRFB_FIXED_FNODE, "SPADFS_ALLOC_HINT_(FIXED)");
		if (__unlikely(IS_ERR(fnode_block))) goto return_default;
		if (hint == HINT_SMALL) group = ((struct fixed_fnode_block *)fnode_block)->hint_small;
		else group = ((struct fixed_fnode_block *)fnode_block)->hint_large;
		goto brelse_ret;
	}
	fnode_block = SPADFS_READ_FNODE_BLOCK(fs, f->parent_fnode_block, &bh, SRFB_FNODE | SRFB_FIXED_FNODE, "SPADFS_ALLOC_HINT");
	if (__unlikely(IS_ERR(fnode_block))) goto return_default;
	fnode = (struct fnode *)((char *)fnode_block + f->parent_fnode_pos);
	if (hint == HINT_SMALL) group = f->blk1_n;
	else group = f->blk2_n;
	brelse_ret:
	__brelse(bh);
	return (__d_off)group << fs->sectors_per_group_bits;
}

void SPADFS_SET_NEW_HINT(SPADFNODE *f, struct alloc *al)
{
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	struct buffer_head *bh;
	unsigned hint;
	SPADFS *fs = f->fs;
	if (__unlikely(!inode(f)->i_nlink)) return;
	hint = al->sector >> fs->sectors_per_group_bits;
	if (__unlikely(is_fnode_fixed(f))) {
		fnode_block = SPADFS_READ_FNODE_BLOCK(fs, f->fnode_block, &bh, SRFB_FIXED_FNODE, "SPADFS_SET_NEW_HINT_(FIXED)");
		if (__unlikely(IS_ERR(fnode_block))) return;
		start_concurrent_atomic_buffer_modify(bh);
		if (__likely(al->flags & ALLOC_BIG_FILE)) ((struct fixed_fnode_block *)fnode_block)->hint_large = hint;
		else ((struct fixed_fnode_block *)fnode_block)->hint_small = hint;
		goto checksum_done;
	}
	fnode_block = SPADFS_READ_FNODE_BLOCK(fs, f->parent_fnode_block, &bh, SRFB_FNODE | SRFB_FIXED_FNODE, "SPADFS_SET_NEW_HINT");
	if (__unlikely(IS_ERR(fnode_block))) return;
	fnode = (struct fnode *)((char *)fnode_block + f->parent_fnode_pos);
	start_concurrent_atomic_buffer_modify(bh);
	if (__likely(al->flags & ALLOC_BIG_FILE)) fnode->run2n = hint;
	else fnode->run1n = hint;
	checksum_done:
	DO_FNODE_BLOCK_CHECKSUM(fs, fnode_block);
	end_concurrent_atomic_buffer_modify(bh);
	__brelse(bh);
}

void SPADFS_GET_DIR_HINT(SPADFNODE *f, __u16 *small, __u16 *big)
{
	SPADFS *fs = f->fs;
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	struct buffer_head *bh;
	*small = *big = 0;
	if (__unlikely(!f->fnode_block)) return;
	fnode_block = SPADFS_READ_FNODE_BLOCK(fs, f->fnode_block, &bh, SRFB_FNODE | SRFB_FIXED_FNODE, "SPADFS_GET_DIR_HINT");
	if (__unlikely(IS_ERR(fnode_block))) return;
	fnode = (struct fnode *)((char *)fnode_block + f->fnode_pos);
	*small = fnode->run1n;
	*big = fnode->run2n;
	__brelse(bh);
}

int SPADFS_WRITE_FILE(SPADFNODE *f)
{
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	struct buffer_head *bh;
	SPADFS *fs = f->fs;
	BUG_ON(S_ISDIR(inode(f)->i_mode));
	if (__unlikely(!f->fnode_block)) return 0;
	fnode_block = SPADFS_READ_FNODE_BLOCK(fs, f->fnode_block, &bh, SRFB_FNODE | SRFB_FIXED_FNODE, "SPADFS_WRITE_FILE");
	if (__unlikely(IS_ERR(fnode_block))) return PTR_ERR(fnode_block);
	fnode = (struct fnode *)((char *)fnode_block + f->fnode_pos);
	start_concurrent_atomic_buffer_modify(bh);
	if (__unlikely((fnode->next & FNODE_NEXT_SIZE) != FNODE_SIZE(fnode->namelen, f->ea_size))) {
		SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "READ DIFFERENT FILE FNODE AT %LX/%X: %X", (unsigned long long)f->fnode_block, f->fnode_pos, fnode->next);
		end_concurrent_atomic_buffer_modify(bh);
		__brelse(bh);
		return -EFSERROR;
	}
	if (!CC_CURRENT(fs, fnode->cc, fnode->txc)) {
		CC_SET_CURRENT(fs, &fnode->cc, &fnode->txc);
		fnode->next &= ~FNODE_NEXT_FREE;
	}
	SET_SPADFS_FILE(f, fnode, !CC_VALID(fs, fnode->cc, fnode->txc));
	WRITE_EA(f, fnode);
	DO_FNODE_BLOCK_CHECKSUM(fs, fnode_block);
	end_concurrent_atomic_buffer_modify(bh);
	__brelse(bh);
	return 0;
}

int SPADFS_WRITE_DIRECTORY(SPADFNODE *f)
{
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	struct buffer_head *bh;
	SPADFS *fs = f->fs;
	BUG_ON(!S_ISDIR(inode(f)->i_mode));
	if (__unlikely(!f->fnode_block)) return 0;
	fnode_block = SPADFS_READ_FNODE_BLOCK(fs, f->fnode_block, &bh, SRFB_FNODE | SRFB_FIXED_FNODE, "SPADFS_WRITE_DIRECTORY");
	if (__unlikely(IS_ERR(fnode_block))) return PTR_ERR(fnode_block);
	fnode = (struct fnode *)((char *)fnode_block + f->fnode_pos);
	start_concurrent_atomic_buffer_modify(bh);
	if (__unlikely((fnode->next & FNODE_NEXT_SIZE) != FNODE_SIZE(fnode->namelen, f->ea_size))) {
		SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "READ DIFFERENT DIRECTORY FNODE AT %LX/%X: %X", (unsigned long long)f->fnode_block, f->fnode_pos, fnode->next);
		end_concurrent_atomic_buffer_modify(bh);
		__brelse(bh);
		return -EFSERROR;
	}
	if (!CC_CURRENT(fs, fnode->cc, fnode->txc)) {
		CC_SET_CURRENT(fs, &fnode->cc, &fnode->txc);
		fnode->next &= ~FNODE_NEXT_FREE;
	}
	SET_SPADFS_DIRECTORY(f, fnode, !CC_VALID(fs, fnode->cc, fnode->txc));
	WRITE_EA(f, fnode);
	DO_FNODE_BLOCK_CHECKSUM(fs, fnode_block);
	end_concurrent_atomic_buffer_modify(bh);
	__brelse(bh);
	return 0;
}

static int SPADFS_FIND_LEAF_PAGE_FOR_HASH(SPADFNODE *fn, __d_off root, hash_t hash, __d_off *fnode_sector, __d_off *prev_dnode, int *hash_bits, int *splitable)
{
	SPADFS *fs = fn->fs;
	struct buffer_head *bh;
	struct fnode_block *f;
	*prev_dnode = 0;
	*hash_bits = 0;
	*splitable = -1;
	subnode:
	f = SPADFS_READ_FNODE_BLOCK(fs, root, &bh, SRFB_FNODE | SRFB_DNODE, "SPADFS_FIND_LEAF_PAGE_FOR_HASH");
	*fnode_sector = root;
	if (__unlikely(IS_ERR(f))) return PTR_ERR(f);
	if (__likely(f->magic == FNODE_BLOCK_MAGIC)) {
		__brelse(bh);
		return 0;
	} else {
		int i, j;
		int r;
		__d_off new_root;
		hash_t hpos;
		int version = DNODE_VERSION(fs, (struct dnode_page *)f);
		__brelse(bh);
		*prev_dnode = root;
		hpos = hash & ((1 << (fs->dnode_hash_bits)) - 1);
		r = SPADFS_READ_DNODE(fs, root, version, hpos, &new_root);
		if (__unlikely(r)) return r;
		if (__unlikely(!new_root)) {
			__d_off new_page_sector;
			int x;
			int step;
			r = SPADFS_ALLOC_LEAF_PAGE(fn, root, 1 << fs->sectors_per_fnodepage_bits, 0, &new_page_sector);
			if (__unlikely(r == -ENOSPC)) r = SPADFS_ALLOC_LEAF_PAGE(fn, root, 1 << fs->sectors_per_block_bits, 0, &new_page_sector);
			if (__unlikely(r)) return r;
			version = SPADFS_BEGIN_MODIFY_DNODE(fs, root);
			if (__unlikely(version < 0)) return version;
			r = SPADFS_WRITE_DNODE(fs, root, version, hpos, new_page_sector);
			if (__unlikely(r)) return r;
			step = 1 << fs->dnode_hash_bits;
			do {
				hpos ^= step >> 1;
				for (x = hpos; x < 1 << fs->dnode_hash_bits; x += step) {
					__d_off test;
					r = SPADFS_READ_DNODE(fs, root, version, x, &test);
					if (__unlikely(r)) return r;
					if (test) goto done;
				}
				for (x = hpos; x < 1 << fs->dnode_hash_bits; x += step) {
					r = SPADFS_WRITE_DNODE(fs, root, version, x, new_page_sector);
					if (__unlikely(r)) return r;
				}
				step >>= 1;
				hpos &= ~step;
			} while (step > 1);
			done:
			root = new_page_sector;
			goto subnode;
		}
		if (__unlikely(*hash_bits % (unsigned)fs->dnode_hash_bits) || __unlikely(*splitable != -1))
			goto bad_tree;
		j = 1 << fs->dnode_hash_bits;
		for (i = 1; i < j; i <<= 1) {
			__d_off test;
			r = SPADFS_READ_DNODE(fs, root, version, hpos ^ i, &test);
			if (__unlikely(r)) return r;
			if (test == new_root) {
				*splitable = hpos;
				break;
			}
			(*hash_bits)++;
		}
		hash >>= fs->dnode_hash_bits;
		if (__unlikely(*hash_bits > HASH_BITS)) goto bad_tree;
		root = new_root;
		goto subnode;
	}
	bad_tree:
	SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "BAD DNODE TREE ON %LX (PARENT %LX/%X)", (unsigned long long)root, (unsigned long long)fn->fnode_block, fn->fnode_pos);
	return -EFSERROR;
}

#define HASH_1_SHIFT	16
#define HASH_1		(1 << HASH_1_SHIFT)
#define HASH_VAL	(HASH_1 - 1)

static int TEST_HASH_BIT(char *name, unsigned namelen, unsigned hash_bits)
{
	hash_t hash = name_len_hash(name, namelen);
	return ((hash >> (hash_bits & HASH_VAL)) ^ (hash_bits >> HASH_1_SHIFT) ^ 1) & 1;
}

static int TEST_HASH(SPADFS *sfs, __d_off sec, int hash_bits)
{
	hash_t hash;
	int found = 0;
	struct buffer_head *bh;
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	unsigned size, namelen;
	next_block:
	fnode_block = SPADFS_READ_FNODE_BLOCK(sfs, sec, &bh, SRFB_FNODE, "TEST_HASH");
	if (__unlikely(IS_ERR(fnode_block))) return PTR_ERR(fnode_block);
	fnode = fnode_block->fnodes;
	next_fnode:
	size = fnode->next & FNODE_NEXT_SIZE;
	namelen = fnode->namelen;
	VALIDATE_FNODE(sfs, fnode_block, fnode, size, namelen, used, free, bad_fnode);
	used:
	hash = name_len_hash(FNODE_NAME(fnode), namelen);
	found |= 1 << ((hash >> hash_bits) & 1);
	if (found == 3) {
		__brelse(bh);
		return 3;
	}
	free:
	fnode = (struct fnode *)((char *)fnode + size);
	if (__likely(((unsigned long)fnode & (FNODE_BLOCK_SIZE - 1)) != 0)) goto next_fnode;
	if (!(fnode_block->flags & FNODE_BLOCK_LAST)) {
		__brelse(bh);
		sec++;
		goto next_block;
	}
	__brelse(bh);
	return found;

	bad_fnode:
	__brelse(bh);
	SPADFS_ERROR(sfs, TXFLAGS_FS_ERROR, "TEST_HASH: BAD FNODE ON BLOCK %LX", (unsigned long long)sec);
	return -EFSERROR;
}

static int COPY_FNODES_TO_BLOCK(SPADFS *sfs, __d_off dst_sec, __d_off src_sec, int (*condition)(char *name, unsigned namelen, unsigned x), unsigned x)
{
	struct fnode_block *src_block, *dst_block;
	struct fnode *src_fnode, *dst_fnode;
	struct buffer_head *bh_src, *bh_dst;
	unsigned size, dsize;
	unsigned namelen;
	dst_block = SPADFS_READ_FNODE_BLOCK(sfs, dst_sec, &bh_dst, SRFB_FNODE, "COPY_FNODES_TO_BLOCK 1");
	if (__unlikely(IS_ERR(dst_block))) return PTR_ERR(dst_block);
	dst_fnode = dst_block->fnodes;
	dsize = FNODE_MAX_SIZE;
	new_src_block:
	src_block = SPADFS_READ_FNODE_BLOCK(sfs, src_sec, &bh_src, SRFB_FNODE, "COPY_FNODES_TO_BLOCK 2");
	if (__unlikely(IS_ERR(src_block))) {
		__brelse(bh_dst);
		return PTR_ERR(src_block);
	}
	src_fnode = src_block->fnodes;
	next_fnode:
	size = src_fnode->next & FNODE_NEXT_SIZE;
	namelen = src_fnode->namelen;
	VALIDATE_FNODE(sfs, src_block, src_fnode, size, namelen, ok, skip, bad_fnode);
	ok:
	if (condition && !condition(FNODE_NAME(src_fnode), namelen, x)) goto skip;
	if (dsize >= size) {
		have_new_dst:
		memcpy(dst_fnode, src_fnode, size);
		SPADFS_MOVE_FNODE_PTR(sfs, src_sec, (char *)src_fnode - (char *)src_block, dst_sec, (char *)dst_fnode - (char *)dst_block, src_fnode->flags & FNODE_FLAGS_DIR);
		if (dsize -= size) {
			dst_fnode = (struct fnode *)((char *)dst_fnode + size);
			dst_fnode->next = dsize | FNODE_NEXT_FREE;
			dst_fnode->cc = 0;
			dst_fnode->txc = 0;
		}
		skip:
		src_fnode = (struct fnode *)((char *)src_fnode + size);
		if (__unlikely(!((unsigned)(unsigned long)src_fnode & (FNODE_BLOCK_SIZE - 1)))) {
			if (__unlikely((src_block->flags & FNODE_BLOCK_LAST) != 0)) goto end;
			__brelse(bh_src);
			src_sec++;
			goto new_src_block;
		}
		goto next_fnode;
	} else {
		DO_FNODE_BLOCK_CHECKSUM(sfs, dst_block);
		mark_buffer_dirty(bh_dst);
		__brelse(bh_dst);
		dst_sec++;
		dst_block = SPADFS_READ_FNODE_BLOCK(sfs, dst_sec, &bh_dst, SRFB_FNODE, "COPY_FNODES_TO_BLOCK 3");
		if (__unlikely(IS_ERR(dst_block))) {
			__brelse(bh_src);
			return PTR_ERR(dst_block);
		}
		dst_fnode = dst_block->fnodes;
		dsize = FNODE_MAX_SIZE;
		goto have_new_dst;
	}
	end:
	__brelse(bh_src);
	DO_FNODE_BLOCK_CHECKSUM(sfs, dst_block);
	mark_buffer_dirty(bh_dst);
	__brelse(bh_dst);
	return 0;

	bad_fnode:
	__brelse(bh_src);
	__brelse(bh_dst);
	SPADFS_ERROR(sfs, TXFLAGS_FS_ERROR, "COPY_FNODES_TO_BLOCK: BAD FNODE ON BLOCK %LX", (unsigned long long)src_sec);
	return -EFSERROR;
}

struct fnode *SPADFS_ADD_FNODE_TO_DIRECTORY(SPADFNODE *dir, const char *name, unsigned namelen, unsigned ea_size, struct buffer_head **bh, __d_off *fnode_address, unsigned *fnode_off, struct fnode_block **pfnode_block, int wlock)
{
	SPADFS *sfs = dir->fs;
	__d_off c[2];
	int hash_bits, splitable;
	int r;
	int pass2;
	__d_off fnode_blk_sector;
	unsigned n_sec;
	__d_off dnode;
	int size, rsize;
	struct fnode *fnode;
	struct fnode_block *fnode_block;
	hash_t hash = name_len_hash(name, namelen);
	c[1] = 0;
	BUG_ON(ea_size > FNODE_MAX_EA_SIZE);
	rsize = FNODE_SIZE(namelen, ea_size);
	total_restart:
	r = SPADFS_FIND_LEAF_PAGE_FOR_HASH(dir, dir->root, hash, &fnode_blk_sector, &dnode, &hash_bits, &splitable);
	if (__unlikely(r)) return ERR_PTR(r);
	pass2 = 0;
	next_fnode_page:
	n_sec = 0;
	next_fnode_block:
	fnode_block = SPADFS_READ_FNODE_BLOCK(sfs, fnode_blk_sector + n_sec, bh, SRFB_FNODE, "SPADFS_ADD_FNODE_TO_DIRECTORY");
	if (__unlikely(IS_ERR(fnode_block))) return (void *)fnode_block;
	fnode = fnode_block->fnodes;
	next_fnode:
	retry_fnode:
	size = fnode->next & FNODE_NEXT_SIZE;
	VALIDATE_FNODE(sfs, fnode_block, fnode, size, fnode->namelen, used, free, bad_fnode);
	free:
	if (!CC_CURRENT(sfs, fnode->cc, fnode->txc)) {
		if (size >= rsize) {
			*fnode_address = fnode_blk_sector + n_sec;
			*fnode_off = (char *)fnode - (char *)fnode_block;
			*pfnode_block = fnode_block;
			start_concurrent_atomic_buffer_modify(*bh);
			if (rsize < size) {
				struct fnode *afterfnode = (struct fnode *)((char *)fnode + rsize);
				afterfnode->next = (size - rsize) | FNODE_NEXT_FREE;
				afterfnode->cc = 0;
				afterfnode->txc = 0;
				fnode->next = rsize | FNODE_NEXT_FREE;
			}
			CC_SET_CURRENT_INVALID(sfs, &fnode->cc, &fnode->txc);
			return fnode;
		} else {
			struct fnode *new_fnode = (struct fnode *)((char *)fnode + size);
			int new_size;
			if (__unlikely(!((unsigned long)new_fnode & (FNODE_BLOCK_SIZE - 1)))) goto end_of_block;
			new_size = new_fnode->next & FNODE_NEXT_SIZE;
			VALIDATE_FNODE(sfs, fnode_block, new_fnode, new_size, new_fnode->namelen, s_used, s_free, bad_fnode);
			s_free:
			if (__likely(!CC_CURRENT(sfs, new_fnode->cc, new_fnode->txc))) {
				start_concurrent_atomic_buffer_modify(*bh);
				fnode->next += new_size;
				DO_FNODE_BLOCK_CHECKSUM(sfs, fnode_block);
				end_concurrent_atomic_buffer_modify(*bh);
				goto retry_fnode;
			}
			s_used:
			fnode = new_fnode;
			size = new_size;
		}
	}
	used:
	fnode = (struct fnode *)((char *)fnode + size);
	if (__likely(((unsigned long)fnode & (FNODE_BLOCK_SIZE - 1)) != 0)) goto next_fnode;
	end_of_block:
	if (!(fnode_block->flags & FNODE_BLOCK_LAST)) {
		n_sec++;
		__brelse(*bh);
		goto next_fnode_block;
	}
	if (CC_VALID(sfs, fnode_block->cc, fnode_block->txc)) {
		fnode_blk_sector = MAKE_D_OFF(fnode_block->next0, fnode_block->next1);
		__brelse(*bh);
		if (__unlikely(SPADFS_STOP_CYCLES(sfs, fnode_blk_sector, &c, "SPADFS_ADD_FNODE_TO_DIRECTORY"))) return ERR_PTR(-EFSERROR);
		pass2 = 1;
		goto next_fnode_page;
	}
	if (CC_CURRENT(sfs, fnode_block->cc, fnode_block->txc)) {
		__brelse(*bh);
		return NEED_SYNC;
	}
	if (__likely(!pass2)) {
		int th;
		__d_off fnode_block_0_sector;
		__d_off fnode_block_1_sector;
		int version;
		int bits;

		if (!wlock) {
			__brelse(*bh);
			return NEED_WLOCK;
		}

		/* 1. REALLOC CURRENT BLOCK IF IT'S ROOT & SMALL */
		
		if (!dnode && n_sec + 1 < 1 << sfs->sectors_per_fnodepage_bits) {
			unsigned new_sectors;
			__d_off new_sector;
			new_sectors = FNODE_BLOCK_SIZE / 512;
			while (new_sectors <= n_sec + 1) new_sectors <<= 1;
			r = SPADFS_ALLOC_LEAF_PAGE(dir, fnode_blk_sector, new_sectors, 0, &new_sector);
			if (__unlikely(r)) {
				if (__likely(r == -ENOSPC)) goto alloc_chain;
				__brelse(*bh);
				return ERR_PTR(r);
			}
			__brelse(*bh);
			COPY_FNODES_TO_BLOCK(sfs, new_sector, fnode_blk_sector, NULL, 0);
			r = SPADFS_FREE_BLOCKS(dir, fnode_blk_sector, n_sec + 1);
			if (__unlikely(r)) {
				return ERR_PTR(r);
			}
			dir->root = new_sector;
			r = SPADFS_WRITE_DIRECTORY(dir);
			if (__unlikely(r)) return ERR_PTR(r);
			fnode_blk_sector = new_sector;
			goto next_fnode_page;
		}

		/* 2. IF IT'S END OF HASH, ALLOC CHAIN */

		if (__unlikely(hash_bits >= HASH_BITS)) goto alloc_chain;

		/* 3. IF THE DNODE IS FULL, ALLOC NEW DNODE */

		if (splitable == -1) {
			__d_off new_dnode;
			r = SPADFS_ALLOC_DNODE_PAGE(dir, fnode_blk_sector, &new_dnode, dnode, fnode_blk_sector);
			if (__unlikely(r)) {
				if (__likely(r == -ENOSPC)) goto alloc_chain;
				__brelse(*bh);
				return ERR_PTR(r);
			}
			__brelse(*bh);
			if (dnode) {
				unsigned i;
				int version = SPADFS_BEGIN_MODIFY_DNODE(sfs, dnode);
				if (__unlikely(version < 0)) return ERR_PTR(version);
				for (i = 0; i < 1 << sfs->dnode_hash_bits; i++) {
					__d_off test;
					r = SPADFS_READ_DNODE(sfs, dnode, version, i, &test);
					if (__unlikely(r)) return ERR_PTR(r);
					if (test == fnode_blk_sector) {
						SPADFS_WRITE_DNODE(sfs, dnode, version, i, new_dnode);
					}
				}
			} else {
				dir->root = new_dnode;
				r = SPADFS_WRITE_DIRECTORY(dir);
				if (__unlikely(r)) return ERR_PTR(r);
			}
			dnode = new_dnode;
			splitable = 0;
			goto next_fnode_page;
		}

		/* 4. TEST SPLITABILITY OF FNODE PAGE */

		th = TEST_HASH(sfs, fnode_blk_sector, hash_bits);
		if (__unlikely(th < 0)) {
			__brelse(*bh);
			return ERR_PTR(th);
		}

		/* 5. ALLOC NEW 2 FNODE PAGES AND SPLIT FNODE */

		if (th & 1) {
			r = SPADFS_ALLOC_LEAF_PAGE(dir, fnode_blk_sector, 1 << sfs->sectors_per_fnodepage_bits, 0, &fnode_block_0_sector);
			if (__unlikely(r)) {
				if (__likely(r == -ENOSPC)) goto alloc_chain;
				__brelse(*bh);
				return ERR_PTR(r);
			}
		} else fnode_block_0_sector = 0;
		if (th & 2) {
			r = SPADFS_ALLOC_LEAF_PAGE(dir, fnode_blk_sector, 1 << sfs->sectors_per_fnodepage_bits, 0, &fnode_block_1_sector);
			if (__unlikely(r)) {
				if (__likely(r == -ENOSPC)) {
					if (fnode_block_0_sector) {
						r = SPADFS_FREE_BLOCKS(dir, fnode_block_0_sector, 1 << sfs->sectors_per_fnodepage_bits);
						if (__unlikely(r)) {
							__brelse(*bh);
							return ERR_PTR(r);
						}
					}
					goto alloc_chain;
				}
				__brelse(*bh);
				return ERR_PTR(r);
			}
			COPY_FNODES_TO_BLOCK(sfs, fnode_block_1_sector, fnode_blk_sector, TEST_HASH_BIT, hash_bits | HASH_1);
		} else fnode_block_1_sector = 0;
		if (th & 1) {
			COPY_FNODES_TO_BLOCK(sfs, fnode_block_0_sector, fnode_blk_sector, TEST_HASH_BIT, hash_bits);
		}

		__brelse(*bh);

		/* 6. READ PARENT DNODE */

		version = SPADFS_BEGIN_MODIFY_DNODE(sfs, dnode);
		if (__unlikely(version < 0)) return ERR_PTR(version);

		/* 7. SPLIT POINTERS ON DNODE */

		bits = hash_bits % (unsigned)sfs->dnode_hash_bits;
		splitable &= (1 << bits) - 1;
		do {
			r = SPADFS_WRITE_DNODE(sfs, dnode, version, splitable, fnode_block_0_sector);
			if (__unlikely(r)) return ERR_PTR(r);
			splitable += 1 << bits;
			r = SPADFS_WRITE_DNODE(sfs, dnode, version, splitable, fnode_block_1_sector);
			if (__unlikely(r)) return ERR_PTR(r);
			splitable += 1 << bits;
		} while (splitable < 1 << sfs->dnode_hash_bits);
		r = SPADFS_FREE_BLOCKS(dir, fnode_blk_sector, n_sec + 1);
		if (__unlikely(r)) return ERR_PTR(r);
		goto total_restart;
	} else {
		if (__unlikely(pass2 == 2)) {
			__brelse(*bh);
			SPADFS_ERROR(sfs, TXFLAGS_FS_ERROR, "COULDN'T ALLOCATE FNODE EVEN IN NEW PAGE");
			return ERR_PTR(-EFSERROR);
		}
		alloc_chain:
		r = SPADFS_ALLOC_LEAF_PAGE(dir, fnode_blk_sector + n_sec, 1 << sfs->sectors_per_block_bits, fnode_blk_sector + n_sec, &fnode_blk_sector);
		if (__unlikely(r)) {
			__brelse(*bh);
			return ERR_PTR(r);
		}
		start_concurrent_atomic_buffer_modify(*bh);
		fnode_block->next0 = MAKE_PART_0(fnode_blk_sector);
		fnode_block->next1 = MAKE_PART_1(fnode_blk_sector);
		fnode_block->cc = sfs->cc;
		fnode_block->txc = sfs->txc;
		DO_FNODE_BLOCK_CHECKSUM(sfs, fnode_block);
		end_concurrent_atomic_buffer_modify(*bh);
		__brelse(*bh);
		pass2 = 2;
		goto next_fnode_page;
	}

	bad_fnode:
	__brelse(*bh);
	SPADFS_ERROR(sfs, TXFLAGS_FS_ERROR, "SPADFS_ADD_FNODE_TO_DIRECTORY: BAD FNODE ON BLOCK %LX", (unsigned long long)(fnode_blk_sector + n_sec));
	return ERR_PTR(-EFSERROR);
}

static __finline__ void SPADFS_SWAP_FNODE(struct fnode *fnode)
{
	__u64 s;
	if (__unlikely(fnode->flags & FNODE_FLAGS_DIR)) {
		__u32 r0 = fnode->run10;
		__u16 r1 = fnode->run11;
		fnode->run10 = fnode->run20;
		fnode->run11 = fnode->run21;
		fnode->run20 = r0;
		fnode->run21 = r1;
	}
	s = fnode->size0;
	fnode->size0 = fnode->size1;
	fnode->size1 = s;
}

int SPADFS_REMOVE_FNODE_FROM_DIRECTORY(SPADFNODE *dir, SPADFNODE *file, struct qstr *name)
{
	SPADFS *fs = dir->fs;
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	struct buffer_head *bh;
	spadfs_ino_t ino;
	ino = file->spadfs_ino;
	if (__unlikely(is_fnode_fixed(file))) {
		int r = SPADFS_LOOKUP_INO(dir, name, &ino, 1);
		if (__unlikely(r)) {
			if (r > 0) SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "CAN'T FIND ENTRY TO DELETE IN DIRECTORY %LX/%X", (unsigned long long)dir->fnode_block, dir->fnode_pos), r = -EFSERROR;
			return r;
		}
	}
	fnode_block = SPADFS_READ_FNODE_BLOCK(fs, spadfs_ino_t_sec(ino), &bh, SRFB_FNODE, "SPADFS_REMOVE_FNODE_FROM_DIRECTORY");
	if (__unlikely(IS_ERR(fnode_block))) return PTR_ERR(fnode_block);
	fnode = (struct fnode *)((char *)fnode_block + spadfs_ino_t_pos(ino));
	start_concurrent_atomic_buffer_modify(bh);
	if (CC_CURRENT(fs, fnode->cc, fnode->txc) && fnode->next & FNODE_NEXT_FREE) {
		fnode->cc = 0;
		fnode->txc = 0;
	} else {
		if (CC_VALID(fs, fnode->cc, fnode->txc) ^ CC_CURRENT(fs, fnode->cc, fnode->txc)) SPADFS_SWAP_FNODE(fnode);
		fnode->cc = fs->cc;
		fnode->txc = fs->txc;
		fnode->next |= FNODE_NEXT_FREE;
	}
	DO_FNODE_BLOCK_CHECKSUM(fs, fnode_block);
	end_concurrent_atomic_buffer_modify(bh);
	return 0;
}

/* need to be called after change of length of extended attributes io change
   of link count --- will reinsert fnode to directory and drop old instance.
   Must be called with read or write lock on filesystem and lock on file (if
   "file" argument is a file).
*/

int SPADFS_REFILE_FNODE(SPADFNODE *dir, SPADFNODE *file)
{
	SPADFS *fs = file->fs;
	if (__unlikely(!file->fnode_block)) return 0;
	if (is_fnode_fixed(file)) {
		struct fnode *fnode, *old_fnode;
		struct buffer_head *bh;
		struct fixed_fnode_block *fixed_fnode_block = (struct fixed_fnode_block *)SPADFS_READ_FNODE_BLOCK(fs, file->fnode_block, &bh, SRFB_FIXED_FNODE, "SPADFS_REFILE_FNODE");
		if (__unlikely(IS_ERR(fixed_fnode_block))) return PTR_ERR(fixed_fnode_block);
		start_atomic_buffer_modify(bh);	/* no need to use concurrent, this block is just for us */
		old_fnode = (struct fnode *)((char *)fixed_fnode_block + file->fnode_pos);
		if (!CC_CURRENT(fs, fixed_fnode_block->cc, fixed_fnode_block->txc)) {
			CC_SET_CURRENT(fs, &fixed_fnode_block->cc, &fixed_fnode_block->txc);
			file->fnode_pos = FIXED_FNODE_BLOCK_FNODE0 + FIXED_FNODE_BLOCK_FNODE1 - file->fnode_pos;
		}
		fnode = (struct fnode *)((char *)fixed_fnode_block + file->fnode_pos);
		*FIXED_FNODE_NLINK_PTR(fnode) = file->spadfs_nlink;
		fnode->next = FNODE_SIZE(0, file->ea_size);
	/* doesn't really matter what we set to (cc, txc), because fnode won't
	   be used in case of crash anyway --- set it to current to prevent
	   switch on update */
		fnode->cc = fs->cc;
		fnode->txc = fs->txc;
		fnode->namelen = 0;
		if (__likely(!S_ISDIR(inode(file)->i_mode))) {
			SET_SPADFS_FILE(file, fnode, 0);
			fnode->flags = 0;
		} else {
			SET_SPADFS_DIRECTORY(file, fnode, 0);
			fnode->flags = FNODE_FLAGS_DIR;
			fnode->run1n = old_fnode->run1n;	/* alloc hints */
			fnode->run2n = old_fnode->run2n;
		}
		WRITE_EA(file, fnode);
		DO_FNODE_BLOCK_CHECKSUM(fs, (struct fnode_block *)fixed_fnode_block);
		end_atomic_buffer_modify(bh);
		__brelse(bh);
		return 0;
	}
		/* FIXME: not done yet */
	return -ENOSYS;
}

