#ifndef _FSCK_H
#define _FSCK_H

#include "libgfs2.h"
#include "osi_tree.h"

#define FSCK_MAX_FORMAT (1802)

#define FSCK_HASH_SHIFT         (13)
#define FSCK_HASH_SIZE          (1 << FSCK_HASH_SHIFT)
#define FSCK_HASH_MASK          (FSCK_HASH_SIZE - 1)

#define query(cx, fmt, args...) fsck_query(cx, fmt, ##args)

/*
 * Exit codes used by fsck-type programs
 * Copied from e2fsck's e2fsck.h
 */
#define FSCK_OK          0      /* No errors */
#define FSCK_NONDESTRUCT 1      /* File system errors corrected */
#define FSCK_REBOOT      2      /* System should be rebooted */
#define FSCK_UNCORRECTED 4      /* File system errors left uncorrected */
#define FSCK_ERROR       8      /* Operational error */
#define FSCK_USAGE       16     /* Usage or syntax error */
#define FSCK_CANCELED    32     /* Aborted with a signal or ^C */
#define FSCK_LIBRARY     128    /* Shared library error */

#define BAD_POINTER_TOLERANCE 10 /* How many bad pointers is too many? */

struct bmap {
	uint64_t size;
	uint64_t mapsize;
	unsigned char *map;
};

struct inode_info
{
	struct osi_node node;
	struct lgfs2_inum num;
	uint32_t   di_nlink;    /* the number of links the inode
				 * thinks it has */
	uint32_t   counted_links; /* the number of links we've found */
};

struct dir_info
{
	struct osi_node node;
	struct lgfs2_inum dinode;
	uint64_t treewalk_parent;
	struct lgfs2_inum dotdot_parent;
	uint32_t di_nlink;
	uint32_t counted_links;
	uint8_t  checked:1;
};

struct dir_status {
	uint8_t dotdir:1;
	uint8_t dotdotdir:1;
	int q;
	uint32_t entry_count;
};

#define DUPFLAG_REF1_FOUND 1 /* Has the original reference been found? */
#define DUPFLAG_REF1_IS_DUPL 2 /* The original reference is also where we
				  determined there was a duplicate. */

struct duptree {
	struct osi_node node;
	int dup_flags;
	int refs;
	uint64_t block;
	osi_list_t ref_inode_list; /* list of inodes referencing a dup block */
	osi_list_t ref_invinode_list; /* list of invalid inodes referencing */
};

enum dup_ref_type {
	REF_AS_DATA = 0, /* dinode references this block as a data block */
	REF_AS_META = 1, /* dinode references this block as a metadata block */
	REF_AS_EA   = 2, /* dinode references this block as an extended attr */
	REF_IS_INODE= 3, /* The reference is itself a dinode.  In other words,
			    it's a dinode, not pointed to as data or
			    metadata */
	REF_TYPES   = 4,
};

struct inode_with_dups {
	osi_list_t list;
	uint64_t block_no;
	int dup_count;
	int reftypecount[REF_TYPES];
	uint64_t parent;
	char *name;
};

enum rgindex_trust_level { /* how far can we trust our RG index? */
	BLIND_FAITH = 0, /* We'd like to trust the rgindex. We always used to
			    before bz 179069. This should cover most cases. */
	YE_OF_LITTLE_FAITH = 1, /* The rindex seems trustworthy but there's
				   rg damage that need to be fixed. */
	OPEN_MINDED = 2, /* At least 1 RG is corrupt. Try to calculate what it
			    should be, in a perfect world where our RGs are all
			    on even boundaries. Blue sky. Chirping birds. */
	DISTRUST = 3,  /* The world isn't perfect, our RGs are not on nice neat
			  boundaries.  The fs must have been messed with by
			  gfs2_grow or something.  Count the RGs by hand. */
	INDIGNATION = 4 /* Not only do we have corruption, but the rgrps
			   aren't on even boundaries, so this file system
			   must have been converted from gfs2_convert. */
};

struct fsck_options {
	char *device;
	unsigned int yes:1;
	unsigned int no:1;
	unsigned int preen:1;
	unsigned int force:1;
};

struct fsck_cx {
	struct lgfs2_sbd *sdp;
	struct osi_root dup_blocks;
	struct osi_root dirtree;
	struct osi_root inodetree;
	const struct fsck_options * const opts;
	unsigned int jnl_size;
};

extern struct lgfs2_inode *fsck_load_inode(struct lgfs2_sbd *sdp, uint64_t block);
extern struct lgfs2_inode *fsck_inode_get(struct lgfs2_sbd *sdp,
					 struct lgfs2_rgrp_tree *rgd,
					 struct lgfs2_buffer_head *bh);
extern void fsck_inode_put(struct lgfs2_inode **ip);

extern int initialize(struct fsck_cx *cx, int *all_clean);
extern void destroy(struct fsck_cx *cx);
extern int pass1(struct fsck_cx *cx);
extern int pass1b(struct fsck_cx *cx);
extern int pass1c(struct fsck_cx *cx);
extern int pass2(struct fsck_cx *cx);
extern int pass3(struct fsck_cx *cx);
extern int pass4(struct fsck_cx *cx);
extern int pass5(struct fsck_cx *cx, struct bmap *bl);
extern int rindex_repair(struct fsck_cx *cx, int trust_lvl, int *ok);
extern int fsck_query(struct fsck_cx *cx, const char *format, ...)
	__attribute__((format(printf,2,3)));
extern struct dir_info *dirtree_find(struct fsck_cx *cx, uint64_t block);
extern void dup_delete(struct fsck_cx *cx, struct duptree *dt);
extern void dirtree_delete(struct fsck_cx *cx, struct dir_info *b);

/* FIXME: Hack to get this going for pass2 - this should be pulled out
 * of pass1 and put somewhere else... */
struct dir_info *dirtree_insert(struct fsck_cx *cx, struct lgfs2_inum inum);

extern struct lgfs2_inode *lf_dip; /* Lost and found directory inode */
extern int lf_was_created;
extern uint64_t last_fs_block, last_reported_block;
extern int64_t last_reported_fblock;
extern int skip_this_pass, fsck_abort;
extern int errors_found, errors_corrected;
extern uint64_t last_data_block;
extern uint64_t first_data_block;
extern int dups_found; /* How many duplicate references have we found? */
extern int dups_found_first; /* How many duplicates have we found the original
				reference for? */

static inline int valid_block(struct lgfs2_sbd *sdp, uint64_t blkno)
{
	return !((blkno > sdp->fssize) || (blkno <= LGFS2_SB_ADDR(sdp)) ||
	         (lgfs2_get_bitmap(sdp, blkno, NULL) < 0));
}

static inline int rgrp_contains_block(struct lgfs2_rgrp_tree *rgd, uint64_t blk)
{
	if (blk < rgd->rt_addr)
		return 0;
	if (blk >= rgd->rt_data0 + rgd->rt_data)
		return 0;
	return 1;
}

static inline int valid_block_ip(struct lgfs2_inode *ip, uint64_t blk)
{
	struct lgfs2_sbd *sdp = ip->i_sbd;
	struct lgfs2_rgrp_tree *rgd = ip->i_rgd;

	if (blk > sdp->fssize)
		return 0;
	if (blk <= LGFS2_SB_ADDR(sdp))
		return 0;
	if (rgd == NULL || !rgrp_contains_block(rgd, blk)) {
		rgd = lgfs2_blk2rgrpd(sdp, blk);
		if (rgd == NULL)
			return 0;
	}

	return rgrp_contains_block(rgd, blk);
}

struct special_blocks {
	osi_list_t list;
	uint64_t block;
};

extern struct special_blocks *blockfind(struct special_blocks *blist, uint64_t num);
extern void special_set(struct special_blocks *blocklist, uint64_t block);
extern void special_free(struct special_blocks *blist);
extern int sb_fixed;
extern int build_per_node(struct fsck_cx *cx);
extern int build_metadir(struct fsck_cx *cx);
extern int build_root(struct fsck_cx *cx);

#endif /* _FSCK_H */
