/* -*- mode: c; c-basic-offset: 8; -*-
 * vim: noexpandtab sw=8 ts=8 sts=0:
 *
 * find_block_inode.c
 *
 * Take a block number and returns the owning inode number.
 *
 * Copyright (C) 2006 Oracle.  All rights reserved.
 *
 * 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.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
 * Boston, MA  02110-1301 USA.
 *
 */

#include "main.h"

extern struct dbgfs_gbls gbls;

struct block_array {
	uint64_t blkno;
	uint32_t inode;		/* Backing inode# */
	uint64_t offset;
	int data;		/* offset is valid if this is set */
#define STATUS_UNKNOWN	0
#define STATUS_USED	1
#define STATUS_FREE	2
	int status;
};

static errcode_t lookup_regular(ocfs2_filesys *fs, uint64_t inode,
				struct ocfs2_extent_list *el,
				struct block_array *ba, int count, int *found)
{
	struct ocfs2_extent_block *eb;
	struct ocfs2_extent_rec *rec;
	errcode_t ret = 0;
	char *buf = NULL;
	int i;
	int j;
	uint64_t numblks;
	uint32_t clusters;

	if (*found >= count)
		return 0;

	ret = ocfs2_malloc_block(gbls.fs->fs_io, &buf);
	if (ret) {
		com_err(gbls.cmd, ret, "while allocating a block");
		goto bail;
	}

	eb = (struct ocfs2_extent_block *)buf;

	for (i = 0; i < el->l_next_free_rec; ++i) {
		rec = &(el->l_recs[i]);
		clusters = ocfs2_rec_clusters(el->l_tree_depth, rec);

		/*
		 * For a sparse file, we may find an empty record.
		 * Just skip it.
		 */
		if (!clusters)
			continue;

		if (el->l_tree_depth) {
			ret = ocfs2_read_extent_block(fs, rec->e_blkno, buf);
			if (ret) {
				com_err(gbls.cmd, ret, "while reading extent "
					"block %"PRIu64, (uint64_t)rec->e_blkno);
				goto bail;
			}

			for (j = 0; j < count; ++j) {
				if (ba[j].status != STATUS_UNKNOWN)
					continue;
				if (ba[j].blkno == rec->e_blkno) {
					ba[j].status = STATUS_USED;
					ba[j].inode = inode;
					(*found)++;
				}
			}

			lookup_regular(fs, inode, &(eb->h_list), ba, count,
				       found);

			continue;
		}

		for (j = 0; j < count; ++j) {
			if (ba[j].status != STATUS_UNKNOWN)
				continue;

			numblks = ocfs2_clusters_to_blocks(fs, clusters);

			if (ba[j].blkno >= rec->e_blkno &&
			    ba[j].blkno < rec->e_blkno + numblks) {
				ba[j].status = STATUS_USED;
				ba[j].inode = inode;
				ba[j].data = 1;
				ba[j].offset = ocfs2_clusters_to_blocks(fs, rec->e_cpos);
				ba[j].offset += ba[j].blkno - rec->e_blkno;
				(*found)++;
			}
		}

		if (*found >= count)
			return 0;
	}
bail:
	if (buf)
		ocfs2_free(&buf);
	return ret;
}

struct walk_it {
	char *buf;
	struct block_array *ba;
	uint64_t inode;
	int count;
	int found;
};

static int walk_chain_func(ocfs2_filesys *fs, uint64_t blkno, int chain,
			   void *priv_data)
{
	struct walk_it *wi = (struct walk_it *)priv_data;
	struct ocfs2_group_desc *gd;
	int i;
	errcode_t ret;

	ret = ocfs2_read_group_desc(fs, blkno, wi->buf);
	if (ret) {
		com_err(gbls.cmd, ret, "while reading group %"PRIu64, blkno);
		return ret;
	}

	gd = (struct ocfs2_group_desc *)wi->buf;

	for (i = 0; i < wi->count; ++i)
	{
		if (wi->ba[i].status != STATUS_UNKNOWN)
			continue;

		if (wi->ba[i].blkno == gd->bg_blkno) {
			wi->ba[i].status = STATUS_USED;
			wi->ba[i].inode = wi->inode;
			wi->found++;
		}

		if (wi->found >= wi->count)
			break;
	}

	return 0;
}

static errcode_t lookup_chain(ocfs2_filesys *fs, struct ocfs2_dinode *di,
			      struct block_array *ba, int count, int *found)
{
	struct walk_it wi;
	errcode_t ret = 0;

	memset(&wi, 0, sizeof(wi));
	wi.ba = ba;
	wi.inode = di->i_blkno;
	wi.count = count;
	wi.found = *found;

	ret = ocfs2_malloc_block(fs->fs_io, &wi.buf);
	if (ret) {
		com_err(gbls.cmd, ret, "while allocating a block");
		goto bail;
	}

	ret = ocfs2_chain_iterate(fs, di->i_blkno, walk_chain_func, &wi);
	if (ret) {
		com_err(gbls.cmd, ret, "while walking extents");
		goto bail;
	}

	*found = wi.found;

bail:
	if (wi.buf)
		ocfs2_free(&wi.buf);

	return ret;
}

static errcode_t lookup_global_bitmap(ocfs2_filesys *fs, uint64_t *blkno)
{
	char sysfile[50];
	errcode_t ret = 0;

	snprintf(sysfile, sizeof(sysfile), "%s",
		 ocfs2_system_inodes[GLOBAL_BITMAP_SYSTEM_INODE].si_name);

	ret = ocfs2_lookup(fs, fs->fs_sysdir_blkno, sysfile,
			   strlen(sysfile), NULL, blkno);
	if (ret)
		com_err(gbls.cmd, ret, "while looking up global bitmap");

	return ret;
}

static errcode_t scan_bitmap(ocfs2_filesys *fs, uint64_t bm_blkno,
			     struct block_array *ba, int count, int *found)
{
	ocfs2_cached_inode *ci = NULL;
	uint32_t num_cluster;
	errcode_t ret = 0;
	int set;
	int i;

	ret = ocfs2_read_cached_inode(fs, bm_blkno, &ci);
	if (ret) {
		com_err(gbls.cmd, ret, "while reading inode %"PRIu64, bm_blkno);
		goto bail;
	}

	ret = ocfs2_load_chain_allocator(fs, ci);
	if (ret) {
		com_err(gbls.cmd, ret, "while loading chain allocator");
		goto bail;
	}

	for (i = 0; i < count; ++i) {
		if (ba[i].status != STATUS_UNKNOWN)
			continue;

		num_cluster = ocfs2_blocks_to_clusters(fs, ba[i].blkno);

		ret = ocfs2_bitmap_test(ci->ci_chains, (uint64_t)num_cluster,
					&set);
		if (ret) {
			com_err(gbls.cmd, ret, "while looking up global bitmap");
			goto bail;
		}

		if (!set) {
			ba[i].status = STATUS_FREE;
			(*found)++;
		}
	}

bail:
	if (ci)
		ocfs2_free_cached_inode(fs, ci);

	return ret;
}

static void check_computed_blocks(ocfs2_filesys *fs, uint64_t gb_blkno,
				  struct block_array *ba, int count, int *found)
{
	uint64_t blks_in_superzone;
	uint32_t cpg;
	uint32_t bpg;
	uint64_t blkoff;
	uint32_t blks_in_cluster = ocfs2_clusters_to_blocks(fs, 1);
	int i;

	/* Blocks in the superblock zone */
	blks_in_superzone = ocfs2_clusters_to_blocks(fs, 1);
	if (blks_in_superzone < OCFS2_SUPER_BLOCK_BLKNO)
		blks_in_superzone = OCFS2_SUPER_BLOCK_BLKNO;

	for (i = 0; i < count; ++i) {
		if (ba[i].blkno <= blks_in_superzone) {
			ba[i].status = STATUS_USED;
			ba[i].inode = OCFS2_SUPER_BLOCK_BLKNO;
			(*found)++;
		}

		if (ba[i].blkno >= fs->fs_first_cg_blkno &&
		    ba[i].blkno < (fs->fs_first_cg_blkno + blks_in_cluster)) {
			ba[i].status = STATUS_USED;
			ba[i].inode = gb_blkno;
			(*found)++;
		}
	}

	if (*found >= count)
		return;

	cpg = ocfs2_group_bitmap_size(fs->fs_blocksize, 0,
			OCFS2_RAW_SB(fs->fs_super)->s_feature_incompat) * 8;
	bpg = ocfs2_clusters_to_blocks(fs, cpg);

	for (i = 0; i < count; ++i) {
		for (blkoff = bpg; blkoff < fs->fs_blocks; blkoff += bpg) {
			if (ba[i].blkno >= blkoff &&
			    ba[i].blkno < (blkoff + blks_in_cluster)) {
				ba[i].status = STATUS_USED;
				ba[i].inode = gb_blkno;
				(*found)++;
			}
		}
	}

	return;
}

errcode_t find_block_inode(ocfs2_filesys *fs, uint64_t *blkno, int count,
			   FILE *out)
{
	errcode_t ret = 0;
	uint64_t inode_num;
	struct block_array *ba = NULL;
	char *buf = NULL;
	struct ocfs2_dinode *di;
	ocfs2_inode_scan *scan = NULL;
	int i;
	int found = 0;
	uint64_t gb_blkno;

	ret = ocfs2_malloc_block(fs->fs_io, &buf);
	if (ret) {
		com_err(gbls.cmd, ret, "while allocating a block");
		goto out;
	}
	di = (struct ocfs2_dinode *)buf;

	ba = calloc(count, sizeof(struct block_array));
	if (!ba) {
		com_err(gbls.cmd, ret, "while allocating memory");
		goto out;
	}

	for (i = 0; i < count; ++i)
		ba[i].blkno = blkno[i];

	if (found >= count)
		goto output;

	ret = lookup_global_bitmap(fs, &gb_blkno);
	if (ret)
		goto out_free;

	check_computed_blocks(fs, gb_blkno, ba, count, &found);
	if (found >= count)
		goto output;

	ret = scan_bitmap(fs, gb_blkno, ba, count, &found);
	if (ret)
		goto out_free;

	if (found >= count)
		goto output;

	ret = ocfs2_open_inode_scan(fs, &scan);
	if (ret) {
		com_err(gbls.cmd, ret, "while opening inode scan");
		goto out_free;
	}

	for (;;) {
		ret = ocfs2_get_next_inode(scan, &inode_num, (char *)di);
		if (ret) {
			com_err(gbls.cmd, ret, "while scanning next inode");
			goto out_close_scan;
		}

		if (!inode_num)
			break;

		if (memcmp(di->i_signature, OCFS2_INODE_SIGNATURE,
			   strlen(OCFS2_INODE_SIGNATURE)))
			continue;

		ocfs2_swap_inode_to_cpu(fs, di);

		if (di->i_fs_generation != fs->fs_super->i_fs_generation)
			continue;

		if (!(di->i_flags & OCFS2_VALID_FL))
			continue;

		for (i = 0; i < count; ++i) {
			if (ba[i].status != STATUS_UNKNOWN)
				continue;
			if (ba[i].blkno == di->i_blkno) {
				ba[i].status = STATUS_USED;
				ba[i].inode = di->i_blkno;
				found++;
			}
		}

		if (found >= count)
			break;

		if (S_ISLNK(di->i_mode) && !di->i_clusters)
			continue;

		if (di->i_flags & (OCFS2_LOCAL_ALLOC_FL | OCFS2_DEALLOC_FL))
			continue;

		if (di->i_blkno == gb_blkno)
			continue;

		if (di->i_flags & OCFS2_CHAIN_FL)
			ret = lookup_chain(fs, di, ba, count, &found);
		else
			ret = lookup_regular(fs, di->i_blkno, &(di->id2.i_list),
					     ba, count, &found);
		if (ret)
			goto out_close_scan;

		if (found >= count)
			break;
	}

output:
	for (i = 0; i < count; ++i)
		dump_icheck(out, (i == 0), ba[i].blkno, ba[i].inode,
			    ba[i].data, ba[i].offset, ba[i].status);

out_close_scan:
	if (scan)
		ocfs2_close_inode_scan(scan);

out_free:
	if (buf)
		ocfs2_free(&buf);
	if (ba)
		free(ba);
out:
	return 0;
}
