#include "SCK.H"

typedef struct __subdir SUBDIR;

struct __subdir {
	LIST_ENTRY list;
	LIST_ENTRY todolist;
	LIST_HEAD subdirs;
	LIST_HEAD blocks;
	blk_t fnode;
	SUBDIR *parent;
	int file;
	int dir_depth;
	int dnode_depth;
	__u32 hash_val;
	__u32 hash_mask;
	char name[1];
};

blk_t root_dir;

static SUBDIR root;
static LIST_HEAD todolist;

static int process_node(SUBDIR *d);
static int check_fnode_block(SUBDIR *d, blk_t blk);
static int check_dnode(SUBDIR *d, blk_t blk);
static void free_node(SUBDIR *d);
static void free_tree(void);
static char *get_filename(void *d_);

static void print_alloc_progress(void)
{
	print_progress(total_allocated, blocks_allocated, 1);
}

int check_dir_tree(void)
{
	int r;
	struct fixed_fnode_block *fx;
	struct fnode *fnode;
	unsigned ea_size;
	char *msg;
	blk_t blk, nlink;

	status_printf(0, "checking directories and files...");
	again:
	hardlink_init();
	INIT_LIST(&todolist);
	memset(&root, 0, sizeof(SUBDIR));
	INIT_LIST(&root.subdirs);

	r = read_fixed_fnode(super.root, 1, &fx, &fnode, get_filename, &root);
	if (__unlikely(r)) {
		if (r == -1) r = 1;
		if (r == 1) log_printf(0, "CAN'T READ ROOT FNODE");
		hardlink_done_ret_r:
		hardlink_done();
		return r;
	}

	ea_size = (fnode->next & FNODE_NEXT_SIZE) - FNODE_EA_POS(0);
	if (__unlikely((msg = check_ea(fnode, (struct fnode_ea *)((char *)fnode + FNODE_EA_POS(0)), ea_size, EA_TYPE_FILE)) != NULL)) {
		log_printf(0, "%s: INVALID EXTENDED ATTRIBUTES: %s", get_filename(&root), msg);
		if (query(qprefix 0, "FIX?")) {
			fix_ea(fnode, (struct fnode_ea *)((char *)fnode + FNODE_EA_POS(0)), &ea_size, EA_TYPE_FILE);
			if (__unlikely(!(fx = update_fixed_fnode_block(fx, super.root)))) {
				log_printf(0, "%s: ERROR UPDATING FIXED FNODE: %s", get_filename(&root), buffer_error);
				r = 1;
				goto hardlink_done_ret_r;
			}
			fnode = fixed_fnode_block_fnode(fx, 0);
		}
	}
	
	if (cc_valid(fnode->cc, fnode->txc)) root_dir = __make64(fnode->run10, fnode->run11);
	else root_dir = __make64(fnode->run20, fnode->run21);
	r = get_fnode_description(root_dir, &root.blocks, get_filename, &root);
	if (__unlikely(r)) {
		if (r != 2) log_printf(0, "CAN'T ALLOCATE ROOT DIRECTORY"), r = 1;
		goto hardlink_done_ret_r;
	}

	ADD_TO_LIST(&todolist, &root.todolist);
	process_todolist:
	while (!LIST_EMPTY(&todolist)) {
		SUBDIR *s = LIST_STRUCT(todolist.next, SUBDIR, todolist);
		DEL_FROM_LIST(&s->todolist);
		if (__unlikely(r = process_node(s))) {
			free_tree();
			goto hardlink_done_ret_r;
		}
		while (LIST_EMPTY(&s->subdirs) && __likely(s != &root)) {
			SUBDIR *p = s->parent;
			free_node(s);
			s = p;
		}
		print_alloc_progress();
	}
	if (__likely(!cross_link_pass)) {
		if (__unlikely(were_cross_links)) {
			log_printf(0, "CROSS LINKED FILES DETECTED, PROCESSING THEM IN NEXT TWO PASSES");
			cross_link_pass = 1;
			free_tree();
			hardlink_done();
			if (__unlikely(r = reset_allocations())) return r;
			status_printf(0, "checking for cross linked metadata...");
			goto again;
		}
	}
	if (__unlikely(cross_link_pass == 1)) {
		cross_link_pass = 2;
		free_tree();
		hardlink_done();
		status_printf(0, "checking for cross linked data...");
		goto again;
	}
	free_tree();
	if (__unlikely(r = do_directories_recovery())) {
		hardlink_done();
		return r;
	}
	if (__unlikely(!LIST_EMPTY(&todolist))) {
		cross_link_pass = 3;
		status_printf(0, "checking recently recovered directories...");
		goto process_todolist;
	}
	while (__unlikely(hardlink_search_for_bad_nlink(&blk, &nlink))) {
		struct fixed_fnode_block *fx;
		struct fnode *fx_fn;
		fx = reread_fixed_fnode_block(blk);
		if (__unlikely(!fx)) {
			log_printf(1, "ERROR READING FIXED FNODE BLOCK %"blk_format"X: %s", blk, buffer_error);
			log_printf(1, "THE DEVICE IS UNRELIABLE, BLOCK HAS BEEN CORRECT BEFORE");
			continue;
		}
		fx_fn = fixed_fnode_block_fnode(fx, 0);
		log_printf(0, "WRONG NLINK FOR FIXED FNODE BLOCK %"blk_format"X: %"blk_format"X, SHOULD BE %"blk_format"X", blk, *FIXED_FNODE_NLINK_PTR(fx_fn), nlink);
		if (__unlikely(*FIXED_FNODE_NLINK_PTR(fx_fn) == nlink)) {
			log_printf(1, "SOMETHING WEIRD HAPPENED, LINK COUNT WAS FIXED UNDER US");
			log_printf(1, "THE DEVICE IS UNRELIABLE");
		}
		if (query(qprefix 0, "FIX?")) {
			*FIXED_FNODE_NLINK_PTR(fx_fn) = nlink;
			if (__unlikely(!update_fixed_fnode_block(fx, blk))) {
				log_printf(1, "ERROR UPDATING FIXED FNODE BLOCK %"blk_format"X: %s", blk, buffer_error);
				log_printf(1, "THE DEVICE IS UNRELIABLE, BLOCK HAS BEEN CORRECT BEFORE");
				continue;
			}
		}
	}
	hardlink_done();
	if (__unlikely(r = do_files_recovery())) return r;
	return 0;
}

static int process_node(SUBDIR *d)
{
	FNODE_DESC *fd;
	LIST_FOR_EACH(fd, &d->blocks, FNODE_DESC, list) {
		unsigned c;
		if (fd->dno) {
			return check_dnode(d, fd->start);
		}
		for (c = 0; c < fd->len; c++) {
			int r = check_fnode_block(d, fd->start + c);
			if (__unlikely(r)) return r;
		}
	}
	return 0;
}

static int check_fnode_block(SUBDIR *d, blk_t blk)
{
	int recover_all = 0;
	unsigned fp;
	struct fnode_block *f = read_buffer(blk, 1);
	if (__unlikely(!f)) {
		read_err:
		log_printf(1, "%s: ERROR ACCESSING FNODE BLOCK %"blk_format"X: %s", get_filename(d), blk, buffer_error);
		log_printf(1, "THE DEVICE IS UNRELIABLE, FNODE HAS BEEN SUCCESSFULY READ BEFORE");
		return 1;
	}
#define reread_block							\
do {									\
	if (__unlikely(!(f = read_buffer(blk, 1)))) goto read_err;	\
} while (0)
#define update_block							\
do {									\
	f->flags |= FNODE_BLOCK_CHECKSUM_VALID;				\
	f->checksum ^= CHECKSUM_BASE ^ __byte_sum(f, FNODE_BLOCK_SIZE);	\
	if (__unlikely(write_buffer(blk, 1, f))) goto read_err;		\
	reread_block;							\
} while (0)
	if (__unlikely(f->magic != FNODE_BLOCK_MAGIC)) {
		log_printf(1, "%s: BAD MAGIC WHEN RE-READING FNODE BLOCK %"blk_format"X: %08X", get_filename(d), blk, f->magic);
		log_printf(1, "THE DEVICE IS UNRELIABLE, MAGIC HAS BEEN CORRECT BEFORE");
		return 1;
	}
	if (f->flags & FNODE_BLOCK_CHECKSUM_VALID && __unlikely(__byte_sum(f, FNODE_BLOCK_SIZE) != CHECKSUM_BASE)) {
		log_printf(0, "%s: BAD CHECKSUM ON FNODE BLOCK %"blk_format"X: %02X", get_filename(d), blk, __byte_sum(f, FNODE_BLOCK_SIZE) ^ CHECKSUM_BASE);
		if (query(qprefix 0, "FIX?")) {
			update_block;
		}
		if (query(qprefix 0, "DELAY PROCESSING ALL REFERENCES FROM THIS BLOCK?")) {
			recover_all |= 1;
		}
	}
	fp = SIZEOF_FNODE_BLOCK;
#define fn ((struct fnode *)((char *)f + fp))
	do {
		int i;
		int dot;
		__u32 hash;
		unsigned size = fn->next & FNODE_NEXT_SIZE;
		unsigned eapos;
		char filename[256];
		SUBDIR *subdir;
		int different_hash_error, duplicate_filename_error, ea_error;
		blk_t link;
		struct fixed_fnode_block *fx;
		struct fnode *fx_fn;
		struct fnode f1;
		RECOVERY_FILEDESC rfd;
		struct {
			__u64 force_alignment;
			char ea[FNODE_MAX_EA_SIZE];
		} ea;
		unsigned ea_size;
		struct {
			__u64 force_alignment;
			char name_ea[FNODE_MAX_EA_SIZE];
		} name_ea;
		unsigned name_ea_size;
		if (__unlikely(cc_check(&fn->cc, &fn->txc, "FNODE", get_filename, d, blk))) update_block;
		if (cc_valid(fn->cc, fn->txc) && fn->next & FNODE_NEXT_FREE) {
			if (__unlikely(size < FNODE_HEAD_SIZE) || __unlikely(fp + size > FNODE_BLOCK_SIZE)) {
				if (recover_all & 2) goto delete_invalid_fnode;
				log_printf(0, "%s: FNODE BLOCK %"blk_format"X: INVALID FREE FNODE SIZE %X AT %X", get_filename(d), blk, fn->next, fp);
				if (query(qprefix 0, "FIX?")) {
					delete_invalid_fnode:
					fn->cc = 0;
					fn->txc = 0;
					size = FNODE_BLOCK_SIZE - fp;
					if (recover_all & 2 || query(qprefix 0, "TRY TO FIND MORE DIRECTORY ENTRIES IN THIS BLOCK?")) {
						recover_all |= 2;
						size = FNODE_EMPTY_MIN_SIZE;
					}
					fn->next = (fn->next & ~FNODE_NEXT_SIZE) | size | FNODE_NEXT_FREE;
					update_block;
					goto cont;
				}
				return 0;
			}
			goto cont;
		}
		if (__unlikely(size <= FNODE_NAME_POS) || __unlikely(fp + size > FNODE_BLOCK_SIZE) || __unlikely(FNODE_NAME_POS + fn->namelen > size) || __unlikely(!fn->namelen) || __unlikely(size - FNODE_EA_POS(fn->namelen) > FNODE_MAX_EA_SIZE)) {
			if (recover_all & 2) goto delete_invalid_fnode;
			log_printf(0, "%s: FNODE BLOCK %"blk_format"X: INVALID FNODE SIZE %X AT %X, NAMELEN %X", get_filename(d), blk, fn->next, fp, fn->namelen);
			if (query(qprefix 0, "DELETE?")) goto delete_invalid_fnode;
			return 0;
		}
		if (__unlikely((fn->flags & (FNODE_FLAGS_DIR | FNODE_FLAGS_HARDLINK)) == (FNODE_FLAGS_DIR | FNODE_FLAGS_HARDLINK))) {
			if (recover_all & 2) goto delete_invalid_fnode;
			log_printf(0, "%s: FNODE BLOCK %"blk_format"X: INVALID FNODE FLAGS %02X AT %X", get_filename(d), blk, (unsigned)fn->flags, fp);
			goto query_delete;
		}
		different_hash_error = 0;
		duplicate_filename_error = 0;
		ea_error = 0;
		memcpy(filename, FNODE_NAME(fn), fn->namelen);
		filename[fn->namelen] = 0;
		for (i = 0; i < fn->namelen; i++) if (__unlikely(INVALID_FILENAME_CHARACTER(i, FNODE_NAME(fn)[i], super.flags_compat_none & FLAG_COMPAT_NONE_UNIX_NAMES))) {
			dot = 0;
			invl_name:
			log_printf(0, "%s: FNODE BLOCK %"blk_format"X: FNODE AT %X HAS INVALID FILENAME \"%.*s\"", get_filename(d), blk, fp, fn->namelen, FNODE_NAME(fn));
			for (; i < fn->namelen; i++) if (dot || INVALID_FILENAME_CHARACTER(i, FNODE_NAME(fn)[i], super.flags_compat_none & FLAG_COMPAT_NONE_UNIX_NAMES)) {
				if (FNODE_NAME(fn)[i] >= 'a' && FNODE_NAME(fn)[i] <= 'z') filename[i] -= 0x20;
				else filename[i] = '_';
			}
			duplicate_filename_error = 1;
			goto name_ok;
		}
		if (FNODE_NAME(fn)[0] == '.' && (fn->namelen == 1 || ((FNODE_NAME(fn)[1] == '.' && fn->namelen == 2)))) {
			dot = 1;
			i = 0;
			goto invl_name;
		}
		hash = name_hash(filename);
		if (__unlikely((hash & d->hash_mask) != (d->hash_val & d->hash_mask))) {
			log_printf(0, "%s: FILENAME \"%s\" BELONGS TO DIFFERENT HASH PAGE", get_filename(d), filename);
			different_hash_error = 1;
		}
		LIST_FOR_EACH(subdir, &d->subdirs, SUBDIR, list) {
			if (__unlikely(!strcmp(subdir->name, filename))) {
				log_printf(0, "%s: DUPLICATE FILENAME \"%s\"", get_filename(d), filename);
				duplicate_filename_error = 1;
				break;
			}
		}
		name_ok:
		subdir = mem_alloc(sizeof(SUBDIR) + strlen(filename));
		if (__unlikely(!subdir)) {
			log_printf(1, "CAN'T ALLOC MEMORY FOR SUBDIRECTORY DESCRIPTION FOR FILE %s/%s", get_filename(d), filename);
			return 2;
		}
		INIT_LIST(&subdir->subdirs);
		subdir->dir_depth = d->dir_depth + 1;
		subdir->dnode_depth = 0;
		strcpy(subdir->name, filename);
		subdir->parent = d;
		name_ea_size = 0;
		eapos = FNODE_EA_POS(fn->namelen);
		ea_size = size - eapos;
		if (__unlikely(ea_size)) {
			char *msg;
			memcpy(ea.ea, (char *)fn + eapos, ea_size);
			if (__unlikely((msg = check_ea(fn, (struct fnode_ea *)ea.ea, ea_size, __likely(!(fn->flags & FNODE_FLAGS_HARDLINK)) ? EA_TYPE_FILE_NAME : EA_TYPE_NAME)) != NULL)) {
				log_printf(0, "%s: FNODE BLOCK %"blk_format"X: INVALID EXTENDED ATTRIBUTES: %s", get_filename(subdir), blk, msg);
				ea_error = 1;
			}
		}
		if (__unlikely(fn->flags & FNODE_FLAGS_DIR)) {
			int r;
			blk_t subblk;
			if (cc_valid(fn->cc, fn->txc)) subblk = __make64(fn->run10, fn->run11);
			else subblk = __make64(fn->run20, fn->run21);
			subdir->fnode = subblk;
			subdir->file = 0;
			subdir->hash_val = 0;
			subdir->hash_mask = 0;
			r = get_fnode_description(subblk, &subdir->blocks, get_filename, subdir);
			if (__unlikely(r == 2)) return 2;
			reread_block;
			if (__unlikely(r)) {
				char *filen;
				filen = get_filename(subdir);
				mem_free(subdir);
				if (r == -1) goto cont;
				log_printf(0, "%s: INVALID DIRECTORY AT %"blk_format"X (FROM %"blk_format"X/%X)", filen, subblk, blk, fp);
				query_delete:
				if (query(qprefix 0, "DELETE?")) {
					skip_this:
					fn->next |= FNODE_NEXT_FREE;
					fn->cc = 0;
					fn->txc = 0;
					update_block;
				}
				goto cont;
			}
			if (__unlikely(recover_all)) goto recover_directory;
			if (__unlikely(different_hash_error | duplicate_filename_error | ea_error)) {
				RECOVERY_FILEDESC rfd;
#ifdef TESTCODE
				test_recover_directory:
#endif
				if (duplicate_filename_error) {
					if (!query(qprefix 0, "RENAME?")) goto dir_nofix;
				} else {
					if (!query(qprefix 0, "FIX?")) goto dir_nofix;
				}
				recover_directory:
				r = dealloc_and_free_fnode_description(&subdir->blocks);
				mem_free(subdir);
				if (__unlikely(r)) return r;
				rfd.size = cc_valid(fn->cc, fn->txc) ? fn->size0 : fn->size1;
				rfd.ctime = fn->ctime;
				rfd.mtime = fn->mtime;
				rfd.flags = fn->flags;
				rfd.dnode = subblk;
				if (__unlikely(r = recover_file(&rfd, ea.ea, ea_size, get_filename(d), filename, NULL, 0, NULL))) return r;
				skip_this_q:
				{
		/* !!! FIXME: delay this step until recovery time */
					static int skip_query = 0;
					if (!ro && !skip_query) {
						log_printf(0, "%s: %s \"%s\" WILL BE DELETED. IT WILL BE RECOVERED AT THE END. IF YOU BREAK FSCK, IT WILL BE LOST", get_filename(d), fn->flags & FNODE_FLAGS_DIR ? "DIRECTORY" : "FILE", filename);
						if (!query(qprefix 0, "CONTINUE?")) return 1;
						skip_query = 1;
					}
				}
				goto skip_this;
			}
#ifdef TESTCODE
			if (recover_all_directories) {
				log_printf(0, "%s: TESTING RECOVERY OF DIRECTORY \"%s\"", get_filename(d), filename);
				goto test_recover_directory;
			}
#endif
			dir_nofix:
			ADD_TO_LIST(&d->subdirs, &subdir->list);
			ADD_TO_LIST(&todolist, &subdir->todolist);
		} else if (__unlikely(fn->flags & FNODE_FLAGS_HARDLINK)) {
			int r;
			subdir->file = 1;
			INIT_LIST(&subdir->blocks);
			link = __make64(fn->anode0, fn->anode1);
			memcpy(name_ea.name_ea, ea.ea, ea_size);
			name_ea_size = ea_size;
			ea_size = 0;
			if (__unlikely(!link)) {
				log_printf(0, "%s: INVALID HARDLINK POINTER", get_filename(subdir));
				mem_free(subdir);
				goto query_delete;
			}
			r = hardlink_link(link);
			if (r == 1) {
				mem_free(subdir);
				if (__unlikely(different_hash_error | duplicate_filename_error/* | ea_error | recover_all*/)) {
	/* What if we should recover this one? */
	/* I couldn't think of a method how to handle this without major
	   code bloat, so I just kill the reference. File won't be lost,
	   just a hardlink on it will be */
					if (query(qprefix 0, "DELETE REFERENCE?")) {
						hardlink_unlink(link);
						goto skip_this;
					}
				}
				if (ea_error && query(qprefix 0, "FIX?")) {
	/* EAs can be fixed in place */
					unsigned new_name_ea_size = name_ea_size;
					fix_ea(fn, (struct fnode_ea *)name_ea.name_ea, &new_name_ea_size, EA_TYPE_NAME);
					memcpy((char *)fn + eapos, name_ea.name_ea, new_name_ea_size);
					if (new_name_ea_size != name_ea_size) {
						struct fnode *xfn;
						fn->next = (fn->next & ~FNODE_NEXT_SIZE) | (eapos + new_name_ea_size);
						xfn = (struct fnode *)((char *)fn + eapos + new_name_ea_size);
						xfn->next = FNODE_NEXT_FREE | (name_ea_size - new_name_ea_size);
						xfn->cc = 0;
						xfn->txc = 0;
					}
					update_block;
				}
				goto cont;
			}
			if (__unlikely(r == 2)) {
				mem_free(subdir);
				return 2;
			}
			if (__unlikely(r == -1)) {
				RECOVERY *rec = hardlink_get_recovery(link);
				r = add_recovery_hardlink(rec, get_filename(d), filename, name_ea.name_ea, name_ea_size);
				mem_free(subdir);
				if (__unlikely(r)) return r;
				goto skip_this_q;
			}
			r = read_fixed_fnode(link, 0, &fx, &fx_fn, get_filename, subdir);
			if (__unlikely(r == 2)) {
				mem_free(subdir);
				return 2;
			}
			if (__unlikely(r == 1)) {
				hardlink_delete(link);
				mem_free(subdir);
				goto query_delete;
			}
			hardlink_set_disk_nlink(link, *FIXED_FNODE_NLINK_PTR(fx_fn));
			memcpy(&f1, fx_fn, sizeof(struct fnode));
			ea_size = (fx_fn->next & FNODE_NEXT_SIZE) - FNODE_EA_POS(0);
			if (__unlikely(ea_size)) {
				char *msg;
				memcpy(ea.ea, (char *)fx_fn + FNODE_EA_POS(0), ea_size);
				if (__unlikely((msg = check_ea(fx_fn, (struct fnode_ea *)ea.ea, ea_size, EA_TYPE_FILE)) != NULL)) {
					log_printf(0, "%s: FIXED FNODE BLOCK %"blk_format"X: INVALID EXTENDED ATTRIBUTES: %s", get_filename(subdir), link, msg);
					ea_error = 1;
				}
			}
			goto check_file;
		} else {
			int r;
			link = 0;
			subdir->file = 1;
			INIT_LIST(&subdir->blocks);
			memcpy(&f1, fn, sizeof(struct fnode));
			check_file:
			r = check_file(&f1, &rfd.runs, get_filename, subdir);
			mem_free(subdir);
			if (__unlikely(r == 2)) goto free_runs_ret_r;
			reread_block;
			if (__unlikely(r == -2)) {
				if (link) if (__unlikely(r = sys_free(link, 1 << super.sectors_per_block_bits, 1))) goto free_runs_ret_r;
				free_file_runs(&rfd.runs);
				goto query_delete;
			}
			if (__unlikely(r == 1) || __unlikely(different_hash_error | duplicate_filename_error | ea_error | recover_all)) {
				if (!r) if ((r = dealloc_blocklist(0, &rfd.runs, 2))) {
					free_runs_ret_r:
					free_file_runs(&rfd.runs);
					return r;
				}
				if (recover_all || (!r && duplicate_filename_error ?
/* do not reformat: code line of qprefix is important for tracking
   "SAME" option behaviour */
				    query(qprefix 0, "RENAME?") :
				    query(qprefix 0, "FIX?"))) {
					RECOVERY *rec;
					rfd.size = cc_valid(f1.cc, f1.txc) ? f1.size0 : f1.size1;
					rfd.ctime = f1.ctime;
					rfd.mtime = f1.mtime;
					rfd.flags = f1.flags;
					if (link) if (__unlikely(r = sys_free(link, 1 << super.sectors_per_block_bits, 1))) goto free_runs_ret_r;
					if (__unlikely(r = recover_file(&rfd, ea.ea, ea_size, get_filename(d), filename, name_ea.name_ea, name_ea_size, &rec))) goto free_runs_ret_r;
					if (link) hardlink_set_recovery(link, rec);
					goto skip_this_q;
				}
				r = force_block_alloc_file_runs(&rfd.runs);
				free_file_runs(&rfd.runs);
				if (__unlikely(r)) return r;
				goto cont;
			}
			free_file_runs(&rfd.runs);
			if (__unlikely(r == -1)) goto cont;
			if (__likely(!link)) {
				if (__unlikely(memcmp(fn, &f1, sizeof(struct fnode)))) {
					memcpy(fn, &f1, sizeof(struct fnode));
					update_block;
				}
			} else {
				fx = reread_fixed_fnode_block(link);
				if (__unlikely(!fx)) {
					log_printf(1, "%s: ERROR RE-READING FIXED FNODE BLOCK %"blk_format"X: %s", get_filename(d), link, buffer_error);
					log_printf(1, "THE DEVICE IS UNRELIABLE, BLOCK HAS BEEN CORRECT BEFORE");
					return 1;
				}
				fx_fn = fixed_fnode_block_fnode(fx, 0);
				if (__unlikely(memcmp(fx_fn, &f1, sizeof(struct fnode)))) {
					memcpy(fx_fn, &f1, sizeof(struct fnode));
					if (__unlikely(!update_fixed_fnode_block(fx, link))) {
						log_printf(1, "%s: ERROR UPDATING FIXED FNODE BLOCK %"blk_format"X: %s", get_filename(d), link, buffer_error);
						log_printf(1, "THE DEVICE IS UNRELIABLE, BLOCK HAS BEEN CORRECT BEFORE");
						return 1;
					}
				}
				reread_block;
			}
		}
		cont:
		print_alloc_progress();
		fp += size;
	} while (fp < FNODE_BLOCK_SIZE);
#undef fn
#undef update_block
	return 0;
}

static unsigned rev_bits(unsigned x)
{
	int i = dnode_hash_bits;
	unsigned y = 0;
	do {
		y <<= 1;
		y |= x & 1;
		x >>= 1;
	} while (--i);
	return y;
}

static __finline__ blk_t dnode_decode(struct dnode_page_entry *e)
{
	return __make64(e->b0 | (e->b1 << 16), e->b2);
}

static int check_dnode(SUBDIR *d, blk_t blk)
{
	blk_t up_dnode;
	struct dnode_page *dn = read_buffer(blk, dnode_page_sectors);
	unsigned data, o;
	unsigned i;
	if (__unlikely(!dn)) {
		read_err:
		log_printf(1, "%s: ERROR ACCESSING DNODE %"blk_format"X: %s", get_filename(d), blk, buffer_error);
		log_printf(1, "THE DEVICE IS UNRELIABLE, DNODE HAS BEEN SUCCESSFULY READ BEFORE");
		return 1;
	}
#define update_head							\
do {									\
	if (__unlikely(write_buffer(blk, 1, dn))) goto read_err;	\
	if (__unlikely(!(dn = read_buffer(blk, dnode_page_sectors)))) goto read_err;\
} while (0)
	if (__unlikely(dn->magic != DNODE_PAGE_MAGIC)) {
		log_printf(1, "%s: BAD MAGIC WHEN RE-READING DNODE %"blk_format"X: %08X", get_filename(d), blk, dn->magic);
		log_printf(1, "THE DEVICE IS UNRELIABLE, MAGIC HAS BEEN CORRECT BEFORE");
		return 1;
	}
	if (__unlikely((dn->gflags & DNODE_GFLAGS_PAGE_SIZE_BITS_MINUS_1) >> DNODE_GFLAGS_PAGE_SIZE_BITS_MINUS_1_SHIFT != super.sectors_per_page_bits - 1)) {
		log_printf(0, "%s: DNODE %"blk_format"X HAS INVALID PAGE SIZE FLAG (%d != %d)", get_filename(d), blk, (dn->gflags & DNODE_GFLAGS_PAGE_SIZE_BITS_MINUS_1) >> DNODE_GFLAGS_PAGE_SIZE_BITS_MINUS_1_SHIFT, super.sectors_per_page_bits - 1);
		if (query(qprefix 0, "FIX?")) {
			dn->gflags = (dn->gflags & ~DNODE_GFLAGS_PAGE_SIZE_BITS_MINUS_1) | ((super.sectors_per_page_bits - 1) << DNODE_GFLAGS_PAGE_SIZE_BITS_MINUS_1_SHIFT);
			update_head;
		}
	}
	up_dnode = !d->dnode_depth ? 0 : d->parent->fnode;
	if (__unlikely(dn->up_dnode != up_dnode)) {
		log_printf(0, "%s: DNODE %"blk_format"X HAS BAD UP-DNODE POINTER (%"blk_format"X != %"blk_format"X)", get_filename(d), blk, dn->up_dnode, up_dnode);
		if (query(qprefix 0, "FIX?")) {
			dn->up_dnode = up_dnode;
			update_head;
		}
	}
	if (__unlikely(cc_check(&dn->cc, &dn->txc, "DNODE", get_filename, d, blk))) update_head;
	if (cc_valid(dn->cc, dn->txc)) data = DNODE_ENTRY_OFFSET, o = 0;
	else data = DNODE_ENTRY_OFFSET + dnode_data_size, o = 1;
#define reread_dnode							\
do {									\
	if (__unlikely(!(dn = read_buffer(blk, dnode_page_sectors)))) goto read_err;\
} while (0)
#define update_dnode							\
do {									\
	dn->flags[o] |= DNODE_CHECKSUM_VALID;				\
	dn->checksum[o] = __byte_sum((char *)dn + data, dnode_data_size) ^ CHECKSUM_BASE;\
	if (__unlikely(write_buffer(blk, dnode_page_sectors, dn))) goto read_err;\
	reread_dnode;							\
} while (0)
	if (dn->flags[o] & DNODE_CHECKSUM_VALID) {
		if (__unlikely(__byte_sum((char *)dn + data, dnode_data_size) != (CHECKSUM_BASE ^ dn->checksum[o]))) {
			log_printf(0, "%s: BAD CHECKSUM ON DNODE %"blk_format"X: %02X", get_filename(d), blk, __byte_sum((char *)dn + data, dnode_data_size) ^ CHECKSUM_BASE ^ dn->checksum[o]);
			if (query(qprefix 0, "FIX?")) {
				dn->checksum[o] = __byte_sum((char *)dn + data, dnode_data_size) ^ CHECKSUM_BASE;
				update_head;
			}
		}
	}
#define dnode_entry(x)	((struct dnode_page_entry *)((char *)dn + data) + (x))
#define dnode_val(x)	dnode_decode(dnode_entry(x))
#define dnode_set(x, v)	dnode_entry(x)->b0 = (v), dnode_entry(x)->b1 = (blk_t)(v) >> 16, dnode_entry(x)->b2 = (__u64)(v) >> 32
	for (i = 0; i < 1 << dnode_hash_bits;) {
		SUBDIR *subdir;
		int r;
		unsigned b, j;
		int hash_bits;
		blk_t ptr;
		print_alloc_progress();
		ptr = dnode_val(rev_bits(i));
		if (!ptr) {
			i++;
			continue;
		}
		subdir = mem_alloc(sizeof(SUBDIR));
		if (__unlikely(!subdir)) {
			log_printf(1, "CAN'T ALLOC MEMORY FOR SUBDIRECTORY DESCRIPTION FOR DIRECTORY %s", get_filename(d));
			return 2;
		}
		INIT_LIST(&subdir->subdirs);
		subdir->fnode = ptr;
		subdir->file = 0;
		subdir->dir_depth = d->dir_depth;
		subdir->dnode_depth = d->dnode_depth + 1;
		subdir->name[0] = 0;
		subdir->parent = d;
		r = get_fnode_description(ptr, &subdir->blocks, get_filename, d);
		if (__unlikely(r)) {
			mem_free(subdir);
			if (r == -1) goto dn_skip_this;
			if (r == 2) return 2;
			reread_dnode;
			log_printf(0, "%s: DNODE %"blk_format"X HAS INVALID POINTER TO %"blk_format"X AT %X", get_filename(d), blk, ptr, i);
			if (query(qprefix 0, "CLEAR?")) {
				for (j = 0; i + j < 1 << dnode_hash_bits && dnode_val(rev_bits(i + j)) == ptr; j++) dnode_set(rev_bits(i + j), 0);
				update_dnode;
				goto cont;
			}
			dn_skip_this:
			for (j = 0; i + j < 1 << dnode_hash_bits && dnode_val(rev_bits(i + j)) == ptr; j++) ;
			goto cont;
		}
		reread_dnode;
		b = 1 << __BSF(i | (1 << dnode_hash_bits));
		for (j = 1; j < b; j++) {
			blk_t newptr = dnode_val(rev_bits(i + j));
			if (newptr != ptr) {
				LIST_HEAD l;
				if (!newptr) break;
				r = get_fnode_description(newptr, &l, get_filename, d);
				if (__likely(!r)) {
					if (__unlikely(r = dealloc_and_free_fnode_description(&l))) {
						mem_free(subdir);
						return r;
					}
					reread_dnode;
					break;
				}
				if (__unlikely(r == 2)) {
					mem_free(subdir);
					return 2;
				}
				reread_dnode;
				log_printf(0, "%s: DNODE %"blk_format"X HAS INVALID POINTER TO %"blk_format"X AT %X BREAKING RUN %"blk_format"X", get_filename(d), blk, newptr, i + j, ptr);
				if (query(qprefix 0, "FIX?")) {
					dnode_set(rev_bits(i + j), ptr);
					j++;
					while (j < b && dnode_val(rev_bits(i + j)) == newptr) dnode_set(rev_bits(i + j), ptr), j++;
					j--;
					update_dnode;
				}
			}
		}
		if (j & (j - 1) || (i + j < 1 << dnode_hash_bits && dnode_val(rev_bits(i + j)) == ptr)) {
			log_printf(0, "%s: DNODE %"blk_format"X HAS UNALIGNED RUN AT %X", get_filename(d), blk, i);
			if (query(qprefix 0, "FIX?")) {
				if (j & (j - 1)) {
					unsigned k;
					for (k = j; k & (k - 1); k++) if (dnode_val(rev_bits(i + k))) goto go_back;
					for (k = j; k & (k - 1); k++) dnode_set(rev_bits(i + k), ptr);
					update_dnode;
					j = k;
					goto fixed;
					go_back:
					for (k = j; k & (k - 1); k--) dnode_set(rev_bits(i + k - 1), 0);
					update_dnode;
					j = k;
					goto fixed;
				} else {
					unsigned k;
					for (k = j; i + k < 1 << dnode_hash_bits && dnode_val(rev_bits(i + k)) == ptr; k++) dnode_set(rev_bits(i + k), 0);
					update_dnode;
					goto fixed;
				}
			}
		}
		fixed:
		hash_bits = dnode_hash_bits - __BSR(j);
		if (__unlikely(d->dnode_depth * dnode_hash_bits + hash_bits > 32)) {
			unsigned k;
			log_printf(0, "%s: DNODE %"blk_format"X HAS TOO DEEP POINTERS TO %"blk_format"X (%u BITS)", get_filename(d), blk, ptr, d->dnode_depth * dnode_hash_bits - hash_bits);
			invl_hash:
			if (query(qprefix 0, "CLEAR?")) {
				for (k = 0; k < j; k++) dnode_set(rev_bits(i + k), 0);
				update_dnode;
			}
			r = dealloc_and_free_fnode_description(&subdir->blocks);
			mem_free(subdir);
			if (__unlikely(r)) return r;
			goto cont;
		}
		if (__unlikely(LIST_STRUCT(subdir->blocks.next, FNODE_DESC, list)->dno) && __unlikely(j != 1)) {
			log_printf(0, "%s: DNODE %"blk_format"X HAS MULTIPLE POINTERS TO DNODE %"blk_format"X", get_filename(d), blk, ptr);
			goto invl_hash;
		}
		if (__likely(d->dnode_depth * dnode_hash_bits + hash_bits != 32))
			subdir->hash_mask = ((__u32)1 << (d->dnode_depth * dnode_hash_bits + hash_bits)) - 1;
		else
			subdir->hash_mask = ~0;
		subdir->hash_val = d->hash_val | rev_bits(i) << (d->dnode_depth * dnode_hash_bits);
		ADD_TO_LIST(&d->subdirs, &subdir->list);
		ADD_TO_LIST(&todolist, &subdir->todolist);
		cont:
		i += j;
	}
	return 0;
#undef update_head
}

int claim_recovered_directory(char *path, char *filename, blk_t dnode, void **result)
{
	int r;
	SUBDIR *subdir = mem_alloc(sizeof(SUBDIR) + 1 + strlen(path) + strlen(filename));
	if (__unlikely(!subdir)) {
		log_printf(1, "CAN'T ALLOC MEMORY FOR SUBDIRECTORY DESCRIPTION WHEN RECOVERING DIRECTORY %s/%s", path, filename);
		return 2;
	}
	INIT_LIST(&subdir->subdirs);
	subdir->dir_depth = 1;
	subdir->dnode_depth = 0;
	subdir->file = 0;
	subdir->hash_val = 0;
	subdir->hash_mask = 0;
	strcpy(subdir->name, path);
	strcat(subdir->name, "/");
	strcat(subdir->name, filename);
	subdir->parent = &root;
	r = get_fnode_description(dnode, &subdir->blocks, get_filename, subdir);
	if (__unlikely(r)) {
		if (r == 2) return r;
		log_printf(0, "%s: INVALID RECOVERED DIRECTORY AT %"blk_format"X (IT WAS PROBABLY ALLOCATED OR OVERWRITTEN BY SOMETHING ELSE)", subdir->name, dnode);
		return r;
	}
	ADD_TO_LIST(&root.subdirs, &subdir->list);
	ADD_TO_LIST(&todolist, &subdir->todolist);
	*result = subdir;
	return 0;
}

int undo_recovered_directory(void *result)
{
	int r;
	SUBDIR *subdir = result;
	DEL_FROM_LIST(&subdir->list);
	DEL_FROM_LIST(&subdir->todolist);
	r = dealloc_and_free_fnode_description(&subdir->blocks);
	mem_free(subdir);
	if (__unlikely(r)) return r;
	return 0;
}

static void free_node(SUBDIR *d)
{
	free_fnode_description(&d->blocks);
	if (__likely(d != &root)) {
		DEL_FROM_LIST(&d->list);
		mem_free(d);
	}
}

static void free_tree(void)
{
	SUBDIR *d = &root, *p;
	do {
		while (!LIST_EMPTY(&d->subdirs)) d = LIST_STRUCT(d->subdirs.next, SUBDIR, list);
		p = d->parent;
		free_node(d);
	} while ((d = p));
}

static char *filename = NULL;
static int filenamelen = 0;

static char *get_filename(void *d_)
{
	SUBDIR *d = d_;
	int p, q;
	if (__unlikely(d == &root)) return "/";
	p = 0;
	do {
		int l;
		if (__unlikely(d->dnode_depth)) continue;
		if (__unlikely(p + strlen(d->name) + 2 > filenamelen)) {
			char *newfn = mem_alloc(p + strlen(d->name) + 2);
			if (__unlikely(!newfn)) {
				log_printf(1, "CAN'T ALLOC MEMORY FOR FILENAME");
				return NULL;
			}
			memcpy(newfn, filename, p);
			mem_free(filename);
			filename = newfn;
			filenamelen = p + strlen(d->name) + 2;
		}
		for (l = strlen(d->name) - 1; l >= 0; l--) {
			filename[p++] = d->name[l];
		}
		filename[p++] = '/';
	} while ((d = d->parent) != &root);
	while (p >= 2 && filename[p - 1] == '/' && filename[p - 2] == '/') p--;
	filename[p] = 0;
	for (q = p >> 1, p = p - q, q--; q >= 0; q--) {
		char c = filename[q];
		filename[q] = filename[p];
		filename[p++] = c;
	}
	return filename;
}

void done_filename(void)
{
	if (__unlikely(filename != NULL)) mem_free(filename);
}
