#include "SCK.H"

__u64 super_ptr[64];
__u64 tx_ptr[64];
int tx_valid;
unsigned block_mask;
unsigned dnode_hash_bits;
unsigned dnode_data_size;
unsigned dnode_page_sectors;

static __u64 detect_size(void)
{
	__u64 b = 1, c;
	if (__unlikely(!read_buffer(0, 1))) return 0;
	while (read_buffer(b, 1) && (b << 1 <= MAX_SIZE)) b <<= 1;
	b >>= 1;
	for (c = b >> 1; c; c >>= 1) if (read_buffer(b + c, 1)) b |= c;
	return b + 1;
}

void write_super(void)
{
	super.checksum = 0;
	super.checksum = CHECKSUM_BASE ^ __byte_sum(&super, 512);
	write_buffer(SUPERBLOCK_SECTOR, 1, &super);
}

void write_txblock(void)
{
	if (tx_valid) {
		tx.checksum = 0;
		tx.checksum = CHECKSUM_BASE ^ __byte_sum(&tx, 512);
		write_buffer(super.txblock, 1, &tx);
	}
}

void init_super(void)
{
	memset(&super, 0, 512);
	memset(&tx, 0, 512);
	block_mask = 0;
	tx_valid = 0;
}

static void fill_empty_txblock(void)
{
	tx.magic = TXBLOCK_MAGIC;
	tx.cc = 0xffff;
	tx.a_cc = 0;
	tx.a_txc = 0;
	tx.txflags = TXFLAGS_FS_ERROR;
}

int check_super(void)
{
	int r;
	struct superblock *s;
	struct txblock *t;
	char *m;
	status_printf(0, "checking super block...");
	if (__unlikely(!(s = read_buffer(SUPERBLOCK_SECTOR, 1)))) {
		log_printf(0, "UNABLE TO READ SUPER BLOCK: %s", buffer_error);
		return 1;
	}
	memcpy(&super, s, 512);
	if (__unlikely(memcmp(super.signature, SUPERBLOCK_SIGNATURE, sizeof super.signature))) {
		log_printf(0, "SPADFS SUPERBLOCK NOT FOUND");
		return 1;
	}
	if (__unlikely((__u32)(super.byte_sex & 0xFFFFFFFF) != 0x89ABCDEF || (__u32)(super.byte_sex >> 32) != 0x01234567)) {
		log_printf(0, "BYTE SEX DOES NOT MATCH");
		return 1;
	}
	if (__unlikely(super.version_major != SPADFS_VERSION_MAJOR) /*|| __unlikely(super.version_middle > SPADFS_VERSION_MIDDLE) feature flags are used instead */) {
		log_printf(0, "BAD %s VERSION NUMBER (DISK: %d.%d.%d, CKSPADFS: %d.%d.%d)", super.version_major != SPADFS_VERSION_MAJOR ? "MAJOR" : "MIDDLE", super.version_major, super.version_middle, super.version_minor, SPADFS_VERSION_MAJOR, SPADFS_VERSION_MIDDLE, SPADFS_VERSION_MINOR);
		return 1;
	}
	if (__unlikely(super.flags_compat_rw & ~(0)) || __unlikely(super.flags_compat_ro & ~(0)) || __unlikely(super.flags_compat_none & ~(FLAG_COMPAT_NONE_UNIX_NAMES))) {
		log_printf(0, "INCOMPATIBLE FEATURE FLAGS IN SUPERBLOCK: %08X/%08X/%08X/%08X", (unsigned)super.flags_compat_fsck, (unsigned)super.flags_compat_rw, (unsigned)super.flags_compat_ro, (unsigned)super.flags_compat_none);
		return 1;
	}
	if (__unlikely(__byte_sum(&super, 512) != CHECKSUM_BASE)) {
		log_printf(0, "BAD CHECKSUM ON SUPERBLOCK: %02X", __byte_sum(&super, 512) ^ CHECKSUM_BASE);
		if (query(qprefix 0, "FIX?")) {
			write_super();
		}
	}
	if (__unlikely(super.sectors_per_page_bits > __BSR(BUFFER_SIZE))) {
		log_printf(0, "SECTOR_PER_PAGE_BITS %d NOT SUPPORTED (MAX SUPPORTED %d)", super.sectors_per_page_bits, __BSR(BUFFER_SIZE));
		return 2;
	}
	if (__unlikely((m = validate_super(&super)) != NULL)) {
		log_printf(0, "INVALID PARAMETERS IN SUPERBLOCK: %s", m);
		return 1;
	}
	block_mask = (1 << super.sectors_per_block_bits) - 1;
	dnode_hash_bits = SSIZE_BITS + super.sectors_per_page_bits - DNODE_PAGE_ENTRY_BITS - 1;
	dnode_data_size = DNODE_PAGE_ENTRY_SIZE << dnode_hash_bits;
	dnode_page_sectors = DNODE_ENTRY_OFFSET + (dnode_data_size << 1);
	dnode_page_sectors = (dnode_page_sectors + (1 << SSIZE_BITS << super.sectors_per_block_bits) - 1) & ~((1 << SSIZE_BITS << super.sectors_per_block_bits) - 1);
	dnode_page_sectors >>= SSIZE_BITS;
	if (S_ISREG(st.st_mode)) goto skip_sizetest;
	if (__unlikely((super.size << SSIZE_BITS << 1) != (io_t)(super.size << SSIZE_BITS << 1))) {
		bad_system:
		log_printf(0, "YOUR SYSTEM HAS IO OFFSET LIMITED TO 32 BITS. CAN'T CHECK FILESYSTEM OF THIS SIZE");
		return 2;
	}
	if (__unlikely(super.size != (blk_t)super.size)) {
		recompile:
		log_printf(0, "TOO BIG SIZE, RECOMPILE WITH 64-BIT BLK_T");
		return 2;
	}
	if (__unlikely(!read_buffer(super.size - 1, 1))) {
		__u64 ds;
		log_printf(0, "CAN'T READ END OF FILE SYSTEM: %s", buffer_error);
		ds = detect_size();
		if (__unlikely((ds << SSIZE_BITS << 1) != (io_t)(ds << SSIZE_BITS << 1))) goto bad_system;
		if (__unlikely(ds != (blk_t)ds)) goto recompile;
		log_printf(0, "SUPERBLOCK HAS SIZE %"blk_format"X, DETECTED SIZE %"blk_format"X", (blk_t)super.size, (blk_t)ds);
		if (ds < super.size) {
			blk_t ss = super.size;
			super.size = ds & ~(blk_t)block_mask;
			if (__unlikely(validate_super(&super) != NULL)) {
				super.size = ss;
				log_printf(0, "DEVICE SIZE IS TOO SMALL - CAN'T RECOVER");
				return 1;
			}
#ifdef __linux__
			if (ss - ds < getpagesize() / 512)
				log_printf(1, "THIS ERROR CAN HAPPEN DUE TO BUG IN BUFFER CACHE IN OLDER LINUX KERNELS. DO NOT FIX IT AND TERMINATE SPADFSCK OR YOU MIGHT LOSE SOME DATA !!!");
#endif
			if (query(qprefix 1, "TRUNCATE? (WARNING!!! THIS OPERATION WILL DESTROY DATA IN INACCESSIBLE AREA OF DEVICE)")) {
				super.reserve_sectors = get_default_reserved(super.size) & ~(blk_t)block_mask;
				write_super();
			} else {
				super.size = ss;
				if (y) return 1;
				log_printf(1, "FURTHER ERRORS MIGHT BE CAUSED BY INACCESSIBLE AREA. IT IS NOT RECOMMENDED TO FIX THEM");
			}
		}
	}
	skip_sizetest:
	total_allocated = super.size;
	if (__unlikely(reserve != -1)) {
		reserve >>= 9;
		reserve &= ~(__u64)((1 << super.sectors_per_block_bits) - 1);
		if (__unlikely(reserve > super.size)) {
			log_printf(0, "REQUESTING TOO MANY RESERVED SECTORS, IGNORED");
		} else {
			super.reserve_sectors = reserve;
			write_super();
		}
		reserve = -1;
	}
	if (__unlikely(super.reserve_sectors > super.size)) {
		log_printf(0, "INVALID NUMBER OF RESERVED SECTORS (%"blk_format"X) < SIZE (%"blk_format"X)", super.reserve_sectors, super.size);
		if (query(qprefix 0, "FIX?")) {
			super.reserve_sectors = get_default_reserved(super.size) & ~(blk_t)block_mask;
			write_super();
		}
	}
	if (__unlikely(r = init_swapfile())) return r;
	if (__unlikely(r = alloc_blocks(0, SUPERBLOCK_SECTOR + (1 << super.sectors_per_block_bits)))) {
		if (r == 1) log_printf(0, "SUPERBLOCK ALLOCATION ERROR: %s", alloc_error);
		return r;
	}
	if (__unlikely(!(t = read_buffer(super.txblock, 1)))) {
		log_printf(0, "UNABLE TO READ TX BLOCK %"blk_format"X: %s", super.txblock, buffer_error);
		tx_error:
		fill_empty_txblock();
		return 0;
	}
	if (__unlikely(t->magic != TXBLOCK_MAGIC)) {
		log_printf(0, "BAD MAGIC ON TX BLOCK %"blk_format"X", super.txblock);
		goto tx_error;
	}
	if (__unlikely(__byte_sum(t, 512) != CHECKSUM_BASE)) {
		log_printf(0, "BAD CHECKSUM ON TX BLOCK %"blk_format"X: %02X", super.txblock, __byte_sum(t, 512) ^ CHECKSUM_BASE);
		goto tx_error;
	}
	if (__unlikely(r = alloc_blocks(super.txblock, (1 << super.sectors_per_block_bits)))) {
		if (r == 2) return 2;
		log_printf(0, "TX BLOCK ALLOCATION ERROR: %s", alloc_error);
		goto tx_error;
	}
	memcpy(&tx, t, sizeof(struct txblock));
	if (tx.txflags & TXFLAGS_DIRTY) log_printf(0, "FILESYSTEM HAS DIRTY FLAG");
	if (tx.txflags & TXFLAGS_IO_READ_ERROR) log_printf(0, "FILESYSTEM HAS I/O READ ERROR FLAG");
	if (tx.txflags & TXFLAGS_IO_WRITE_ERROR) log_printf(0, "FILESYSTEM HAS I/O WRITE ERROR FLAG");
	if (tx.txflags & TXFLAGS_FS_ERROR) log_printf(0, "FILESYSTEM HAS STRUCTURE ERROR FLAG");
	if (tx.txflags & TXFLAGS_CHECKSUM_ERROR) log_printf(0, "FILESYSTEM HAS CHECKSUM ERROR FLAG");
	if (tx.txflags & TXFLAGS_EA_ERROR) log_printf(0, "FILESYSTEM HAS EXTENDED ATTRIBUTE ERROR FLAG");
	if (tx.txflags & ~(TXFLAGS_DIRTY | TXFLAGS_IO_READ_ERROR | TXFLAGS_IO_WRITE_ERROR | TXFLAGS_FS_ERROR | TXFLAGS_CHECKSUM_ERROR | TXFLAGS_EA_ERROR)) log_printf(0, "FILESYSTEM HAS UNKNOWN ERROR FLAGS %08X", (unsigned)tx.txflags);
	tx_valid = 1;
	return 0;
}

int recover_super_allocs(void)
{
	int r;
	if (__unlikely(r = alloc_blocks(0, SUPERBLOCK_SECTOR + (1 << super.sectors_per_block_bits)))) {
		if (r == 1) log_printf(1, "FAILED TO RECOVER SUPERBLOCK ALLOCATION: %s", alloc_error);
		return 2;
	}
	if (tx_valid) {
		if (__unlikely(r = alloc_blocks(super.txblock, (1 << super.sectors_per_block_bits)))) {
			if (r == 1) log_printf(1, "FAILED TO RECOVER TX BLOCK ALLOCATION: %s", alloc_error);
			return 2;
		}
	}
	return 0;
}

int fix_txblock(void)
{
	int r;
	if (__likely(tx_valid)) return 0;
	status_printf(0, "fixing tx block...");
	r = alloc_try(super.txblock, 1);
	if (!r) {
		if (!query(qprefix 0, "WRITE TX BLOCK?")) return 0;
	} else {
		blk_t new_tx;
		if (__unlikely(r > 0)) return r;
		if (!query(qprefix 0, "ALLOC NEW TX BLOCK?")) return 0;
		if (__unlikely(r = alloc_space(1, "TX BLOCK", 1, 0, &new_tx))) return r;
		super.txblock = new_tx;
		write_super();
	}
	tx_valid = 1;
	write_txblock();
	return 0;
}

int fix_errorflag(void)
{
	if (!ro && __likely(tx_valid) && tx.txflags) {
		status_printf(0, "clearing error flags...");
		tx.txflags = 0;
		write_txblock();
	}
	return 0;
}

void done_super(void)
{
}
