/**
 * dir.c
 *
 * Many parts of codes are copied from Linux kernel/fs/f2fs.
 *
 * Copyright (C) 2015 Huawei Ltd.
 * Witten by:
 *   Hou Pengyang <houpengyang@huawei.com>
 *   Liu Shuoran <liushuoran@huawei.com>
 *   Jaegeuk Kim <jaegeuk@kernel.org>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 */
#include "fsck.h"
#include "node.h"

static int room_for_filename(const u8 *bitmap, int slots, int max_slots)
{
	int bit_start = 0;
	int zero_start, zero_end;
next:
	zero_start = find_next_zero_bit_le(bitmap, max_slots, bit_start);
	if (zero_start >= max_slots)
		return max_slots;

	zero_end = find_next_bit_le(bitmap, max_slots, zero_start + 1);

	if (zero_end - zero_start >= slots)
		return zero_start;
	bit_start = zero_end;
	goto next;

}

void make_dentry_ptr(struct f2fs_dentry_ptr *d, struct f2fs_node *node_blk,
							void *src, int type)
{
	if (type == 1) {
		struct f2fs_dentry_block *t = (struct f2fs_dentry_block *)src;
		d->max = NR_DENTRY_IN_BLOCK;
		d->nr_bitmap = SIZE_OF_DENTRY_BITMAP;
		d->bitmap = t->dentry_bitmap;
		d->dentry = t->dentry;
		d->filename = t->filename;
	} else {
		int entry_cnt = NR_INLINE_DENTRY(node_blk);
		int bitmap_size = INLINE_DENTRY_BITMAP_SIZE(node_blk);
		int reserved_size = INLINE_RESERVED_SIZE(node_blk);

		d->max = entry_cnt;
		d->nr_bitmap = bitmap_size;
		d->bitmap = (u8 *)src;
		d->dentry = (struct f2fs_dir_entry *)
				((char *)src + bitmap_size + reserved_size);
		d->filename = (__u8 (*)[F2FS_SLOT_LEN])((char *)src +
				bitmap_size + reserved_size +
				SIZE_OF_DIR_ENTRY * entry_cnt);
	}
}

static struct f2fs_dir_entry *find_target_dentry(const u8 *name,
		unsigned int len, f2fs_hash_t namehash, int *max_slots,
		struct f2fs_dentry_ptr *d)
{
	struct f2fs_dir_entry *de;
	unsigned long bit_pos = 0;
	int max_len = 0;

	if (max_slots)
		*max_slots = 0;
	while (bit_pos < (unsigned long)d->max) {
		if (!test_bit_le(bit_pos, d->bitmap)) {
			bit_pos++;
			max_len++;
			continue;
		}

		de = &d->dentry[bit_pos];
		if (le16_to_cpu(de->name_len) == len &&
			de->hash_code == namehash &&
			!memcmp(d->filename[bit_pos], name, len)) {
			goto found;
		}

		if (max_slots && max_len > *max_slots)
			*max_slots = max_len;
		max_len = 0;
		bit_pos += GET_DENTRY_SLOTS(le16_to_cpu(de->name_len));
	}
	de = NULL;
found:
	if (max_slots && max_len > *max_slots)
		*max_slots = max_len;
	return de;
}

static struct f2fs_dir_entry *find_in_block(void *block,
		const u8 *name, int len, f2fs_hash_t namehash,
		int *max_slots)
{
	struct f2fs_dentry_ptr d;

	make_dentry_ptr(&d, NULL, block, 1);
	return find_target_dentry(name, len, namehash, max_slots, &d);
}

static int find_in_level(struct f2fs_sb_info *sbi,struct f2fs_node *dir,
		unsigned int level, struct dentry *de)
{
	unsigned int nbucket, nblock;
	unsigned int bidx, end_block;
	struct f2fs_dir_entry *dentry = NULL;
	struct dnode_of_data dn;
	void *dentry_blk;
	int max_slots = 214;
	nid_t ino = le32_to_cpu(dir->footer.ino);
	f2fs_hash_t namehash;
	unsigned int dir_level = dir->i.i_dir_level;
	int ret = 0;

	namehash = f2fs_dentry_hash(de->name, de->len);

	nbucket = dir_buckets(level, dir_level);
	nblock = bucket_blocks(level);

	bidx = dir_block_index(level, dir_level, le32_to_cpu(namehash) % nbucket);
	end_block = bidx + nblock;

	dentry_blk = calloc(BLOCK_SZ, 1);
	ASSERT(dentry_blk);

	memset(&dn, 0, sizeof(dn));
	for (; bidx < end_block; bidx++) {

		/* Firstly, we should know direct node of target data blk */
		if (dn.node_blk && dn.node_blk != dn.inode_blk)
			free(dn.node_blk);

		set_new_dnode(&dn, dir, NULL, ino);
		get_dnode_of_data(sbi, &dn, bidx, LOOKUP_NODE);
		if (dn.data_blkaddr == NULL_ADDR)
			continue;

		ret = dev_read_block(dentry_blk, dn.data_blkaddr);
		ASSERT(ret >= 0);

		dentry = find_in_block(dentry_blk, de->name, de->len,
						namehash, &max_slots);
		if (dentry) {
			ret = 1;
			de->ino = le32_to_cpu(dentry->ino);
			break;
		}
	}

	if (dn.node_blk && dn.node_blk != dn.inode_blk)
		free(dn.node_blk);
	free(dentry_blk);

	return ret;
}

static int f2fs_find_entry(struct f2fs_sb_info *sbi,
				struct f2fs_node *dir, struct dentry *de)
{
	unsigned int max_depth;
	unsigned int level;

	max_depth = le32_to_cpu(dir->i.i_current_depth);
	for (level = 0; level < max_depth; level ++) {
		if (find_in_level(sbi, dir, level, de))
			return 1;
	}
	return 0;
}

/* return ino if file exists, otherwise return 0 */
nid_t f2fs_lookup(struct f2fs_sb_info *sbi, struct f2fs_node *dir,
				u8 *name, int len)
{
	int err;
	struct dentry de = {
		.name = name,
		.len = len,
	};

	err = f2fs_find_entry(sbi, dir, &de);
	if (err == 1)
		return de.ino;
	else
		return 0;
}

static void f2fs_update_dentry(nid_t ino, int file_type,
		struct f2fs_dentry_ptr *d,
		const unsigned char *name, int len, f2fs_hash_t name_hash,
		unsigned int bit_pos)
{
	struct f2fs_dir_entry *de;
	int slots = GET_DENTRY_SLOTS(len);
	int i;

	de = &d->dentry[bit_pos];
	de->name_len = cpu_to_le16(len);
	de->hash_code = name_hash;
	memcpy(d->filename[bit_pos], name, len);
	d->filename[bit_pos][len] = 0;
	de->ino = cpu_to_le32(ino);
	de->file_type = file_type;
	for (i = 0; i < slots; i++)
		test_and_set_bit_le(bit_pos + i, d->bitmap);
}

/*
 * f2fs_add_link - Add a new file(dir) to parent dir.
 */
int f2fs_add_link(struct f2fs_sb_info *sbi, struct f2fs_node *parent,
			const unsigned char *name, int name_len, nid_t ino,
			int file_type, block_t p_blkaddr, int inc_link)
{
	int level = 0, current_depth, bit_pos;
	int nbucket, nblock, bidx, block;
	int slots = GET_DENTRY_SLOTS(name_len);
	f2fs_hash_t dentry_hash = f2fs_dentry_hash(name, name_len);
	struct f2fs_dentry_block *dentry_blk;
	struct f2fs_dentry_ptr d;
	struct dnode_of_data dn;
	nid_t pino = le32_to_cpu(parent->footer.ino);
	unsigned int dir_level = parent->i.i_dir_level;
	int ret;

	if (parent == NULL)
		return -EINVAL;

	if (!pino) {
		ERR_MSG("Wrong parent ino:%d \n", pino);
		return -EINVAL;
	}

	dentry_blk = calloc(BLOCK_SZ, 1);
	ASSERT(dentry_blk);

	current_depth = le32_to_cpu(parent->i.i_current_depth);
start:
	if (current_depth == MAX_DIR_HASH_DEPTH) {
		free(dentry_blk);
		ERR_MSG("\tError: MAX_DIR_HASH\n");
		return -ENOSPC;
	}

	/* Need a new dentry block */
	if (level == current_depth)
		++current_depth;

	nbucket = dir_buckets(level, dir_level);
	nblock = bucket_blocks(level);
	bidx = dir_block_index(level, dir_level, le32_to_cpu(dentry_hash) % nbucket);

	memset(&dn, 0, sizeof(dn));
	for (block = bidx; block <= (bidx + nblock - 1); block++) {

		/* Firstly, we should know the direct node of target data blk */
		if (dn.node_blk && dn.node_blk != dn.inode_blk)
			free(dn.node_blk);

		set_new_dnode(&dn, parent, NULL, pino);
		get_dnode_of_data(sbi, &dn, block, ALLOC_NODE);

		if (dn.data_blkaddr == NULL_ADDR) {
			new_data_block(sbi, dentry_blk, &dn, CURSEG_HOT_DATA);
		} else {
			ret = dev_read_block(dentry_blk, dn.data_blkaddr);
			ASSERT(ret >= 0);
		}
		bit_pos = room_for_filename(dentry_blk->dentry_bitmap,
				slots, NR_DENTRY_IN_BLOCK);

		if (bit_pos < NR_DENTRY_IN_BLOCK)
			goto add_dentry;
	}
	level ++;
	goto start;

add_dentry:
	make_dentry_ptr(&d, NULL, (void *)dentry_blk, 1);
	f2fs_update_dentry(ino, file_type, &d, name, name_len, dentry_hash, bit_pos);

	ret = dev_write_block(dentry_blk, dn.data_blkaddr);
	ASSERT(ret >= 0);

	/*
	 * Parent inode needs updating, because its inode info may be changed.
	 * such as i_current_depth and i_blocks.
	 */
	if (parent->i.i_current_depth != cpu_to_le32(current_depth)) {
		parent->i.i_current_depth = cpu_to_le32(current_depth);
		dn.idirty = 1;
	}

	/* Update parent's i_links info*/
	if (inc_link && (file_type == F2FS_FT_DIR)){
		u32 links = le32_to_cpu(parent->i.i_links);
		parent->i.i_links = cpu_to_le32(links + 1);
		dn.idirty = 1;
	}

	if ((__u64)((block + 1) * F2FS_BLKSIZE) >
					le64_to_cpu(parent->i.i_size)) {
		parent->i.i_size = cpu_to_le64((block + 1) * F2FS_BLKSIZE);
		dn.idirty = 1;
	}

	if (dn.ndirty) {
		ret = dev_write_block(dn.node_blk, dn.node_blkaddr);
		ASSERT(ret >= 0);
	}

	if (dn.idirty) {
		ASSERT(parent == dn.inode_blk);
		ret = dev_write_block(dn.inode_blk, p_blkaddr);
		ASSERT(ret >= 0);
	}

	if (dn.node_blk != dn.inode_blk)
		free(dn.node_blk);
	free(dentry_blk);
	return 0;
}

static void make_empty_dir(struct f2fs_sb_info *sbi, struct f2fs_node *inode)
{
	struct f2fs_dentry_block *dent_blk;
	nid_t ino = le32_to_cpu(inode->footer.ino);
	nid_t pino = le32_to_cpu(inode->i.i_pino);
	struct f2fs_summary sum;
	struct node_info ni;
	block_t blkaddr = NULL_ADDR;
	int ret;

	get_node_info(sbi, ino, &ni);

	dent_blk = calloc(BLOCK_SZ, 1);
	ASSERT(dent_blk);

	dent_blk->dentry[0].hash_code = 0;
	dent_blk->dentry[0].ino = cpu_to_le32(ino);
	dent_blk->dentry[0].name_len = cpu_to_le16(1);
	dent_blk->dentry[0].file_type = F2FS_FT_DIR;
	memcpy(dent_blk->filename[0], ".", 1);

	dent_blk->dentry[1].hash_code = 0;
	dent_blk->dentry[1].ino = cpu_to_le32(pino);
	dent_blk->dentry[1].name_len = cpu_to_le16(2);
	dent_blk->dentry[1].file_type = F2FS_FT_DIR;
	memcpy(dent_blk->filename[1], "..", 2);

	test_and_set_bit_le(0, dent_blk->dentry_bitmap);
	test_and_set_bit_le(1, dent_blk->dentry_bitmap);

	set_summary(&sum, ino, 0, ni.version);
	reserve_new_block(sbi, &blkaddr, &sum, CURSEG_HOT_DATA);

	ret = dev_write_block(dent_blk, blkaddr);
	ASSERT(ret >= 0);

	inode->i.i_addr[get_extra_isize(inode)] = cpu_to_le32(blkaddr);
	free(dent_blk);
}

static void page_symlink(struct f2fs_sb_info *sbi, struct f2fs_node *inode,
					const char *symname, int symlen)
{
	nid_t ino = le32_to_cpu(inode->footer.ino);
	struct f2fs_summary sum;
	struct node_info ni;
	char *data_blk;
	block_t blkaddr = NULL_ADDR;
	int ret;

	get_node_info(sbi, ino, &ni);

	/* store into inline_data */
	if ((unsigned long)(symlen + 1) <= MAX_INLINE_DATA(inode)) {
		inode->i.i_inline |= F2FS_INLINE_DATA;
		inode->i.i_inline |= F2FS_DATA_EXIST;
		memcpy(inline_data_addr(inode), symname, symlen);
		return;
	}

	data_blk = calloc(BLOCK_SZ, 1);
	ASSERT(data_blk);

	memcpy(data_blk, symname, symlen);

	set_summary(&sum, ino, 0, ni.version);
	reserve_new_block(sbi, &blkaddr, &sum, CURSEG_WARM_DATA);

	ret = dev_write_block(data_blk, blkaddr);
	ASSERT(ret >= 0);

	inode->i.i_addr[get_extra_isize(inode)] = cpu_to_le32(blkaddr);
	free(data_blk);
}

static void init_inode_block(struct f2fs_sb_info *sbi,
		struct f2fs_node *node_blk, struct dentry *de)
{
	struct f2fs_checkpoint *ckpt = F2FS_CKPT(sbi);
	mode_t mode = de->mode;
	int links = 1;
	unsigned int size;
	int blocks = 1;

	if (de->file_type == F2FS_FT_DIR) {
		mode |= S_IFDIR;
		size = 4096;
		links++;
		blocks++;
	} else if (de->file_type == F2FS_FT_REG_FILE) {
		mode |= S_IFREG;
		size = 0;
	} else if (de->file_type == F2FS_FT_SYMLINK) {
		ASSERT(de->link);
		mode |= S_IFLNK;
		size = strlen(de->link);
		if (size + 1 > MAX_INLINE_DATA(node_blk))
			blocks++;
	} else {
		ASSERT(0);
	}

	node_blk->i.i_mode = cpu_to_le16(mode);
	node_blk->i.i_advise = 0;
	node_blk->i.i_uid = cpu_to_le32(de->uid);
	node_blk->i.i_gid = cpu_to_le32(de->gid);
	node_blk->i.i_links = cpu_to_le32(links);
	node_blk->i.i_size = cpu_to_le32(size);
	node_blk->i.i_blocks = cpu_to_le32(blocks);
	node_blk->i.i_atime = cpu_to_le64(de->mtime);
	node_blk->i.i_ctime = cpu_to_le64(de->mtime);
	node_blk->i.i_mtime = cpu_to_le64(de->mtime);
	node_blk->i.i_atime_nsec = 0;
	node_blk->i.i_ctime_nsec = 0;
	node_blk->i.i_mtime_nsec = 0;
	node_blk->i.i_generation = 0;
	if (de->file_type == F2FS_FT_DIR)
		node_blk->i.i_current_depth = cpu_to_le32(1);
	else
		node_blk->i.i_current_depth = cpu_to_le32(0);
	node_blk->i.i_xattr_nid = 0;
	node_blk->i.i_flags = 0;
	node_blk->i.i_inline = F2FS_INLINE_XATTR;
	node_blk->i.i_pino = cpu_to_le32(de->pino);
	node_blk->i.i_namelen = cpu_to_le32(de->len);
	memcpy(node_blk->i.i_name, de->name, de->len);
	node_blk->i.i_name[de->len] = 0;

	if (c.feature & cpu_to_le32(F2FS_FEATURE_EXTRA_ATTR)) {
		node_blk->i.i_inline |= F2FS_EXTRA_ATTR;
		node_blk->i.i_extra_isize =
				cpu_to_le16(F2FS_TOTAL_EXTRA_ATTR_SIZE);
	}

	node_blk->footer.ino = cpu_to_le32(de->ino);
	node_blk->footer.nid = cpu_to_le32(de->ino);
	node_blk->footer.flag = 0;
	node_blk->footer.cp_ver = ckpt->checkpoint_ver;

	if (S_ISDIR(mode))
		make_empty_dir(sbi, node_blk);
	else if (S_ISLNK(mode))
		page_symlink(sbi, node_blk, de->link, size);

	if (c.feature & cpu_to_le32(F2FS_FEATURE_INODE_CHKSUM))
		node_blk->i.i_inode_checksum =
			cpu_to_le32(f2fs_inode_chksum(node_blk));
}

int convert_inline_dentry(struct f2fs_sb_info *sbi, struct f2fs_node *node,
							block_t p_blkaddr)
{
	struct f2fs_inode *inode = &(node->i);
	unsigned int dir_level = node->i.i_dir_level;
	nid_t ino = le32_to_cpu(node->footer.ino);
	char inline_data[MAX_INLINE_DATA(node)];
	struct dnode_of_data dn;
	struct f2fs_dentry_ptr d;
	unsigned long bit_pos = 0;
	int ret = 0;

	if (!(inode->i_inline & F2FS_INLINE_DENTRY))
		return 0;

	memcpy(inline_data, inline_data_addr(node), MAX_INLINE_DATA(node));
	memset(inline_data_addr(node), 0, MAX_INLINE_DATA(node));
	inode->i_inline &= ~F2FS_INLINE_DENTRY;

	ret = dev_write_block(node, p_blkaddr);
	ASSERT(ret >= 0);

	memset(&dn, 0, sizeof(dn));
	if (!dir_level) {
		struct f2fs_dentry_block *dentry_blk;
		struct f2fs_dentry_ptr src, dst;

		dentry_blk = calloc(BLOCK_SZ, 1);
		ASSERT(dentry_blk);

		set_new_dnode(&dn, node, NULL, ino);
		get_dnode_of_data(sbi, &dn, 0, ALLOC_NODE);
		if (dn.data_blkaddr == NULL_ADDR)
			new_data_block(sbi, dentry_blk, &dn, CURSEG_HOT_DATA);

		make_dentry_ptr(&src, node, (void *)inline_data, 2);
		make_dentry_ptr(&dst, NULL, (void *)dentry_blk, 1);

		 /* copy data from inline dentry block to new dentry block */
		memcpy(dst.bitmap, src.bitmap, src.nr_bitmap);
		memset(dst.bitmap + src.nr_bitmap, 0,
					dst.nr_bitmap - src.nr_bitmap);

		memcpy(dst.dentry, src.dentry, SIZE_OF_DIR_ENTRY * src.max);
		memcpy(dst.filename, src.filename, src.max * F2FS_SLOT_LEN);

		ret = dev_write_block(dentry_blk, dn.data_blkaddr);
		ASSERT(ret >= 0);

		MSG(1, "%s: copy inline entry to block\n", __func__);

		free(dentry_blk);
		return ret;
	}

	make_empty_dir(sbi, node);
	make_dentry_ptr(&d, node, (void *)inline_data, 2);

	while (bit_pos < (unsigned long)d.max) {
		struct f2fs_dir_entry *de;
		const unsigned char *filename;
		int namelen;

		if (!test_bit_le(bit_pos, d.bitmap)) {
			bit_pos++;
			continue;
		}

		de = &d.dentry[bit_pos];
		if (!de->name_len) {
			bit_pos++;
			continue;
		}

		filename = d.filename[bit_pos];
		namelen = le32_to_cpu(de->name_len);

		if (is_dot_dotdot(filename, namelen)) {
			bit_pos += GET_DENTRY_SLOTS(namelen);
			continue;
		}

		ret = f2fs_add_link(sbi, node, filename, namelen,
				le32_to_cpu(de->ino),
				de->file_type, p_blkaddr, 0);
		if (ret)
			MSG(0, "Convert file \"%s\" ERR=%d\n", filename, ret);
		else
			MSG(1, "%s: add inline entry to block\n", __func__);

		bit_pos += GET_DENTRY_SLOTS(namelen);
	}

	return 0;
}

int f2fs_create(struct f2fs_sb_info *sbi, struct dentry *de)
{
	struct f2fs_node *parent, *child;
	struct node_info ni;
	struct f2fs_summary sum;
	block_t blkaddr = NULL_ADDR;
	int ret;

	/* Find if there is a */
	get_node_info(sbi, de->pino, &ni);
	if (ni.blk_addr == NULL_ADDR) {
		MSG(0, "No parent directory pino=%x\n", de->pino);
		return -1;
	}

	parent = calloc(BLOCK_SZ, 1);
	ASSERT(parent);

	ret = dev_read_block(parent, ni.blk_addr);
	ASSERT(ret >= 0);

	/* Must convert inline dentry before the following opertions */
	ret = convert_inline_dentry(sbi, parent, ni.blk_addr);
	if (ret) {
		MSG(0, "Convert inline dentry for pino=%x failed.\n", de->pino);
		return -1;
	}

	ret = f2fs_find_entry(sbi, parent, de);
	if (ret) {
		MSG(0, "Skip the existing \"%s\" pino=%x ERR=%d\n",
					de->name, de->pino, ret);
		if (de->file_type == F2FS_FT_REG_FILE)
			de->ino = 0;
		goto free_parent_dir;
	}

	child = calloc(BLOCK_SZ, 1);
	ASSERT(child);

	f2fs_alloc_nid(sbi, &de->ino, 1);

	init_inode_block(sbi, child, de);

	ret = f2fs_add_link(sbi, parent, child->i.i_name,
				le32_to_cpu(child->i.i_namelen),
				le32_to_cpu(child->footer.ino),
				map_de_type(le16_to_cpu(child->i.i_mode)),
				ni.blk_addr, 1);
	if (ret) {
		MSG(0, "Skip the existing \"%s\" pino=%x ERR=%d\n",
					de->name, de->pino, ret);
		goto free_child_dir;
	}

	/* write child */
	set_summary(&sum, de->ino, 0, ni.version);
	reserve_new_block(sbi, &blkaddr, &sum, CURSEG_HOT_NODE);

	/* update nat info */
	update_nat_blkaddr(sbi, de->ino, de->ino, blkaddr);

	ret = dev_write_block(child, blkaddr);
	ASSERT(ret >= 0);

	update_free_segments(sbi);
	MSG(1, "Info: Create %s -> %s\n"
		"  -- ino=%x, type=%x, mode=%x, uid=%x, "
		"gid=%x, cap=%"PRIx64", size=%lu, pino=%x\n",
		de->full_path, de->path,
		de->ino, de->file_type, de->mode,
		de->uid, de->gid, de->capabilities, de->size, de->pino);
free_child_dir:
	free(child);
free_parent_dir:
	free(parent);
	return 0;
}

int f2fs_mkdir(struct f2fs_sb_info *sbi, struct dentry *de)
{
	return f2fs_create(sbi, de);
}

int f2fs_symlink(struct f2fs_sb_info *sbi, struct dentry *de)
{
	return f2fs_create(sbi, de);
}

int f2fs_find_path(struct f2fs_sb_info *sbi, char *path, nid_t *ino)
{
	struct f2fs_node *parent;
	struct node_info ni;
	struct dentry de;
	int err = 0;
	int ret;
	char *p;

	if (path[0] != '/')
		return -ENOENT;

	*ino = F2FS_ROOT_INO(sbi);
	parent = calloc(BLOCK_SZ, 1);
	ASSERT(parent);

	p = strtok(path, "/");
	while (p) {
		de.name = (const u8 *)p;
		de.len = strlen(p);

		get_node_info(sbi, *ino, &ni);
		if (ni.blk_addr == NULL_ADDR) {
			err = -ENOENT;
			goto err;
		}
		ret = dev_read_block(parent, ni.blk_addr);
		ASSERT(ret >= 0);

		ret = f2fs_find_entry(sbi, parent, &de);
		if (!ret) {
			err = -ENOENT;
			goto err;
		}

		*ino = de.ino;
		p = strtok(NULL, "/");
	}
err:
	free(parent);
	return err;
}