#include "spadfs.h"

/* Helpers for iget5_locked */

static int SPADFS_IGET_TEST(struct inode *inode, void *data)
{
	spadfs_ino_t *ino = data;
	return spadfnode(inode)->spadfs_ino == *ino;
}

static int SPADFS_IGET_INIT(struct inode *inode, void *data)
{
	spadfs_ino_t *ino = data;
	inode->i_ino = spadfs_ino_t_2_ino_t(*ino);
	spadfnode(inode)->spadfs_ino = *ino;
	return 0;
}

/*
 * Read extended attributes and fill inode bits. On error, do not return error
 * and continue as if there were no extended attributes. However, error is
 * written to syslog and TXFLAGS_EA_ERROR bit is set on filesystem, so that fsck
 * will automatically correct it on next reboot.
 */

static __finline__ void SPADFS_GET_EA_UNX(struct inode *inode, struct fnode *fnode)
{
	__u16 mode;
	struct ea_unx *ea;
	struct fnode_ea *lnk;
	lnk = GET_EA((struct fnode_ea *)spadfnode(inode)->ea, spadfnode(inode)->ea_size, EA_SYMLINK_MAGIC, EA_SYMLINK_MAGIC_MASK);
	if (__likely(!lnk)) goto get_unx;
	if (__unlikely(lnk == GET_EA_ERROR)) goto parse_error;
	inode->i_mode = S_IFLNK | S_IRWXUGO;
	inode->i_uid = spadfnode(inode)->fs->uid;
	inode->i_gid = spadfnode(inode)->fs->gid;
	inode->i_op = &spadfs_symlink_iops;
	inode->i_size = lnk->magic >> FNODE_EA_SIZE_SHIFT;
	return;
	get_unx:
	ea = (struct ea_unx *)GET_EA((struct fnode_ea *)spadfnode(inode)->ea, spadfnode(inode)->ea_size, EA_UNX_MAGIC, EA_UNX_MAGIC_MASK);
	if (__unlikely(!ea)) return;
	if (__unlikely(ea == GET_EA_ERROR)) {
		parse_error:
		SPADFS_ERROR(spadfnode(inode)->fs, TXFLAGS_FS_ERROR, "ERROR PARSING EXTENDED ATTRIBUTES ON FNODE %LX/%X", (unsigned long long)spadfnode(inode)->fnode_block, spadfnode(inode)->fnode_pos);
		return;
	}
	spadfnode(inode)->ea_unx = ea;
	mode = ea->mode;
	if (S_ISDIR(mode)) {
		if (__unlikely(!S_ISDIR(inode->i_mode))) {
			SPADFS_ERROR(spadfnode(inode)->fs, TXFLAGS_EA_ERROR, "UNX EXTENDED ATTRIBUTE ERROR ON FNODE %LX/%X: NON-DIRECTORY HAS DIRECTORY MODE %06o", (unsigned long long)spadfnode(inode)->fnode_block, spadfnode(inode)->fnode_pos, mode);
			return;
		}
	} else {
		if (__unlikely(!S_ISREG(inode->i_mode))) {
			SPADFS_ERROR(spadfnode(inode)->fs, TXFLAGS_EA_ERROR, "UNX EXTENDED ATTRIBUTE ERROR ON FNODE %LX/%X: DIRECTORY HAS NON-DIRECTORY MODE %06o", (unsigned long long)spadfnode(inode)->fnode_block, spadfnode(inode)->fnode_pos, mode);
			return;
		}
		if (__unlikely(!S_ISREG(mode))) {
			dev_t rdev = 0;
			if (__likely(S_ISCHR(mode)) || S_ISBLK(mode)) {
				struct ea_rdev *rd = (struct ea_rdev *)GET_EA((struct fnode_ea *)spadfnode(inode)->ea, spadfnode(inode)->ea_size, EA_RDEV_MAGIC, ~0);
				if (__unlikely(!rd)) {
					SPADFS_ERROR(spadfnode(inode)->fs, TXFLAGS_EA_ERROR, "UNX EXTENDED ATTRIBUTE ERROR ON FNODE %LX/%X: NO RDEV ATTRIBUTE FOR FNODE WITH MODE %06o", (unsigned long long)spadfnode(inode)->fnode_block, spadfnode(inode)->fnode_pos, mode);
					return;
				}
				if (__unlikely(rd == GET_EA_ERROR)) goto parse_error;
				rdev = huge_decode_dev(rd->dev);
			} else if (S_ISFIFO(mode) || __likely(S_ISSOCK(mode))) {
			} else {
				SPADFS_ERROR(spadfnode(inode)->fs, TXFLAGS_EA_ERROR, "UNX EXTENDED ATTRIBUTE ERROR ON FNODE %LX/%X: FILE HAS INVALID MODE %06o", (unsigned long long)spadfnode(inode)->fnode_block, spadfnode(inode)->fnode_pos, mode);
				return;
			}
			init_special_inode(inode, mode, rdev);
		}
	}
	if (__likely(fnode != NULL)) {
		if (__likely(S_ISREG(mode))) {
			unsigned bs1;
			unsigned prealloc = CC_VALID(spadfnode(inode)->fs, fnode->cc, fnode->txc) ? ea->prealloc0 : ea->prealloc1;
			if (__unlikely(prealloc > spadfnode(inode)->disk_size)) {
				SPADFS_ERROR(spadfnode(inode)->fs, TXFLAGS_FS_ERROR, "UNX EXTENDED ATTRIBUTE ERROR ON FNODE %LX/%X: PREALLOC (%d) IS LARGER THAN FILE SIZE (%Ld)", (unsigned long long)spadfnode(inode)->fnode_block, spadfnode(inode)->fnode_pos, prealloc, (unsigned long long)spadfnode(inode)->disk_size);
				return;
			}
			inode->i_size = spadfnode(inode)->disk_size - prealloc;
			spadfnode(inode)->mmu_private = inode->i_size;
			bs1 = spadfnode(inode)->fs->blocksize - 1;
			spadfnode(inode)->prealloc = ((spadfnode(inode)->disk_size + bs1) & ~(off_t)bs1) - ((inode->i_size + bs1) & ~(off_t)bs1);
		} else {
			if (__unlikely((ea->prealloc0 | ea->prealloc1) != 0)) {
				SPADFS_ERROR(spadfnode(inode)->fs, TXFLAGS_FS_ERROR, "UNX EXTENDED ATTRIBUTE ERROR ON FNODE %LX/%X: FNODE WITH MODE %06o HAS NON-ZERO PREALLOC", (unsigned long long)spadfnode(inode)->fnode_block, spadfnode(inode)->fnode_pos, mode);
				return;
			}
		}
	}
	inode->i_mode = mode;
	inode->i_uid = ea->uid;
	inode->i_gid = ea->gid;
}

/*
 * Read inode. The inode to read is determined by (already filled) spadfs_ino
 * entry. The sector and position of inode is determined by spadfs_ino_t_sec and
 * spadfs_ino_t_pos macros from spadfs_ino. If position is 0, it is fixed fnode
 * (i.e. hardlink was created before to it) --- in this case spadfs_ino_t_sec
 * points to fixed fnode block.
 * If the fnode is in directory, parent is inode number of that directory, if
 * the fnode is fixed, parent is "don't care".
 */

static int SPADFS_READ_INODE(struct inode *inode, __d_off parent_block, unsigned parent_pos)
{
#define fx	((struct fixed_fnode_block *)fnode_block)
	struct fnode *fnode;
	struct buffer_head *bh;
	unsigned ea_pos, ea_size;
	SPADFS *fs = spadfs(inode->i_sb);
	unsigned pos = spadfs_ino_t_pos(spadfnode(inode)->spadfs_ino);
	struct fnode_block *fnode_block = SPADFS_READ_FNODE_BLOCK(fs, spadfnode(inode)->fnode_block = spadfs_ino_t_sec(spadfnode(inode)->spadfs_ino), &bh, SRFB_FNODE + __unlikely(is_pos_fixed(pos)) * (SRFB_FIXED_FNODE - SRFB_FNODE), "SPADFS_READ_INODE");
	if (__unlikely(IS_ERR(fnode_block))) {
		make_bad:
		make_bad_inode(inode);
		return PTR_ERR(fnode_block);
	}
	if (__unlikely(is_pos_fixed(pos))) {
		pos = FIXED_FNODE_BLOCK_FNODE1 - CC_VALID(fs, fx->cc, fx->txc) * (FIXED_FNODE_BLOCK_FNODE1 - FIXED_FNODE_BLOCK_FNODE0);
		parent_block = 0;
		parent_pos = 0;
		if (__unlikely(!*(__u64 *)((char *)fnode_block + pos - (FIXED_FNODE_BLOCK_FNODE0 - FIXED_FNODE_BLOCK_NLINK0)))) {
			SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "FIXED FNODE %LX HAS ZERO NLINK", (unsigned long long)spadfnode(inode)->fnode_block);
			goto make_bad_put_fserror;
		}
	}
	fnode = (struct fnode *)((char *)fnode_block + (spadfnode(inode)->fnode_pos = pos));

	ea_pos = FNODE_EA_POS(fnode->namelen);
	ea_size = (fnode->next & FNODE_NEXT_SIZE) - ea_pos;
	if (__unlikely(pos + ea_pos + ea_size > FNODE_BLOCK_SIZE) || __unlikely(ea_size > FNODE_MAX_EA_SIZE)) {
		SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "INVALID EXTENDED ATTRIBUTES ON FNODE %LX/%X", (unsigned long long)spadfnode(inode)->fnode_block, spadfnode(inode)->fnode_pos);
		make_bad_put_fserror:
		__brelse(bh);
		fnode_block = ERR_PTR(-EFSERROR);
		goto make_bad;
	}
	if (__unlikely(fnode->flags & FNODE_FLAGS_HARDLINK)) {
		SPADFS_ERROR(fs, TXFLAGS_FS_ERROR, "ATTEMPTING TO READ HARDLINK AS FNODE: %LX/%X", (unsigned long long)spadfnode(inode)->fnode_block, spadfnode(inode)->fnode_pos);
		goto make_bad_put_fserror;
	}
	spadfnode(inode)->ea_size = ea_size;
	memcpy(spadfnode(inode)->ea, (char *)fnode + ea_pos, ea_size);
	inode->i_mode = fs->mode;
	inode->i_uid = fs->uid;
	inode->i_gid = fs->gid;
	inode->i_atime = current_kernel_time();
	inode->i_ctime.tv_sec = fnode->ctime;
	inode->i_mtime.tv_sec = fnode->mtime;
	inode->i_ctime.tv_nsec = 0;
	inode->i_mtime.tv_nsec = 0;
	inode->i_generation = 0;
	spadfnode(inode)->parent_fnode_block = parent_block;
	spadfnode(inode)->parent_fnode_pos = parent_pos;
	spadfnode(inode)->ea_unx = NULL;
	spadfnode(inode)->spadfs_nlink = 1;
	if (__likely(!(fnode->flags & FNODE_FLAGS_DIR))) {
		if (__unlikely(is_fnode_fixed(spadfnode(inode)))) {
			spadfnode(inode)->spadfs_nlink = *FIXED_FNODE_NLINK_PTR(fnode);
		}
		inode->i_mode = (inode->i_mode & ~0111) | S_IFREG;
		spadfnode(inode)->disk_size = CC_VALID(fs, fnode->cc, fnode->txc) ? fnode->size0 : fnode->size1;
		spadfnode(inode)->prealloc = 0;
		inode->i_size = spadfnode(inode)->disk_size;
		spadfnode(inode)->mmu_private = inode->i_size;
		inode->i_blocks = SPADFS_SIZE_2_BLKS(fs, spadfnode(inode)->disk_size);
		spadfnode(inode)->blk1 = MAKE_D_OFF(fnode->run10, fnode->run11);
		spadfnode(inode)->blk1_n = fnode->run1n;
		spadfnode(inode)->blk2 = MAKE_D_OFF(fnode->run20, fnode->run21);
		spadfnode(inode)->blk2_n = fnode->run2n;
		spadfnode(inode)->extent_cache_pb = spadfnode(inode)->blk1;
		spadfnode(inode)->extent_cache_lb = 0;
		spadfnode(inode)->extent_cache_size = spadfnode(inode)->blk1_n;
		spadfnode(inode)->root = MAKE_D_OFF(fnode->anode0, fnode->anode1);
		inode->i_op = &spadfs_file_iops;
		inode->i_fop = &spadfs_file_fops;
		inode->i_data.a_ops = &spadfs_file_aops;
	} else {
		inode->i_mode |= S_IFDIR;
		inode->i_size = 0;
		inode->i_blocks = 0;
		spadfnode(inode)->root = CC_VALID(fs, fnode->cc, fnode->txc) ? MAKE_D_OFF(fnode->run10, fnode->run11) : MAKE_D_OFF(fnode->run20, fnode->run21);
		inode->i_op = &spadfs_dir_iops;
		inode->i_fop = &spadfs_dir_fops;
	}
	SPADFS_GET_EA_UNX(inode, fnode);
	/* See the comment about internal and external nlink counts at set_nlink */
	set_nlink(inode);
	__brelse(bh);
	return 0;
#undef fx
}

/* Get an inode from cache or read it from disk */

struct inode *SPADFS_IGET(struct super_block *s, spadfs_ino_t spadfs_ino, __d_off parent_block, unsigned parent_pos)
{
	struct inode *inode = iget5_locked(s, spadfs_ino_t_2_ino_t(spadfs_ino), SPADFS_IGET_TEST, SPADFS_IGET_INIT, &spadfs_ino);
	if (__unlikely(!inode)) {
		printk("SPADFS: UNABLE TO ALLOCATE INODE\n");
		return ERR_PTR(-ENOMEM);
	}
	if (__unlikely(inode->i_state & I_NEW)) {
		int r = SPADFS_READ_INODE(inode, parent_block, parent_pos);
		unlock_new_inode(inode);
		if (__unlikely(r)) {
			iput(inode);
			return ERR_PTR(r);
		}
	}
	return inode;
}

struct inode *SPADFS_NEW_INODE(struct inode *dir, mode_t mode, time_t ctime, void *ea, unsigned ea_size, __d_off fnode_block, unsigned fnode_pos, __d_off dir_root)
{
	spadfs_ino_t ino = make_spadfs_ino_t(fnode_block, fnode_pos);
	struct inode *inode;
	inode = new_inode(dir->i_sb);
	if (__unlikely(!inode)) return ERR_PTR(-ENOMEM);
	inode->i_uid = 0;
	inode->i_gid = 0;
	inode->i_mode = mode;
	inode->i_ino = spadfs_ino_t_2_ino_t(ino);
	inode->i_blocks = 0;
	inode->i_generation = 0;
	inode->i_ctime.tv_sec = ctime;
	inode->i_ctime.tv_nsec = 0;
	inode->i_mtime.tv_sec = ctime;
	inode->i_mtime.tv_nsec = 0;
	inode->i_atime.tv_sec = ctime;
	inode->i_atime.tv_nsec = 0;
	inode->i_nlink = 1;
	spadfnode(inode)->spadfs_nlink = 1;
	spadfnode(inode)->prealloc = 0;
	spadfnode(inode)->disk_size = 0;
	spadfnode(inode)->mmu_private = 0;
	spadfnode(inode)->spadfs_ino = ino;
	spadfnode(inode)->blk1 = 0;
	spadfnode(inode)->blk2 = 0;
	spadfnode(inode)->blk1_n = 0;
	spadfnode(inode)->blk2_n = 0;
	spadfnode(inode)->extent_cache_lb = 0;
	spadfnode(inode)->extent_cache_pb = 0;
	spadfnode(inode)->extent_cache_size = 0;
	spadfnode(inode)->root = dir_root;
	spadfnode(inode)->fnode_block = fnode_block;
	spadfnode(inode)->parent_fnode_block = spadfnode(dir)->fnode_block;
	spadfnode(inode)->fnode_pos = fnode_pos;
	spadfnode(inode)->parent_fnode_pos = spadfnode(dir)->fnode_pos;
	spadfnode(inode)->ea_unx = NULL;
	spadfnode(inode)->ea_size = ea_size;
	memcpy(spadfnode(inode)->ea, ea, ea_size);
	BUG_ON(ea_size & (FNODE_EA_ALIGN - 1));
	if (__unlikely(S_ISDIR(mode))) {
		inode->i_op = &spadfs_dir_iops;
		inode->i_fop = &spadfs_dir_fops;
	} else {
		inode->i_op = &spadfs_file_iops;
		inode->i_fop = &spadfs_file_fops;
		inode->i_data.a_ops = &spadfs_file_aops;
	}
	SPADFS_GET_EA_UNX(inode, NULL);
	insert_inode_hash(inode);
	return inode;
}

void SPADFS_DELETE_INODE(struct inode *i)
{
	SPADFS *fs;
	if (__unlikely(S_ISDIR(i->i_mode))) goto clr;
	truncate_inode_pages(&i->i_data, 0);
	fs = spadfnode(i)->fs;
	down_read(&fs->sync_lock);
	/*down(&spadfnode(i)->lock); no one can modify this inode anyway */
	SPADFS_DELETE_FILE_CONTENT(spadfnode(i));
	/*up(&spadfnode(i)->lock);*/
	up_read(&fs->sync_lock);
	clr:
	clear_inode(i);
}

int SPADFS_FSYNC(struct file *file, struct dentry *dentry, int datasync)
{
	struct inode *i = dentry->d_inode;
	int r = write_inode_now(i, 0);
	if (__unlikely(r)) return r;
	return SPADFS_COMMIT(spadfnode(i)->fs);
}

int SPADFS_SETATTR_COMMON(struct inode *inode, struct iattr *iattr)
{
	int error = inode_change_ok(inode, iattr);
	if (__unlikely(error)) return error;
	if ((iattr->ia_valid & ATTR_UID && iattr->ia_uid != inode->i_uid) ||
	    (iattr->ia_valid & ATTR_GID && iattr->ia_gid != inode->i_gid)) {
		error = DQUOT_TRANSFER(inode, iattr) ? -EDQUOT : 0;
		if (__unlikely(error)) return error;
	}
	return inode_setattr(inode, iattr);
}

int SPADFS_GETATTR(struct vfsmount *mnt, struct dentry *dentry, struct kstat *stat)
{
	struct inode *inode = dentry->d_inode;
	generic_fillattr(inode, stat);
	stat->blksize = spadfnode(inode)->fs->xfer_size;
#if 0
	/* Kernel returns -EOVERFLOW on stat32 if ino >= 2^32. This would cause
	   random program fails. */
	stat->ino = spadfs_ino_t_2_ino64_t(spadfnode(inode)->spadfs_ino);
#endif
	return 0;
}

void SPADFS_UPDATE_EA(struct inode *inode)
{
	if (__unlikely(!spadfnode(inode)->ea_unx)) return;
	spadfnode(inode)->ea_unx->mode = inode->i_mode;
	spadfnode(inode)->ea_unx->uid = inode->i_uid;
	spadfnode(inode)->ea_unx->gid = inode->i_gid;
}

void SPADFS_MOVE_FNODE_PTR(SPADFS *sfs, __d_off src_sec, unsigned src_pos, __d_off dst_sec, unsigned dst_pos, int is_dir)
{
	spadfs_ino_t spadfs_ino = make_spadfs_ino_t(src_sec, src_pos);
	struct inode *inode = ilookup5(sfs->s, spadfs_ino_t_2_ino_t(spadfs_ino), SPADFS_IGET_TEST, &spadfs_ino);
	if (__unlikely(inode != NULL)) {
		spadfnode(inode)->fnode_block = dst_sec;
		spadfnode(inode)->fnode_pos = dst_pos;
		spadfnode(inode)->spadfs_ino = make_spadfs_ino_t(dst_sec, dst_pos);
		inode->i_ino = spadfs_ino_t_2_ino_t(spadfnode(inode)->spadfs_ino);
		remove_inode_hash(inode);
		insert_inode_hash(inode);
		iput(inode);
	}
	if (__unlikely(is_dir)) {
		SPADFNODE *f;
		struct hlist_node *n;
		down(&sfs->inode_list_lock);
		hlist_for_each_entry(f, n, &sfs->inode_list, inode_list) {
			if (__unlikely(f->parent_fnode_block == src_sec) && f->parent_fnode_pos == src_pos) {
				f->parent_fnode_block = dst_sec;
				f->parent_fnode_pos = dst_pos;
			}
			cond_resched();
		}
		up(&sfs->inode_list_lock);
	}
}

