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

static struct dentry *SPADFS_LOOKUP(struct inode *dir, struct dentry *dentry, struct nameidata *nd)
{
	int r;
	spadfs_ino_t ino;
	struct inode *result;
	SPADFS *fs = spadfnode(dir)->fs;
	if (__unlikely(dentry->d_name.len > MAX_NAME_LEN)) return ERR_PTR(-ENAMETOOLONG);
	if (__unlikely(!spadfnode(dir)->fnode_block)) return ERR_PTR(-ENOENT);
	r = SPADFS_LOOKUP_INO(spadfnode(dir), &dentry->d_name, &ino, 0);
	if (__unlikely(r)) {
		if (r < 0) return ERR_PTR(r);
		goto not_found;
	}
	down_read(&fs->sync_lock);
	result = SPADFS_IGET(dir->i_sb, ino, spadfnode(dir)->fnode_block, spadfnode(dir)->fnode_pos);
	up_read(&fs->sync_lock);
	if (__unlikely(IS_ERR(result))) return (void *)result;

	SPADFS_SET_DENTRY_OPERATIONS(fs, dentry);
	d_add(dentry, result);
	return NULL;

	not_found:
	SPADFS_SET_DENTRY_OPERATIONS(fs, dentry);
	d_add(dentry, NULL);
	return NULL;
}

static int SPADFS_READDIR(struct file *file, void *dirent, filldir_t filldir)
{
	int r;
	SPADFNODE *f;
	struct buffer_head *bh;
	struct fnode_block *fnode_block;
	__d_off secno;
	hash_t hash, next_hash;
	unsigned order, pos;
	unsigned current_order;
	__d_off c[2];
	c[1] = 0;
	f = spadfnode(file->f_dentry->d_inode);
	if (__unlikely(is_dir_off_t_special(file->f_pos))) {
		unsigned n = dir_off_t_special_n(file->f_pos);
		switch (n) {
			case 0:
				if (__unlikely(filldir(dirent, ".", 1, file->f_pos, file->f_dentry->d_inode->i_ino, DT_DIR))) goto out;
				file->f_pos = make_special_dir_off_t(1);
			case 1:
				if (__unlikely(filldir(dirent, "..", 2, file->f_pos, file->f_dentry->d_parent->d_inode->i_ino, DT_DIR))) goto out;
				file->f_pos = make_dir_off_t(0, 0, SIZEOF_FNODE_BLOCK);
				break;
			case 2: goto out;
			default:
				r = -EINVAL;
				goto err;
		}
	}

	if (__unlikely(!f->fnode_block)) goto eof;

	hash = dir_off_t_hash(file->f_pos);
	order = dir_off_t_order(file->f_pos);
	pos = dir_off_t_pos(file->f_pos);

	new_hash_lookup:
	fnode_block = SPADFS_FIND_HASH_BLOCK(f, hash, &bh, &secno, &next_hash);
	current_order = 0;
	if (__unlikely(!fnode_block)) goto use_next_hash;
	next_fnode_block:
	if (__unlikely(IS_ERR(fnode_block))) {
		r = PTR_ERR(fnode_block);
		goto err;
	}
	if (__likely(current_order >= order)) {
		unsigned size, namelen, fpos;
		struct fnode *fnode = fnode_block->fnodes;
		next_fnode:
		size = fnode->next & FNODE_NEXT_SIZE;
		VALIDATE_FNODE(f->fs, fnode_block, fnode, size, namelen = fnode->namelen, ok, skip, bad_fnode);
		ok:
		fpos = (char *)fnode - (char *)fnode_block;
		if (fpos >= pos) {
			ino_t ino;
			unsigned dt;
			file->f_pos = make_dir_off_t(hash, current_order, fpos);
			if (__likely(!(fnode->flags & FNODE_FLAGS_HARDLINK))) {
				ino = spadfs_ino_t_2_ino_t(make_spadfs_ino_t(secno, fpos));
				if (__unlikely(fnode->flags & FNODE_FLAGS_DIR)) {
					dt = DT_DIR;
				} else {
					struct fnode_ea *ea;
					struct fnode_ea *eas = (struct fnode_ea *)((char *)fnode + FNODE_EA_POS(namelen));
					unsigned ea_size = size - FNODE_EA_POS(namelen);
					ea = GET_EA(eas, ea_size, EA_UNX_MAGIC, EA_UNX_MAGIC_MASK);
					if (__unlikely(!ea)) {
						ea = GET_EA(eas, ea_size, EA_SYMLINK_MAGIC, EA_SYMLINK_MAGIC_MASK);
						if (__unlikely(ea == GET_EA_ERROR)) goto ea_error;
						if (__likely(ea != NULL)) dt = DT_LNK;
						else dt = DT_REG;
					} else if (__unlikely(ea == GET_EA_ERROR)) {
						ea_error:
						SPADFS_ERROR(f->fs, TXFLAGS_FS_ERROR, "ERROR PARSING EXTENDED ATTRIBUTES ON FNODE %LX/%X DURING READDIR OF %LX/%X", (unsigned long long)secno, fpos, (unsigned long long)f->fnode_block, f->fnode_pos);
						goto brelse_ret_fserror;
					} else {
						/* documented in include/linux/fs.h, so I hope it won't change */
						dt = (((struct ea_unx *)ea)->mode >> 12) & 15 ;
					}
				}
			} else {
				ino = spadfs_ino_t_2_ino_t(make_fixed_spadfs_ino_t(MAKE_D_OFF(fnode->anode0, fnode->anode1)));
				dt = DT_UNKNOWN;
			}
			if (__unlikely(filldir(dirent, FNODE_NAME(fnode), namelen, file->f_pos, ino, dt))) {
				__brelse(bh);
				goto out;
			}
		}
		skip:
		fnode = (struct fnode *)((char *)fnode + size);
		if (__likely(((unsigned long)fnode & (FNODE_BLOCK_SIZE - 1)) != 0)) goto next_fnode;
		pos = SIZEOF_FNODE_BLOCK;
	}
	if (!(fnode_block->flags & FNODE_BLOCK_LAST)) {
		__brelse(bh);
		secno++;
		read_next_fnode_block:
		if (__unlikely(SPADFS_STOP_CYCLES(f->fs, secno, &c, "SPADFS_READDIR"))) {
			r = -EFSERROR;
			goto err;
		}
		current_order++;
		fnode_block = SPADFS_READ_FNODE_BLOCK(f->fs, secno, &bh, SRFB_FNODE, "SPADFS_READDIR");
		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)) {
		order = 0;
		hash = next_hash;
		goto new_hash_lookup;
	}

	eof:
	file->f_pos = make_special_dir_off_t(2);
	out:
	r = 0;
	err:
	return r;

	bad_fnode:
	SPADFS_ERROR(f->fs, TXFLAGS_FS_ERROR, "READDIR: BAD FNODE ON BLOCK %LX", (unsigned long long)secno);
	brelse_ret_fserror:
	__brelse(bh);
	r = -EFSERROR;
	goto err;
}

static int SPADFS_CREATE(struct inode *dir, struct dentry *dentry, int mode, struct nameidata *nd)
{
	SPADFS *fs = spadfnode(dir)->fs;
	struct buffer_head *bh;
	__d_off fnode_address;
	unsigned fnode_off;
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	int wlock = 0;
	int synced = 0;
	time_t ctime;
	struct inode *inode;
	int r;
	struct ea_unx *ea;

	lock_and_again:
	ND_LOCK(fs, wlock);
	if (__unlikely(!spadfnode(dir)->fnode_block)) {
		ND_UNLOCK(fs, wlock);
		return -ENOENT;
	}
	fnode = SPADFS_ADD_FNODE_TO_DIRECTORY(spadfnode(dir), dentry->d_name.name, dentry->d_name.len, FNODE_EA_DO_ALIGN(sizeof(struct ea_unx)), &bh, &fnode_address, &fnode_off, &fnode_block, wlock);
	if (__unlikely(IS_ERR(fnode))) {
		ND_UNLOCK(fs, wlock);
		if (__likely(fnode == ERR_PTR(-ENOSPC)) && !synced) goto do_sync;
		return PTR_ERR(fnode);
	}
	if (__unlikely(fnode == NEED_SYNC)) {
		ND_UNLOCK(fs, wlock);
		do_sync:
		synced = 1;
		if (__unlikely(r = SPADFS_COMMIT(fs))) return r;
		goto lock_and_again;
	}
	if (__unlikely(fnode == NEED_WLOCK)) {
		BUG_ON(wlock);
		ND_UNLOCK(fs, wlock);
		wlock = 1;
		goto lock_and_again;
	}
	fnode->size0 = 0;
	fnode->size1 = 0;
	fnode->ctime = fnode->mtime = ctime = get_seconds();
	fnode->run10 = 0;
	fnode->run11 = 0;
	fnode->run1n = 0;
	fnode->run20 = 0;
	fnode->run21 = 0;
	fnode->run2n = 0;
	fnode->anode0 = 0;
	fnode->anode1 = 0;
	fnode->flags = 0;
	fnode->namelen = dentry->d_name.len;
	SPADFS_SET_NAME(fs, FNODE_NAME(fnode), dentry->d_name.name, dentry->d_name.len);
	ea = (struct ea_unx *)((char *)fnode + FNODE_EA_POS(dentry->d_name.len));
	SPADFS_INIT_UNX(dir, ea, mode);
	DO_FNODE_BLOCK_CHECKSUM(fs, fnode_block);
	end_concurrent_atomic_buffer_modify(bh);
	inode = SPADFS_NEW_INODE(dir, mode, ctime, ea, FNODE_EA_DO_ALIGN(sizeof(struct ea_unx)), fnode_address, fnode_off, 0);
	if (__unlikely(IS_ERR(inode))) {
		__brelse(bh);
		ND_UNLOCK(fs, wlock);
		return PTR_ERR(inode);
	}
	__brelse(bh);
	d_instantiate(dentry, inode);
	ND_UNLOCK(fs, wlock);
	return 0;
}

static int SPADFS_MKDIR(struct inode *dir, struct dentry *dentry, int mode)
{
	SPADFS *fs = spadfnode(dir)->fs;
	struct buffer_head *bh;
	__d_off fnode_address;
	unsigned fnode_off;
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	int wlock = 0;
	int synced = 0;
	time_t ctime;
	struct inode *inode;
	int r;
	struct ea_unx *ea;
	__d_off new_dir;
	__u16 hint_small, hint_big;

	mode |= S_IFDIR;

	lock_and_again:
	ND_LOCK(fs, wlock);
		/* if it were after SPADFS_ADD_FNODE_TO_DIRECTORY, it might deadlock */
	SPADFS_GET_DIR_HINT(spadfnode(dir), &hint_small, &hint_big);

	if (__unlikely(!spadfnode(dir)->fnode_block)) {
		ND_UNLOCK(fs, wlock);
		return -ENOENT;
	}
	r = SPADFS_ALLOC_LEAF_PAGE(spadfnode(dir), spadfnode(dir)->fnode_block, 1 << fs->sectors_per_block_bits, 0, &new_dir);
	if (__unlikely(r)) {
		if (__likely(r == -ENOSPC) && !synced) goto unlock_do_sync;
		unlock_ret_r:
		ND_UNLOCK(fs, wlock);
		return r;
	}
	fnode = SPADFS_ADD_FNODE_TO_DIRECTORY(spadfnode(dir), dentry->d_name.name, dentry->d_name.len, FNODE_EA_DO_ALIGN(sizeof(struct ea_unx)), &bh, &fnode_address, &fnode_off, &fnode_block, wlock);
	if (__unlikely(IS_ERR(fnode))) {
		r = SPADFS_FREE_BLOCKS(spadfnode(dir), new_dir, 1 << fs->sectors_per_block_bits);
		if (__unlikely(r)) goto unlock_ret_r;
		ND_UNLOCK(fs, wlock);
		if (__likely(fnode == ERR_PTR(-ENOSPC)) && !synced) goto do_sync;
		return PTR_ERR(fnode);
	}
	if (__unlikely(fnode == NEED_SYNC)) {
		r = SPADFS_FREE_BLOCKS(spadfnode(dir), new_dir, 1 << fs->sectors_per_block_bits);
		if (__unlikely(r)) goto unlock_ret_r;
		unlock_do_sync:
		ND_UNLOCK(fs, wlock);
		do_sync:
		synced = 1;
		if (__unlikely(r = SPADFS_COMMIT(fs))) return r;
		goto lock_and_again;
	}
	if (__unlikely(fnode == NEED_WLOCK)) {
		BUG_ON(wlock);
		r = SPADFS_FREE_BLOCKS(spadfnode(dir), new_dir, 1 << fs->sectors_per_block_bits);
		if (__unlikely(r)) goto unlock_ret_r;
		ND_UNLOCK(fs, wlock);
		wlock = 1;
		goto lock_and_again;
	}
	fnode->size0 = 0;
	fnode->size1 = 0;
	fnode->ctime = fnode->mtime = ctime = get_seconds();
	fnode->run10 = MAKE_PART_0(new_dir);
	fnode->run11 = MAKE_PART_1(new_dir);
	fnode->run20 = MAKE_PART_0(new_dir);
	fnode->run21 = MAKE_PART_1(new_dir);
	fnode->run1n = hint_small;
	fnode->run2n = hint_big;
	fnode->anode0 = 0;
	fnode->anode1 = 0;
	fnode->flags = FNODE_FLAGS_DIR;
	fnode->namelen = dentry->d_name.len;
	SPADFS_SET_NAME(fs, FNODE_NAME(fnode), dentry->d_name.name, dentry->d_name.len);
	ea = (struct ea_unx *)((char *)fnode + FNODE_EA_POS(dentry->d_name.len));
	SPADFS_INIT_UNX(dir, ea, mode);
	DO_FNODE_BLOCK_CHECKSUM(fs, fnode_block);
	end_concurrent_atomic_buffer_modify(bh);
	inode = SPADFS_NEW_INODE(dir, mode, ctime, ea, FNODE_EA_DO_ALIGN(sizeof(struct ea_unx)), fnode_address, fnode_off, new_dir);
	if (__unlikely(IS_ERR(inode))) {
		__brelse(bh);
		r = SPADFS_FREE_BLOCKS(spadfnode(dir), new_dir, 1 << fs->sectors_per_block_bits);
		if (__unlikely(r)) inode = ERR_PTR(r);
		ND_UNLOCK(fs, wlock);
		return PTR_ERR(inode);
	}
	__brelse(bh);
	d_instantiate(dentry, inode);
	ND_UNLOCK(fs, wlock);
	return 0;
}

static int SPADFS_MKNOD(struct inode *dir, struct dentry *dentry, int mode, dev_t rdev)
{
	SPADFS *fs = spadfnode(dir)->fs;
	struct buffer_head *bh;
	__d_off fnode_address;
	unsigned fnode_off;
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	int wlock = 0;
	int synced = 0;
	time_t ctime;
	struct inode *inode;
	int r;
	struct ea_unx *ea;
	int rdev_needed;

	if (__likely(S_ISFIFO(mode)) || __likely(S_ISSOCK(mode))) {
		rdev_needed = 0;
	} else if (__unlikely(S_ISBLK(mode)) || __unlikely(S_ISCHR(mode))) {
		rdev_needed = 1;
		if (__unlikely(!huge_valid_dev(rdev))) return -EINVAL;
	} else {
		return -EINVAL;
	}

	lock_and_again:
	ND_LOCK(fs, wlock);
	if (__unlikely(!spadfnode(dir)->fnode_block)) {
		ND_UNLOCK(fs, wlock);
		return -ENOENT;
	}
	fnode = SPADFS_ADD_FNODE_TO_DIRECTORY(spadfnode(dir), dentry->d_name.name, dentry->d_name.len, FNODE_EA_DO_ALIGN(sizeof(struct ea_unx)) + (rdev_needed ? FNODE_EA_DO_ALIGN(sizeof(struct ea_rdev)) : 0), &bh, &fnode_address, &fnode_off, &fnode_block, wlock);
	if (__unlikely(IS_ERR(fnode))) {
		ND_UNLOCK(fs, wlock);
		if (__likely(fnode == ERR_PTR(-ENOSPC)) && !synced) goto do_sync;
		return PTR_ERR(fnode);
	}
	if (__unlikely(fnode == NEED_SYNC)) {
		ND_UNLOCK(fs, wlock);
		do_sync:
		synced = 1;
		if (__unlikely(r = SPADFS_COMMIT(fs))) return r;
		goto lock_and_again;
	}
	if (__unlikely(fnode == NEED_WLOCK)) {
		BUG_ON(wlock);
		ND_UNLOCK(fs, wlock);
		wlock = 1;
		goto lock_and_again;
	}
	fnode->size0 = 0;
	fnode->size1 = 0;
	fnode->ctime = fnode->mtime = ctime = get_seconds();
	fnode->run10 = 0;
	fnode->run11 = 0;
	fnode->run1n = 0;
	fnode->run20 = 0;
	fnode->run21 = 0;
	fnode->run2n = 0;
	fnode->anode0 = 0;
	fnode->anode1 = 0;
	fnode->flags = 0;
	fnode->namelen = dentry->d_name.len;
	SPADFS_SET_NAME(fs, FNODE_NAME(fnode), dentry->d_name.name, dentry->d_name.len);
	ea = (struct ea_unx *)((char *)fnode + FNODE_EA_POS(dentry->d_name.len));
	SPADFS_INIT_UNX(dir, ea, mode);
	if (rdev_needed) {
		struct ea_rdev *rd = (struct ea_rdev *)((char *)ea + FNODE_EA_DO_ALIGN(sizeof(struct ea_unx)));
		rd->magic = EA_RDEV_MAGIC;
		rd->pad = 0;
		rd->dev = huge_encode_dev(rdev);
	}
	DO_FNODE_BLOCK_CHECKSUM(fs, fnode_block);
	end_concurrent_atomic_buffer_modify(bh);
	inode = SPADFS_NEW_INODE(dir, S_IFREG, ctime, ea, FNODE_EA_DO_ALIGN(sizeof(struct ea_unx)) + (rdev_needed ? FNODE_EA_DO_ALIGN(sizeof(struct ea_rdev)) : 0), fnode_address, fnode_off, 0);
	if (__unlikely(IS_ERR(inode))) {
		__brelse(bh);
		ND_UNLOCK(fs, wlock);
		return PTR_ERR(inode);
	}
	__brelse(bh);
	d_instantiate(dentry, inode);
	ND_UNLOCK(fs, wlock);
	return 0;
}

static int SPADFS_SYMLINK(struct inode *dir, struct dentry *dentry, const char *symlink)
{
	SPADFS *fs = spadfnode(dir)->fs;
	struct buffer_head *bh;
	__d_off fnode_address;
	unsigned fnode_off;
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	int wlock = 0;
	int synced = 0;
	time_t ctime;
	struct inode *inode;
	int r;
	struct fnode_ea *ea;
	unsigned strlen_symlink;
	unsigned ea_size;

	strlen_symlink = strlen(symlink);
	ea_size = FNODE_EA_DO_ALIGN(sizeof(struct fnode_ea) + strlen_symlink);
	if (__unlikely(ea_size > FNODE_MAX_EA_SIZE)) return -ENAMETOOLONG;

	lock_and_again:
	ND_LOCK(fs, wlock);
	if (__unlikely(!spadfnode(dir)->fnode_block)) {
		ND_UNLOCK(fs, wlock);
		return -ENOENT;
	}
	fnode = SPADFS_ADD_FNODE_TO_DIRECTORY(spadfnode(dir), dentry->d_name.name, dentry->d_name.len, ea_size, &bh, &fnode_address, &fnode_off, &fnode_block, wlock);
	if (__unlikely(IS_ERR(fnode))) {
		ND_UNLOCK(fs, wlock);
		if (__likely(fnode == ERR_PTR(-ENOSPC)) && !synced) goto do_sync;
		return PTR_ERR(fnode);
	}
	if (__unlikely(fnode == NEED_SYNC)) {
		ND_UNLOCK(fs, wlock);
		do_sync:
		synced = 1;
		if (__unlikely(r = SPADFS_COMMIT(fs))) return r;
		goto lock_and_again;
	}
	if (__unlikely(fnode == NEED_WLOCK)) {
		BUG_ON(wlock);
		ND_UNLOCK(fs, wlock);
		wlock = 1;
		goto lock_and_again;
	}
	fnode->size0 = 0;
	fnode->size1 = 0;
	fnode->ctime = fnode->mtime = ctime = get_seconds();
	fnode->run10 = 0;
	fnode->run11 = 0;
	fnode->run1n = 0;
	fnode->run20 = 0;
	fnode->run21 = 0;
	fnode->run2n = 0;
	fnode->anode0 = 0;
	fnode->anode1 = 0;
	fnode->flags = 0;
	fnode->namelen = dentry->d_name.len;
	SPADFS_SET_NAME(fs, FNODE_NAME(fnode), dentry->d_name.name, dentry->d_name.len);
	ea = (struct fnode_ea *)((char *)fnode + FNODE_EA_POS(dentry->d_name.len));
	ea->magic = EA_SYMLINK_MAGIC | (strlen_symlink << FNODE_EA_SIZE_SHIFT);
	strncpy((char *)(ea + 1), symlink, ea_size - sizeof(struct fnode_ea));
	DO_FNODE_BLOCK_CHECKSUM(fs, fnode_block);
	end_concurrent_atomic_buffer_modify(bh);
	inode = SPADFS_NEW_INODE(dir, S_IFREG, ctime, ea, ea_size, fnode_address, fnode_off, 0);
	if (__unlikely(IS_ERR(inode))) {
		__brelse(bh);
		ND_UNLOCK(fs, wlock);
		return PTR_ERR(inode);
	}
	__brelse(bh);
	d_instantiate(dentry, inode);
	ND_UNLOCK(fs, wlock);
	return 0;
}

static int SPADFS_LINK(struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry)
{
	struct inode *file = old_dentry->d_inode;
	struct inode *old_dir = old_dentry->d_parent->d_inode;
	SPADFS *fs = spadfnode(file)->fs;
	int r;
	__d_off link_address;
	unsigned link_off;
	struct fnode *fnode;
	struct fnode_block *link_fnode_block;
	struct buffer_head *bh;
	int synced = 0;
	lock_and_again:
	down_write(&fs->sync_lock);
	if (!is_fnode_fixed(spadfnode(file))) {
		/* this is the first link --- need to move file to its own new block */
		__d_off new_fxblk;
		__u16 hint_small, hint_big;
		SPADFS_GET_DIR_HINT(spadfnode(old_dir), &hint_small, &hint_big);
		if (__unlikely(spadfnode(file)->spadfs_nlink > 1)) {
			SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "FILE HAS MORE LINKS BUT IS IN DIRECTORY");
			r = -EFSERROR;
			goto unlock_ret_r;
		}
		r = SPADFS_ALLOC_FIXED_FNODE_BLOCK(spadfnode(old_dir), spadfnode(old_dir)->fnode_block, FNODE_SIZE(0, spadfnode(file)->ea_size), hint_small, hint_big, &new_fxblk);
		if (__unlikely(r)) {
			unlock_ret_r:
			up_write(&fs->sync_lock);
			return r;
		}
		fnode = SPADFS_ADD_FNODE_TO_DIRECTORY(spadfnode(old_dir), old_dentry->d_name.name, old_dentry->d_name.len, 0, &bh, &link_address, &link_off, &link_fnode_block, 1);
		if (__unlikely(IS_ERR(fnode))) {
			r = SPADFS_FREE_BLOCKS(spadfnode(old_dir), new_fxblk, 1 << fs->sectors_per_block_bits);
			if (__unlikely(r)) goto unlock_ret_r;
			add_fnode_error:
			up_write(&fs->sync_lock);
			if (__likely(fnode == ERR_PTR(-ENOSPC)) && !synced) goto do_sync;
			return PTR_ERR(fnode);
		}
		if (__unlikely(fnode == NEED_SYNC)) {
			r = SPADFS_FREE_BLOCKS(spadfnode(old_dir), new_fxblk, 1 << fs->sectors_per_block_bits);
			if (__unlikely(r)) goto unlock_ret_r;
			add_fnode_need_sync:
			up_write(&fs->sync_lock);
			do_sync:
			synced = 1;
			if (__unlikely(r = SPADFS_COMMIT(fs))) return r;
			goto lock_and_again;
		}
		BUG_ON(fnode == NEED_WLOCK);

		MAKE_FIXED_FNODE(fnode, new_fxblk);
		fnode->namelen = old_dentry->d_name.len;
		SPADFS_SET_NAME(fs, FNODE_NAME(fnode), old_dentry->d_name.name, old_dentry->d_name.len);
		DO_FNODE_BLOCK_CHECKSUM(fs, link_fnode_block);
		end_concurrent_atomic_buffer_modify(bh);

		SPADFS_REMOVE_FNODE_FROM_DIRECTORY(spadfnode(old_dir), spadfnode(file), &old_dentry->d_name);

		spadfnode(file)->fnode_block = new_fxblk;
		spadfnode(file)->fnode_pos = FIXED_FNODE_BLOCK_FNODE0;
		spadfnode(file)->spadfs_ino = make_fixed_spadfs_ino_t(new_fxblk);
		file->i_ino = spadfs_ino_t_2_ino_t(spadfnode(file)->spadfs_ino);
		spadfnode(file)->parent_fnode_block = 0;
		spadfnode(file)->parent_fnode_pos = 0;
		remove_inode_hash(file);
		insert_inode_hash(file);
		SPADFS_WRITE_FILE(spadfnode(file));
	}

	fnode = SPADFS_ADD_FNODE_TO_DIRECTORY(spadfnode(new_dir), new_dentry->d_name.name, new_dentry->d_name.len, 0, &bh, &link_address, &link_off, &link_fnode_block, 1);
	if (__unlikely(IS_ERR(fnode))) goto add_fnode_error;
	if (__unlikely(fnode == NEED_SYNC)) goto add_fnode_need_sync;
	BUG_ON(fnode == NEED_WLOCK);

	MAKE_FIXED_FNODE(fnode, spadfnode(file)->fnode_block);
	fnode->namelen = new_dentry->d_name.len;
	SPADFS_SET_NAME(fs, FNODE_NAME(fnode), new_dentry->d_name.name, new_dentry->d_name.len);
	DO_FNODE_BLOCK_CHECKSUM(fs, link_fnode_block);
	end_concurrent_atomic_buffer_modify(bh);

	/* if (!S_ISDIR(inode(file)->i_mode)) down(&spadfnode(new_file)->lock); already in fs write lock */
	spadfnode(file)->spadfs_nlink++;
	set_nlink(file);
	r = SPADFS_REFILE_FNODE(spadfnode(old_dir) /* doesn't matter */, spadfnode(file));
	if (__unlikely(r)) {
		spadfnode(file)->spadfs_nlink--;
		set_nlink(file);
	} else {
		atomic_inc(&file->i_count);
		d_instantiate(new_dentry, file);
	}
	/* if (!S_ISDIR(inode(file)->i_mode)) up(&spadfnode(new_file)->lock); */
	up_write(&fs->sync_lock);
	return r;
}

static __finline__ void SET_DELETED_FILE(SPADFNODE *file)
{
	if (__unlikely(is_fnode_fixed(file))) {
		SPADFS_FREE_BLOCKS(file, file->fnode_block, 1 << file->fs->sectors_per_block_bits);
	}
	file->fnode_block = 0;
	file->fnode_pos = 0;
	file->parent_fnode_block = 0;
	file->parent_fnode_pos = 0;
	file->spadfs_ino = spadfs_ino_t_deleted;
	inode(file)->i_ino = spadfs_ino_t_2_ino_t(spadfs_ino_t_deleted);
	remove_inode_hash(inode(file));
	insert_inode_hash(inode(file));
	file->spadfs_nlink = 0;
	inode(file)->i_nlink = 0;
}

static int SPADFS_UNLINK_UNLOCKED(struct inode *dir, struct dentry *dentry, int refcount)
{
	SPADFNODE *file = spadfnode(dentry->d_inode);
	int r = SPADFS_REMOVE_FNODE_FROM_DIRECTORY(spadfnode(dir), file, &dentry->d_name);
	if (__unlikely(r)) return r;
	if (__unlikely(file->spadfs_nlink != 1) && __likely(!S_ISDIR(inode(file)->i_mode))) {
		file->spadfs_nlink--;
		set_nlink(inode(file));
		r = SPADFS_REFILE_FNODE(spadfnode(dir), file);
		return r;
	}
	SET_DELETED_FILE(file);
	if (__likely(atomic_read(&dentry->d_count) <= refcount) && __likely(!S_ISDIR(inode(file)->i_mode))) {
		/* If the file is not open, delete its data atomically with sync_lock held.
		   If it is open and system crashes, there will be lost blocks. */
		SPADFS_DELETE_FILE_CONTENT(file);
	}
	return 0;
}

static int SPADFS_UNLINK(struct inode *dir, struct dentry *dentry)
{
	int r;
	SPADFNODE *file = spadfnode(dentry->d_inode);
	down_read(&file->fs->sync_lock);
	down(&file->lock);
	r = SPADFS_UNLINK_UNLOCKED(dir, dentry, 1);
	up(&file->lock);
	up_read(&file->fs->sync_lock);
	return r;
}

static int SPADFS_RMDIR(struct inode *dir, struct dentry *dentry)
{
	SPADFNODE *file = spadfnode(dentry->d_inode);
	int r;
	down_read(&file->fs->sync_lock);
	r = SPADFS_CHECK_DIR_EMPTY(file);
	if (__unlikely(r)) goto unlock_ret_r;
	r = SPADFS_REMOVE_RECURSIVE(file, file->root, 0);
	if (__unlikely(r)) goto unlock_ret_r;
	r = SPADFS_UNLINK_UNLOCKED(dir, dentry, 2);
	unlock_ret_r:
	up_read(&file->fs->sync_lock);
	return r;
}

static int SPADFS_RENAME(struct inode *old_dir, struct dentry *old_dentry, struct inode *new_dir, struct dentry *new_dentry)
{
	SPADFS *fs = spadfnode(old_dir)->fs;
	struct inode *old_file = old_dentry->d_inode;
	struct inode *new_file = new_dentry->d_inode;
	__d_off fnode_address;
	unsigned fnode_off;
	struct fnode_block *fnode_block;
	struct fnode *fnode;
	struct buffer_head *bh;
	int r;
	int synced = 0;
	__u16 hint_small, hint_big;

	lock_and_again:
	down_write(&fs->sync_lock);
		/* if it were after SPADFS_ADD_FNODE_TO_DIRECTORY, it would deadlock */
	if (__unlikely(S_ISDIR(old_file->i_mode)))
		SPADFS_GET_DIR_HINT(spadfnode(old_file), &hint_small, &hint_big);

	if (__unlikely(new_file != NULL)) {
		if (__unlikely(S_ISDIR(new_file->i_mode))) {
			r = SPADFS_CHECK_DIR_EMPTY(spadfnode(new_file));
			if (__unlikely(r)) goto unlock_ret_r;
		}
	}

	fnode = SPADFS_ADD_FNODE_TO_DIRECTORY(spadfnode(new_dir), new_dentry->d_name.name, new_dentry->d_name.len, __unlikely(is_fnode_fixed(spadfnode(old_file))) ? 0 : spadfnode(old_file)->ea_size, &bh, &fnode_address, &fnode_off, &fnode_block, 1);
	if (__unlikely(IS_ERR(fnode))) {
		up_write(&fs->sync_lock);
		if (__likely(fnode == ERR_PTR(-ENOSPC)) && !synced) goto do_sync;
		return PTR_ERR(fnode);
	}
	if (__unlikely(fnode == NEED_SYNC)) {
		up_write(&fs->sync_lock);
		do_sync:
		synced = 1;
		if (__unlikely(r = SPADFS_COMMIT(fs))) return r;
		goto lock_and_again;
	}
	BUG_ON(fnode == NEED_WLOCK);
	if (__unlikely(is_fnode_fixed(spadfnode(old_file)))) {
		MAKE_FIXED_FNODE(fnode, spadfnode(old_file)->fnode_block);
	} else {
		memcpy((char *)fnode + FNODE_EA_POS(new_dentry->d_name.len), spadfnode(old_file)->ea, spadfnode(old_file)->ea_size);
		if (__likely(!S_ISDIR(old_file->i_mode))) {
			SET_SPADFS_FILE(spadfnode(old_file), fnode, 1);
			fnode->flags = 0;
		} else {
			SET_SPADFS_DIRECTORY(spadfnode(old_file), fnode, 1);
			fnode->run1n = hint_small;
			fnode->run2n = hint_big;
			fnode->flags = FNODE_FLAGS_DIR;
		}
	}
	fnode->namelen = new_dentry->d_name.len;
	SPADFS_SET_NAME(fs, FNODE_NAME(fnode), new_dentry->d_name.name, new_dentry->d_name.len);
	DO_FNODE_BLOCK_CHECKSUM(fs, fnode_block);
	end_concurrent_atomic_buffer_modify(bh);
	SPADFS_REMOVE_FNODE_FROM_DIRECTORY(spadfnode(old_dir), spadfnode(old_file), &old_dentry->d_name);
	if (__likely(!is_fnode_fixed(spadfnode(old_file)))) {
		SPADFS_MOVE_FNODE_PTR(fs, spadfnode(old_file)->fnode_block, spadfnode(old_file)->fnode_pos, fnode_address, fnode_off, S_ISDIR(old_file->i_mode));
		spadfnode(old_file)->parent_fnode_block = spadfnode(new_dir)->fnode_block;
		spadfnode(old_file)->parent_fnode_pos = spadfnode(new_dir)->fnode_pos;
		/* already done by SPADFS_MOVE_FNODE_PTR
		spadfnode(old_file)->fnode_block = fnode_address;
		spadfnode(old_file)->fnode_pos = fnode_off;
		spadfnode(old_file)->spadfs_ino = make_spadfs_ino_t(fnode_address, fnode_off);
		old_file->i_ino = spadfs_ino_t_2_ino_t(spadfnode(old_file)->spadfs_ino);
		remove_inode_hash(old_file);
		insert_inode_hash(old_file);*/
		if (__unlikely(spadfnode(old_file)->fnode_block != fnode_address) ||
		    __unlikely(spadfnode(old_file)->fnode_pos != fnode_off) ||
		    __unlikely(spadfnode(old_file)->spadfs_ino != make_spadfs_ino_t(fnode_address, fnode_off)) ||
		    __unlikely(old_file->i_ino != spadfs_ino_t_2_ino_t(spadfnode(old_file)->spadfs_ino))) {
		    	panic("SPADFS_MOVE_FNODE_PTR DIDN'T DO ITS JOB: %LX != %LX || %X != %X || %LX != %LX || %lX != %lX", (unsigned long long)spadfnode(old_file)->fnode_block, (unsigned long long)fnode_address, spadfnode(old_file)->fnode_pos, fnode_off, (unsigned long long)spadfnode(old_file)->spadfs_ino, (unsigned long long)make_spadfs_ino_t(fnode_address, fnode_off), old_file->i_ino, spadfs_ino_t_2_ino_t(spadfnode(old_file)->spadfs_ino));
		}
	}
	if (__unlikely(new_file != NULL)) {
		if (__unlikely(S_ISDIR(new_file->i_mode))) {
			SPADFS_REMOVE_RECURSIVE(spadfnode(new_file), spadfnode(new_file)->root, 0);
		}
		SPADFS_UNLINK_UNLOCKED(new_dir, new_dentry, 2);
	}

	r = 0;
	unlock_ret_r:
	up_write(&fs->sync_lock);
	return r;
}

static int SPADFS_DIR_SETATTR(struct dentry *dentry, struct iattr *iattr)
{
	struct inode *inode = dentry->d_inode;
	int r = SPADFS_SETATTR_COMMON(inode, iattr);
	if (__unlikely(r)) return r;
	down_read(&spadfnode(inode)->fs->sync_lock);
	SPADFS_UPDATE_EA(inode);
	r = SPADFS_WRITE_DIRECTORY(spadfnode(inode));
	up_read(&spadfnode(inode)->fs->sync_lock);
	return r;
}

struct inode_operations spadfs_dir_iops = {
	.lookup = SPADFS_LOOKUP,
	.create = SPADFS_CREATE,
	.mkdir = SPADFS_MKDIR,
	.mknod = SPADFS_MKNOD,
	.symlink = SPADFS_SYMLINK,
	.link = SPADFS_LINK,
	.unlink = SPADFS_UNLINK,
	.rmdir = SPADFS_RMDIR,
	.rename = SPADFS_RENAME,
	.setattr = SPADFS_DIR_SETATTR,
	.getattr = SPADFS_GETATTR,
};

struct file_operations spadfs_dir_fops = {
	.llseek = generic_file_llseek,
	.read = generic_read_dir,
	.readdir = SPADFS_READDIR,
	.fsync = SPADFS_FSYNC,
};

