// SPDX-License-Identifier: GPL-2.0-or-later
/*
 *   Copyright (C) 2019 Namjae Jeon <linkinjeon@kernel.org>
 *   Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com>
 */

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <getopt.h>
#include <inttypes.h>
#include <string.h>
#include <errno.h>
#include <locale.h>

#include "exfat_ondisk.h"
#include "libexfat.h"
#include "fsck.h"
#include "repair.h"

struct fsck_user_input {
	struct exfat_user_input		ei;
	enum fsck_ui_options		options;
};

#define EXFAT_MAX_UPCASE_CHARS	0x10000

#ifdef WORDS_BIGENDIAN
typedef __u8	bitmap_t;
#else
typedef __u32	bitmap_t;
#endif

#define BITS_PER	(sizeof(bitmap_t) * 8)
#define BIT_MASK(__c)	(1 << ((__c) % BITS_PER))
#define BIT_ENTRY(__c)	((__c) / BITS_PER)

#define EXFAT_BITMAP_SIZE(__c_count)	\
	(DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t))
#define EXFAT_BITMAP_GET(__bmap, __c)	\
			(((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] & BIT_MASK(__c))
#define EXFAT_BITMAP_SET(__bmap, __c)	\
			(((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] |= \
			 BIT_MASK(__c))

#define FSCK_EXIT_NO_ERRORS		0x00
#define FSCK_EXIT_CORRECTED		0x01
#define FSCK_EXIT_NEED_REBOOT		0x02
#define FSCK_EXIT_ERRORS_LEFT		0x04
#define FSCK_EXIT_OPERATION_ERROR	0x08
#define FSCK_EXIT_SYNTAX_ERROR		0x10
#define FSCK_EXIT_USER_CANCEL		0x20
#define FSCK_EXIT_LIBRARY_ERROR		0x80

struct exfat_stat {
	long		dir_count;
	long		file_count;
	long		error_count;
	long		fixed_count;
};

struct path_resolve_ctx {
	struct exfat_inode	*ancestors[255];
	__le16			utf16_path[PATH_MAX + 2];
	char			local_path[PATH_MAX * MB_LEN_MAX + 1];
};

struct exfat_stat exfat_stat;
struct path_resolve_ctx path_resolve_ctx;

static struct option opts[] = {
	{"repair",	no_argument,	NULL,	'r' },
	{"repair-yes",	no_argument,	NULL,	'y' },
	{"repair-no",	no_argument,	NULL,	'n' },
	{"repair-auto",	no_argument,	NULL,	'p' },
	{"version",	no_argument,	NULL,	'V' },
	{"verbose",	no_argument,	NULL,	'v' },
	{"help",	no_argument,	NULL,	'h' },
	{"?",		no_argument,	NULL,	'?' },
	{NULL,		0,		NULL,	 0  }
};

static void usage(char *name)
{
	fprintf(stderr, "Usage: %s\n", name);
	fprintf(stderr, "\t-r | --repair        Repair interactively\n");
	fprintf(stderr, "\t-y | --repair-yes    Repair without ask\n");
	fprintf(stderr, "\t-n | --repair-no     No repair\n");
	fprintf(stderr, "\t-p | --repair-auto   Repair automatically\n");
	fprintf(stderr, "\t-a                   Repair automatically\n");
	fprintf(stderr, "\t-V | --version       Show version\n");
	fprintf(stderr, "\t-v | --verbose       Print debug\n");
	fprintf(stderr, "\t-h | --help          Show help\n");

	exit(FSCK_EXIT_SYNTAX_ERROR);
}

#define fsck_err(parent, inode, fmt, ...)		\
({							\
		resolve_path_parent(&path_resolve_ctx,	\
			parent, inode);			\
		exfat_err("ERROR: %s: " fmt,		\
			path_resolve_ctx.local_path,	\
			##__VA_ARGS__);			\
})

static struct exfat_inode *alloc_exfat_inode(__u16 attr)
{
	struct exfat_inode *node;
	int size;

	size = offsetof(struct exfat_inode, name) + NAME_BUFFER_SIZE;
	node = (struct exfat_inode *)calloc(1, size);
	if (!node) {
		exfat_err("failed to allocate exfat_node\n");
		return NULL;
	}

	node->parent = NULL;
	INIT_LIST_HEAD(&node->children);
	INIT_LIST_HEAD(&node->sibling);
	INIT_LIST_HEAD(&node->list);

	node->last_pclus = EXFAT_EOF_CLUSTER;
	node->attr = attr;
	if (attr & ATTR_SUBDIR)
		exfat_stat.dir_count++;
	else
		exfat_stat.file_count++;
	return node;
}

static void free_exfat_inode(struct exfat_inode *node)
{
	free(node);
}

static void inode_free_children(struct exfat_inode *dir, bool file_only)
{
	struct exfat_inode *node, *i;

	list_for_each_entry_safe(node, i, &dir->children, sibling) {
		if (file_only) {
			if (!(node->attr & ATTR_SUBDIR)) {
				list_del(&node->sibling);
				free_exfat_inode(node);
			}
		} else {
			list_del(&node->sibling);
			list_del(&node->list);
			free_exfat_inode(node);
		}
	}
}

static void inode_free_file_children(struct exfat_inode *dir)
{
	inode_free_children(dir, true);
}

/* delete @child and all ancestors that does not have
 * children
 */
static void inode_free_ancestors(struct exfat_inode *child)
{
	struct exfat_inode *parent;

	if (!list_empty(&child->children))
		return;

	do {
		if (!(child->attr & ATTR_SUBDIR)) {
			exfat_err("not directory.\n");
			return;
		}

		parent = child->parent;
		list_del(&child->sibling);
		free_exfat_inode(child);

		child = parent;
	} while (child && list_empty(&child->children));

	return;
}

static void free_exfat(struct exfat *exfat)
{
	int i;

	if (exfat) {
		if (exfat->bs)
			free(exfat->bs);
		if (exfat->alloc_bitmap)
			free(exfat->alloc_bitmap);
		if (exfat->disk_bitmap)
			free(exfat->disk_bitmap);
		for (i = 0; i < 2; i++) {
			if (exfat->buffer_desc[i].buffer)
				free(exfat->buffer_desc[i].buffer);
			if (exfat->buffer_desc[i].dirty)
				free(exfat->buffer_desc[i].dirty);
		}
		free(exfat);
	}
}

static int init_exfat(struct exfat *exfat, struct pbr *bs)
{
	int i;

	INIT_LIST_HEAD(&exfat->dir_list);
	exfat->bs = bs;
	exfat->clus_count = le32_to_cpu(bs->bsx.clu_count);
	exfat->clus_size = EXFAT_CLUSTER_SIZE(bs);
	exfat->sect_size = EXFAT_SECTOR_SIZE(bs);

	/* TODO: bitmap could be very large. */
	exfat->alloc_bitmap = (char *)calloc(1,
			EXFAT_BITMAP_SIZE(exfat->clus_count));
	if (!exfat->alloc_bitmap) {
		exfat_err("failed to allocate bitmap\n");
		goto err;
	}

	exfat->disk_bitmap = (char *)malloc(
				EXFAT_BITMAP_SIZE(exfat->clus_count));
	if (!exfat->disk_bitmap) {
		exfat_err("failed to allocate bitmap\n");
		goto err;
	}

	/* allocate cluster buffers */
	for (i = 0; i < 2; i++) {
		exfat->buffer_desc[i].buffer =
			(char *)malloc(exfat->clus_size);
		if (!exfat->buffer_desc[i].buffer)
			goto err;
		exfat->buffer_desc[i].dirty =
			(char *)calloc(
			(exfat->clus_size / exfat->sect_size), 1);
		if (!exfat->buffer_desc[i].dirty)
			goto err;
	}
	return 0;
err:
	free_exfat(exfat);
	return -ENOMEM;
}

static void exfat_free_dir_list(struct exfat *exfat)
{
	struct exfat_inode *dir, *i;

	list_for_each_entry_safe(dir, i, &exfat->dir_list, list) {
		inode_free_file_children(dir);
		list_del(&dir->list);
		free_exfat_inode(dir);
	}
}

/*
 * get references of ancestors that include @child until the count of
 * ancestors is not larger than @count and the count of characters of
 * their names is not larger than @max_char_len.
 * return true if root is reached.
 */
bool get_ancestors(struct exfat_inode *child,
		struct exfat_inode **ancestors, int count,
		int max_char_len,
		int *ancestor_count)
{
	struct exfat_inode *dir;
	int name_len, char_len;
	int root_depth, depth, i;

	root_depth = 0;
	char_len = 0;
	max_char_len += 1;

	dir = child;
	while (dir) {
		name_len = exfat_utf16_len(dir->name, NAME_BUFFER_SIZE);
		if (char_len + name_len > max_char_len)
			break;

		/* include '/' */
		char_len += name_len + 1;
		root_depth++;

		dir = dir->parent;
	}

	depth = MIN(root_depth, count);

	for (dir = child, i = depth - 1; i >= 0; dir = dir->parent, i--)
		ancestors[i] = dir;

	*ancestor_count = depth;
	return dir == NULL;
}

static int resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child)
{
	int depth, i;
	int name_len;
	__le16 *utf16_path;
	static const __le16 utf16_slash = cpu_to_le16(0x002F);
	static const __le16 utf16_null = cpu_to_le16(0x0000);
	size_t in_size;

	ctx->local_path[0] = '\0';

	get_ancestors(child,
			ctx->ancestors,
			sizeof(ctx->ancestors) / sizeof(ctx->ancestors[0]),
			PATH_MAX,
			&depth);

	utf16_path = ctx->utf16_path;
	for (i = 0; i < depth; i++) {
		name_len = exfat_utf16_len(ctx->ancestors[i]->name,
				NAME_BUFFER_SIZE);
		memcpy((char *)utf16_path, (char *)ctx->ancestors[i]->name,
				name_len * 2);
		utf16_path += name_len;
		memcpy((char *)utf16_path, &utf16_slash, sizeof(utf16_slash));
		utf16_path++;
	}

	if (depth > 0)
		utf16_path--;
	memcpy((char *)utf16_path, &utf16_null, sizeof(utf16_null));
	utf16_path++;

	in_size = (utf16_path - ctx->utf16_path) * sizeof(__le16);
	return exfat_utf16_dec(ctx->utf16_path, in_size,
				ctx->local_path, sizeof(ctx->local_path));
}

static int resolve_path_parent(struct path_resolve_ctx *ctx,
			struct exfat_inode *parent, struct exfat_inode *child)
{
	int ret;
	struct exfat_inode *old;

	old = child->parent;
	child->parent = parent;

	ret = resolve_path(ctx, child);
	child->parent = old;
	return ret;
}

#define repair_file_ask(iter, inode, code, fmt, ...)	\
({							\
		resolve_path_parent(&path_resolve_ctx,	\
				(iter)->parent, inode);	\
		exfat_repair_ask((iter)->exfat, code,	\
			"ERROR: %s: " fmt,		\
			path_resolve_ctx.local_path,	\
			##__VA_ARGS__);			\
})

static inline bool heap_clus(struct exfat *exfat, clus_t clus)
{
	return clus >= EXFAT_FIRST_CLUSTER &&
		(clus - EXFAT_FIRST_CLUSTER) < exfat->clus_count;
}

int get_next_clus(struct exfat *exfat, struct exfat_inode *node,
				clus_t clus, clus_t *next)
{
	off_t offset;

	*next = EXFAT_EOF_CLUSTER;

	if (!heap_clus(exfat, clus))
		return -EINVAL;

	if (node->is_contiguous) {
		*next = clus + 1;
		return 0;
	}

	offset = (off_t)le32_to_cpu(exfat->bs->bsx.fat_offset) <<
				exfat->bs->bsx.sect_size_bits;
	offset += sizeof(clus_t) * clus;

	if (exfat_read(exfat->blk_dev->dev_fd, next, sizeof(*next), offset)
			!= sizeof(*next))
		return -EIO;
	*next = le32_to_cpu(*next);
	return 0;
}

static int set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus)
{
	off_t offset;

	offset = le32_to_cpu(exfat->bs->bsx.fat_offset) <<
		exfat->bs->bsx.sect_size_bits;
	offset += sizeof(clus_t) * clus;

	if (exfat_write(exfat->blk_dev->dev_fd, &next_clus, sizeof(next_clus),
			offset) != sizeof(next_clus))
		return -EIO;
	return 0;
}

static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node)
{
	struct exfat_dentry *stream_de;
	clus_t clus, prev, next;
	uint64_t count, max_count;

	clus = node->first_clus;
	prev = EXFAT_EOF_CLUSTER;
	count = 0;
	max_count = DIV_ROUND_UP(node->size, exfat->clus_size);

	if (node->size == 0 && node->first_clus == EXFAT_FREE_CLUSTER)
		return 0;

	/* the first cluster is wrong */
	if ((node->size == 0 && node->first_clus != EXFAT_FREE_CLUSTER) ||
		(node->size > 0 && !heap_clus(exfat, node->first_clus))) {
		if (repair_file_ask(&exfat->de_iter, node,
			ER_FILE_FIRST_CLUS, "first cluster is wrong"))
			goto truncate_file;
		else
			return -EINVAL;
	}

	while (clus != EXFAT_EOF_CLUSTER) {
		if (count >= max_count) {
			if (node->is_contiguous)
				break;
			if (repair_file_ask(&exfat->de_iter, node,
					ER_FILE_SMALLER_SIZE,
					"more clusters are allocated. "
					"truncate to %" PRIu64 " bytes",
					count * exfat->clus_size))
				goto truncate_file;
			else
				return -EINVAL;
		}

		/*
		 * This cluster is already allocated. it may be shared with
		 * the other file, or there is a loop in cluster chain.
		 */
		if (EXFAT_BITMAP_GET(exfat->alloc_bitmap,
				clus - EXFAT_FIRST_CLUSTER)) {
			if (repair_file_ask(&exfat->de_iter, node,
					ER_FILE_DUPLICATED_CLUS,
					"cluster is already allocated for "
					"the other file. truncated to %"
					PRIu64 " bytes",
					count * exfat->clus_size))
				goto truncate_file;
			else
				return -EINVAL;
		}

		if (!EXFAT_BITMAP_GET(exfat->disk_bitmap,
				clus - EXFAT_FIRST_CLUSTER)) {
			if (repair_file_ask(&exfat->de_iter, node,
					ER_FILE_INVALID_CLUS,
					"cluster is marked as free. truncate to %" PRIu64 " bytes",
					count * exfat->clus_size))
				goto truncate_file;

			else
				return -EINVAL;
		}

		/* This cluster is allocated or not */
		if (get_next_clus(exfat, node, clus, &next))
			goto truncate_file;
		if (!node->is_contiguous) {
			if (!heap_clus(exfat, next) &&
					next != EXFAT_EOF_CLUSTER) {
				if (repair_file_ask(&exfat->de_iter, node,
						ER_FILE_INVALID_CLUS,
						"broken cluster chain. "
						"truncate to %"
						PRIu64 " bytes",
						count * exfat->clus_size))
					goto truncate_file;

				else
					return -EINVAL;
			}
		}

		count++;
		EXFAT_BITMAP_SET(exfat->alloc_bitmap,
				clus - EXFAT_FIRST_CLUSTER);
		prev = clus;
		clus = next;
	}

	if (count < max_count) {
		if (repair_file_ask(&exfat->de_iter, node,
			ER_FILE_LARGER_SIZE, "less clusters are allocated. "
			"truncates to %" PRIu64 " bytes",
			count * exfat->clus_size))
			goto truncate_file;
		else
			return -EINVAL;
	}

	return 0;
truncate_file:
	node->size = count * exfat->clus_size;
	if (!heap_clus(exfat, prev))
		node->first_clus = EXFAT_FREE_CLUSTER;

	exfat_de_iter_get_dirty(&exfat->de_iter, 1, &stream_de);
	if (count * exfat->clus_size <
			le64_to_cpu(stream_de->stream_valid_size))
		stream_de->stream_valid_size = cpu_to_le64(
				count * exfat->clus_size);
	if (!heap_clus(exfat, prev))
		stream_de->stream_start_clu = EXFAT_FREE_CLUSTER;
	stream_de->stream_size = cpu_to_le64(
			count * exfat->clus_size);

	/* remaining clusters will be freed while FAT is compared with
	 * alloc_bitmap.
	 */
	if (!node->is_contiguous && heap_clus(exfat, prev))
		return set_fat(exfat, prev, EXFAT_EOF_CLUSTER);
	return 1;
}

static bool root_get_clus_count(struct exfat *exfat, struct exfat_inode *node,
							clus_t *clus_count)
{
	clus_t clus;

	clus = node->first_clus;
	*clus_count = 0;

	do {
		if (!heap_clus(exfat, clus)) {
			exfat_err("/: bad cluster. 0x%x\n", clus);
			return false;
		}

		if (EXFAT_BITMAP_GET(exfat->alloc_bitmap,
				clus - EXFAT_FIRST_CLUSTER)) {
			exfat_err("/: cluster is already allocated, or "
				"there is a loop in cluster chain\n");
			return false;
		}

		EXFAT_BITMAP_SET(exfat->alloc_bitmap,
				clus - EXFAT_FIRST_CLUSTER);

		if (get_next_clus(exfat, node, clus, &clus) != 0) {
			exfat_err("/: broken cluster chain\n");
			return false;
		}

		(*clus_count)++;
	} while (clus != EXFAT_EOF_CLUSTER);
	return true;
}

static off_t exfat_s2o(struct exfat *exfat, off_t sect)
{
	return sect << exfat->bs->bsx.sect_size_bits;
}

off_t exfat_c2o(struct exfat *exfat, unsigned int clus)
{
	if (clus < EXFAT_FIRST_CLUSTER)
		return ~0L;

	return exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset) +
				((off_t)(clus - EXFAT_FIRST_CLUSTER) <<
				 exfat->bs->bsx.sect_per_clus_bits));
}

static int boot_region_checksum(int dev_fd,
				int bs_offset, unsigned int sect_size)
{
	void *sect;
	unsigned int i;
	uint32_t checksum;
	int ret = 0;

	sect = malloc(sect_size);
	if (!sect)
		return -ENOMEM;

	checksum = 0;
	for (i = 0; i < 11; i++) {
		if (exfat_read(dev_fd, sect, sect_size,
				bs_offset * sect_size + i * sect_size) !=
				(ssize_t)sect_size) {
			exfat_err("failed to read boot region\n");
			ret = -EIO;
			goto out;
		}
		boot_calc_checksum(sect, sect_size, i == 0, &checksum);
	}

	if (exfat_read(dev_fd, sect, sect_size,
			bs_offset * sect_size + 11 * sect_size) !=
			(ssize_t)sect_size) {
		exfat_err("failed to read a boot checksum sector\n");
		ret = -EIO;
		goto out;
	}

	for (i = 0; i < sect_size/sizeof(checksum); i++) {
		if (le32_to_cpu(((__le32 *)sect)[i]) != checksum) {
			exfat_err("checksum of boot region is not correct. %#x, but expected %#x\n",
				le32_to_cpu(((__le32 *)sect)[i]), checksum);
			ret = -EINVAL;
			goto out;
		}
	}
out:
	free(sect);
	return ret;
}

static int exfat_mark_volume_dirty(struct exfat *exfat, bool dirty)
{
	uint16_t flags;

	if (!(exfat->options & FSCK_OPTS_REPAIR_WRITE))
		return 0;

	flags = le16_to_cpu(exfat->bs->bsx.vol_flags);
	if (dirty)
		flags |= 0x02;
	else
		flags &= ~0x02;

	exfat->bs->bsx.vol_flags = cpu_to_le16(flags);
	if (exfat_write(exfat->blk_dev->dev_fd, exfat->bs,
			sizeof(struct pbr), 0) != (ssize_t)sizeof(struct pbr)) {
		exfat_err("failed to set VolumeDirty\n");
		return -EIO;
	}

	if (fsync(exfat->blk_dev->dev_fd) != 0) {
		exfat_err("failed to set VolumeDirty\n");
		return -EIO;
	}
	return 0;
}

static int read_boot_region(struct exfat_blk_dev *bd, struct pbr **pbr,
			    int bs_offset, unsigned int sect_size,
			    bool verbose)
{
	struct pbr *bs;
	int ret = -EINVAL;

	*pbr = NULL;
	bs = (struct pbr *)malloc(sizeof(struct pbr));
	if (!bs) {
		exfat_err("failed to allocate memory\n");
		return -ENOMEM;
	}

	if (exfat_read(bd->dev_fd, bs, sizeof(*bs),
			bs_offset * sect_size) != (ssize_t)sizeof(*bs)) {
		exfat_err("failed to read a boot sector\n");
		ret = -EIO;
		goto err;
	}

	if (memcmp(bs->bpb.oem_name, "EXFAT   ", 8) != 0) {
		if (verbose)
			exfat_err("failed to find exfat file system\n");
		goto err;
	}

	ret = boot_region_checksum(bd->dev_fd, bs_offset, sect_size);
	if (ret < 0)
		goto err;

	ret = -EINVAL;
	if (EXFAT_SECTOR_SIZE(bs) < 512 || EXFAT_SECTOR_SIZE(bs) > 4 * KB) {
		if (verbose)
			exfat_err("too small or big sector size: %d\n",
				  EXFAT_SECTOR_SIZE(bs));
		goto err;
	}

	if (EXFAT_CLUSTER_SIZE(bs) > 32 * MB) {
		if (verbose)
			exfat_err("too big cluster size: %d\n",
				  EXFAT_CLUSTER_SIZE(bs));
		goto err;
	}

	if (bs->bsx.fs_version[1] != 1 || bs->bsx.fs_version[0] != 0) {
		if (verbose)
			exfat_err("unsupported exfat version: %d.%d\n",
				  bs->bsx.fs_version[1], bs->bsx.fs_version[0]);
		goto err;
	}

	if (bs->bsx.num_fats != 1) {
		if (verbose)
			exfat_err("unsupported FAT count: %d\n",
				  bs->bsx.num_fats);
		goto err;
	}

	if (le64_to_cpu(bs->bsx.vol_length) * EXFAT_SECTOR_SIZE(bs) >
			bd->size) {
		if (verbose)
			exfat_err("too large sector count: %" PRIu64 ", expected: %llu\n",
				  le64_to_cpu(bs->bsx.vol_length),
				  bd->num_sectors);
		goto err;
	}

	if (le32_to_cpu(bs->bsx.clu_count) * EXFAT_CLUSTER_SIZE(bs) >
			bd->size) {
		if (verbose)
			exfat_err("too large cluster count: %u, expected: %u\n",
				  le32_to_cpu(bs->bsx.clu_count),
				  bd->num_clusters);
		goto err;
	}

	*pbr = bs;
	return 0;
err:
	free(bs);
	return ret;
}

static int restore_boot_region(struct exfat_blk_dev *bd, unsigned int sect_size)
{
	int i;
	char *sector;
	int ret;

	sector = malloc(sect_size);
	if (!sector)
		return -ENOMEM;

	for (i = 0; i < 12; i++) {
		if (exfat_read(bd->dev_fd, sector, sect_size,
				BACKUP_BOOT_SEC_IDX * sect_size +
				i * sect_size) !=
				(ssize_t)sect_size) {
			ret = -EIO;
			goto free_sector;
		}
		if (i == 0)
			((struct pbr *)sector)->bsx.perc_in_use = 0xff;

		if (exfat_write(bd->dev_fd, sector, sect_size,
				BOOT_SEC_IDX * sect_size +
				i * sect_size) !=
				(ssize_t)sect_size) {
			ret = -EIO;
			goto free_sector;
		}
	}

	if (fsync(bd->dev_fd)) {
		ret = -EIO;
		goto free_sector;
	}
	ret = 0;

free_sector:
	free(sector);
	return ret;
}

static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs)
{
	struct pbr *boot_sect;
	unsigned int sect_size;
	int ret;

	/* First, find out the exfat sector size */
	boot_sect = malloc(sizeof(*boot_sect));
	if (boot_sect == NULL)
		return -ENOMEM;

	if (exfat_read(exfat->blk_dev->dev_fd, boot_sect,
		       sizeof(*boot_sect), 0) != (ssize_t)sizeof(*boot_sect)) {
		exfat_err("failed to read Main boot sector\n");
		return -EIO;
	}

	sect_size = 1 << boot_sect->bsx.sect_size_bits;
	free(boot_sect);

	/* check boot regions */
	ret = read_boot_region(exfat->blk_dev, bs,
			       BOOT_SEC_IDX, sect_size, true);
	if (ret == -EINVAL && exfat_repair_ask(exfat, ER_BS_BOOT_REGION,
				"boot region is corrupted. try to restore the region from backup"
				)) {
		const unsigned int sector_sizes[] = {512, 4096, 1024, 2048};
		unsigned int i;

		if (sect_size >= 512 && sect_size <= EXFAT_MAX_SECTOR_SIZE) {
			ret = read_boot_region(exfat->blk_dev, bs,
					       BACKUP_BOOT_SEC_IDX, sect_size,
					       false);
			if (!ret)
				goto restore;
		}

		for (i = 0; i < sizeof(sector_sizes)/sizeof(sector_sizes[0]); i++) {
			if (sector_sizes[i] == sect_size)
				continue;

			ret = read_boot_region(exfat->blk_dev, bs,
					       BACKUP_BOOT_SEC_IDX,
					       sector_sizes[i], false);
			if (!ret) {
				sect_size = sector_sizes[i];
				goto restore;
			}
		}
		exfat_err("backup boot region is also corrupted\n");
	}

	return ret;
restore:
	ret = restore_boot_region(exfat->blk_dev, sect_size);
	if (ret) {
		exfat_err("failed to restore boot region from backup\n");
		free(*bs);
		*bs = NULL;
	}
	return ret;
}

static void dentry_calc_checksum(struct exfat_dentry *dentry,
				__le16 *checksum, bool primary)
{
	unsigned int i;
	uint8_t *bytes;

	bytes = (uint8_t *)dentry;

	*checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[0];
	*checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[1];

	i = primary ? 4 : 2;
	for (; i < sizeof(*dentry); i++) {
		*checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[i];
	}
}

static __le16 file_calc_checksum(struct exfat_de_iter *iter)
{
	__le16 checksum;
	struct exfat_dentry *file_de, *de;
	int i;

	checksum = 0;
	exfat_de_iter_get(iter, 0, &file_de);

	dentry_calc_checksum(file_de, &checksum, true);
	for (i = 1; i <= file_de->file_num_ext; i++) {
		exfat_de_iter_get(iter, i, &de);
		dentry_calc_checksum(de, &checksum, false);
	}

	return checksum;
}

/*
 * return 0 if there are no errors, or 1 if errors are fixed, or
 * an error code
 */
static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node)
{
	struct exfat *exfat = iter->exfat;
	struct exfat_dentry *dentry;
	int ret = 0;
	uint16_t checksum;
	bool valid = true;

	ret = check_clus_chain(exfat, node);
	if (ret < 0)
		return ret;

	if (node->size > le32_to_cpu(exfat->bs->bsx.clu_count) *
				(uint64_t)exfat->clus_size) {
		fsck_err(iter->parent, node,
			"size %" PRIu64 " is greater than cluster heap\n",
			node->size);
		valid = false;
	}

	if (node->size == 0 && node->is_contiguous) {
		if (repair_file_ask(iter, node, ER_FILE_ZERO_NOFAT,
				"empty, but has no Fat chain\n")) {
			exfat_de_iter_get_dirty(iter, 1, &dentry);
			dentry->stream_flags &= ~EXFAT_SF_CONTIGUOUS;
			ret = 1;
		} else
			valid = false;
	}

	if ((node->attr & ATTR_SUBDIR) &&
			node->size % exfat->clus_size != 0) {
		fsck_err(iter->parent, node,
			"directory size %" PRIu64 " is not divisible by %d\n",
			node->size, exfat->clus_size);
		valid = false;
	}

	checksum = file_calc_checksum(iter);
	exfat_de_iter_get(iter, 0, &dentry);
	if (checksum != le16_to_cpu(dentry->file_checksum)) {
		if (repair_file_ask(iter, node, ER_DE_CHECKSUM,
				"the checksum of a file is wrong")) {
			exfat_de_iter_get_dirty(iter, 0, &dentry);
			dentry->file_checksum = cpu_to_le16(checksum);
			ret = 1;
		} else
			valid = false;
	}

	return valid ? ret : -EINVAL;
}

static int read_file_dentries(struct exfat_de_iter *iter,
			struct exfat_inode **new_node, int *skip_dentries)
{
	struct exfat_dentry *file_de, *stream_de, *name_de;
	struct exfat_inode *node;
	int i, ret;

	/* TODO: mtime, atime, ... */

	ret = exfat_de_iter_get(iter, 0, &file_de);
	if (ret || file_de->type != EXFAT_FILE) {
		exfat_err("failed to get file dentry. %d\n", ret);
		return -EINVAL;
	}
	ret = exfat_de_iter_get(iter, 1, &stream_de);
	if (ret || stream_de->type != EXFAT_STREAM) {
		exfat_err("failed to get stream dentry. %d\n", ret);
		return -EINVAL;
	}

	*new_node = NULL;
	node = alloc_exfat_inode(le16_to_cpu(file_de->file_attr));
	if (!node)
		return -ENOMEM;

	if (file_de->file_num_ext < 2) {
		exfat_err("too few secondary count. %d\n",
				file_de->file_num_ext);
		free_exfat_inode(node);
		return -EINVAL;
	}

	for (i = 2; i <= file_de->file_num_ext; i++) {
		ret = exfat_de_iter_get(iter, i, &name_de);
		if (ret || name_de->type != EXFAT_NAME) {
			exfat_err("failed to get name dentry. %d\n", ret);
			ret = -EINVAL;
			goto err;
		}

		memcpy(node->name +
			(i-2) * ENTRY_NAME_MAX, name_de->name_unicode,
			sizeof(name_de->name_unicode));
	}

	node->first_clus = le32_to_cpu(stream_de->stream_start_clu);
	node->is_contiguous =
		((stream_de->stream_flags & EXFAT_SF_CONTIGUOUS) != 0);
	node->size = le64_to_cpu(stream_de->stream_size);

	if (node->size < le64_to_cpu(stream_de->stream_valid_size)) {
		if (repair_file_ask(iter, node, ER_FILE_VALID_SIZE,
			"valid size %" PRIu64 " greater than size %" PRIu64,
			le64_to_cpu(stream_de->stream_valid_size),
			node->size)) {
			exfat_de_iter_get_dirty(iter, 1, &stream_de);
			stream_de->stream_valid_size =
					stream_de->stream_size;
		} else {
			ret = -EINVAL;
			goto err;
		}
	}

	*skip_dentries = (file_de->file_num_ext + 1);
	*new_node = node;
	return 0;
err:
	*skip_dentries = 0;
	*new_node = NULL;
	free_exfat_inode(node);
	return ret;
}

static int read_file(struct exfat_de_iter *de_iter,
		struct exfat_inode **new_node, int *dentry_count)
{
	struct exfat_inode *node;
	int ret;

	*new_node = NULL;

	ret = read_file_dentries(de_iter, &node, dentry_count);
	if (ret)
		return ret;

	ret = check_inode(de_iter, node);
	if (ret < 0) {
		free_exfat_inode(node);
		return -EINVAL;
	}

	*new_node = node;
	return ret;
}

static bool read_volume_label(struct exfat_de_iter *iter)
{
	struct exfat *exfat;
	struct exfat_dentry *dentry;
	__le16 disk_label[VOLUME_LABEL_MAX_LEN];

	exfat = iter->exfat;
	if (exfat_de_iter_get(iter, 0, &dentry))
		return false;

	if (dentry->vol_char_cnt == 0)
		return true;

	if (dentry->vol_char_cnt > VOLUME_LABEL_MAX_LEN) {
		exfat_err("too long label. %d\n", dentry->vol_char_cnt);
		return false;
	}

	memcpy(disk_label, dentry->vol_label, sizeof(disk_label));
	if (exfat_utf16_dec(disk_label, dentry->vol_char_cnt*2,
		exfat->volume_label, sizeof(exfat->volume_label)) < 0) {
		exfat_err("failed to decode volume label\n");
		return false;
	}

	exfat_info("volume label [%s]\n", exfat->volume_label);
	return true;
}

static void exfat_bitmap_set_range(struct exfat *exfat,
			clus_t start_clus, clus_t count)
{
	clus_t clus;

	if (!heap_clus(exfat, start_clus) ||
		!heap_clus(exfat, start_clus + count))
		return;

	clus = start_clus;
	while (clus < start_clus + count) {
		EXFAT_BITMAP_SET(exfat->alloc_bitmap,
				clus - EXFAT_FIRST_CLUSTER);
		clus++;
	}
}

static bool read_bitmap(struct exfat_de_iter *iter)
{
	struct exfat_dentry *dentry;
	struct exfat *exfat;

	exfat = iter->exfat;
	if (exfat_de_iter_get(iter, 0, &dentry))
		return false;

	exfat_debug("start cluster %#x, size %#" PRIx64 "\n",
			le32_to_cpu(dentry->bitmap_start_clu),
			le64_to_cpu(dentry->bitmap_size));

	if (le64_to_cpu(dentry->bitmap_size) <
			DIV_ROUND_UP(exfat->clus_count, 8)) {
		exfat_err("invalid size of allocation bitmap. 0x%" PRIx64 "\n",
				le64_to_cpu(dentry->bitmap_size));
		return false;
	}
	if (!heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) {
		exfat_err("invalid start cluster of allocate bitmap. 0x%x\n",
				le32_to_cpu(dentry->bitmap_start_clu));
		return false;
	}

	exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu);
	exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8);

	exfat_bitmap_set_range(exfat, le64_to_cpu(dentry->bitmap_start_clu),
			DIV_ROUND_UP(exfat->disk_bitmap_size,
			exfat->clus_size));

	if (exfat_read(exfat->blk_dev->dev_fd, exfat->disk_bitmap,
			exfat->disk_bitmap_size,
			exfat_c2o(exfat, exfat->disk_bitmap_clus)) !=
			(ssize_t)exfat->disk_bitmap_size)
		return false;

	return true;
}

static bool read_upcase_table(struct exfat_de_iter *iter)
{
	struct exfat_dentry *dentry;
	struct exfat *exfat;
	ssize_t size;
	__le16 *upcase;
	__le32 checksum;

	exfat = iter->exfat;

	if (exfat_de_iter_get(iter, 0, &dentry))
		return false;

	if (!heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) {
		exfat_err("invalid start cluster of upcase table. 0x%x\n",
			le32_to_cpu(dentry->upcase_start_clu));
		return false;
	}

	size = (ssize_t)le64_to_cpu(dentry->upcase_size);
	if (size > (ssize_t)(EXFAT_MAX_UPCASE_CHARS * sizeof(__le16)) ||
			size == 0 || size % sizeof(__le16)) {
		exfat_err("invalid size of upcase table. 0x%" PRIx64 "\n",
			le64_to_cpu(dentry->upcase_size));
		return false;
	}

	upcase = (__le16 *)malloc(size);
	if (!upcase) {
		exfat_err("failed to allocate upcase table\n");
		return false;
	}

	if (exfat_read(exfat->blk_dev->dev_fd, upcase, size,
			exfat_c2o(exfat,
			le32_to_cpu(dentry->upcase_start_clu))) != size) {
		exfat_err("failed to read upcase table\n");
		free(upcase);
		return false;
	}

	checksum = 0;
	boot_calc_checksum((unsigned char *)upcase, size, false, &checksum);
	if (le32_to_cpu(dentry->upcase_checksum) != checksum) {
		exfat_err("corrupted upcase table %#x (expected: %#x)\n",
			checksum, le32_to_cpu(dentry->upcase_checksum));
		free(upcase);
		return false;
	}

	exfat_bitmap_set_range(exfat, le32_to_cpu(dentry->upcase_start_clu),
			DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size),
			exfat->clus_size));

	free(upcase);
	return true;
}

static int read_children(struct exfat *exfat, struct exfat_inode *dir)
{
	int ret;
	struct exfat_inode *node = NULL;
	struct exfat_dentry *dentry;
	int dentry_count;
	struct exfat_de_iter *de_iter;

	de_iter = &exfat->de_iter;
	ret = exfat_de_iter_init(de_iter, exfat, dir);
	if (ret == EOF)
		return 0;
	else if (ret)
		return ret;

	while (1) {
		ret = exfat_de_iter_get(de_iter, 0, &dentry);
		if (ret == EOF) {
			break;
		} else if (ret) {
			fsck_err(dir->parent, dir,
				"failed to get a dentry. %d\n", ret);
			goto err;
		}

		dentry_count = 1;

		switch (dentry->type) {
		case EXFAT_FILE:
			ret = read_file(de_iter, &node, &dentry_count);
			if (ret < 0) {
				exfat_stat.error_count++;
				goto err;
			} else if (ret) {
				exfat_stat.error_count++;
				exfat_stat.fixed_count++;
			}

			if ((node->attr & ATTR_SUBDIR) && node->size) {
				node->parent = dir;
				list_add_tail(&node->sibling, &dir->children);
				list_add_tail(&node->list, &exfat->dir_list);
			} else
				free_exfat_inode(node);
			break;
		case EXFAT_VOLUME:
			if (!read_volume_label(de_iter)) {
				exfat_err("failed to verify volume label\n");
				ret = -EINVAL;
				goto err;
			}
			break;
		case EXFAT_BITMAP:
			if (!read_bitmap(de_iter)) {
				exfat_err(
					"failed to verify allocation bitmap\n");
				ret = -EINVAL;
				goto err;
			}
			break;
		case EXFAT_UPCASE:
			if (!read_upcase_table(de_iter)) {
				exfat_err(
					"failed to verify upcase table\n");
				ret = -EINVAL;
				goto err;
			}
			break;
		case EXFAT_LAST:
			goto out;
		default:
			if (IS_EXFAT_DELETED(dentry->type))
				break;
			exfat_err("unknown entry type. 0x%x\n", dentry->type);
			ret = -EINVAL;
			goto err;
		}

		exfat_de_iter_advance(de_iter, dentry_count);
	}
out:
	exfat_de_iter_flush(de_iter);
	return 0;
err:
	inode_free_children(dir, false);
	INIT_LIST_HEAD(&dir->children);
	exfat_de_iter_flush(de_iter);
	return ret;
}

static int write_dirty_fat(struct exfat *exfat)
{
	struct buffer_desc *bd;
	off_t offset;
	ssize_t len;
	size_t read_size, write_size;
	clus_t clus, last_clus, clus_count, i;
	unsigned int idx;

	clus = 0;
	last_clus = le32_to_cpu(exfat->bs->bsx.clu_count) + 2;
	bd = exfat->buffer_desc;
	idx = 0;
	offset = le32_to_cpu(exfat->bs->bsx.fat_offset) *
		exfat->sect_size;
	read_size = exfat->clus_size;
	write_size = exfat->sect_size;

	while (clus < last_clus) {
		clus_count = MIN(read_size / sizeof(clus_t), last_clus - clus);
		len = exfat_read(exfat->blk_dev->dev_fd, bd[idx].buffer,
				clus_count * sizeof(clus_t), offset);
		if (len != (ssize_t)(sizeof(clus_t) * clus_count)) {
			exfat_err("failed to read fat entries, %zd\n", len);
			return -EIO;
		}

		/* TODO: read ahead */

		for (i = clus ? clus : EXFAT_FIRST_CLUSTER;
				i < clus + clus_count; i++) {
			if (!EXFAT_BITMAP_GET(exfat->alloc_bitmap,
					i - EXFAT_FIRST_CLUSTER) &&
					((clus_t *)bd[idx].buffer)[i - clus] !=
					EXFAT_FREE_CLUSTER) {
				((clus_t *)bd[idx].buffer)[i - clus] =
					EXFAT_FREE_CLUSTER;
				bd[idx].dirty[(i - clus) /
					(write_size / sizeof(clus_t))] = true;
			}
		}

		for (i = 0; i < read_size; i += write_size) {
			if (bd[idx].dirty[i / write_size]) {
				if (exfat_write(exfat->blk_dev->dev_fd,
						&bd[idx].buffer[i], write_size,
						offset + i) !=
						(ssize_t)write_size) {
					exfat_err("failed to write "
						"fat entries\n");
					return -EIO;

				}
				bd[idx].dirty[i / write_size] = false;
			}
		}

		idx ^= 0x01;
		clus = clus + clus_count;
		offset += len;
	}
	return 0;
}

static int write_dirty_bitmap(struct exfat *exfat)
{
	struct buffer_desc *bd;
	off_t offset, last_offset, bitmap_offset;
	ssize_t len;
	ssize_t read_size, write_size, i, size;
	int idx;

	offset = exfat_c2o(exfat, exfat->disk_bitmap_clus);
	last_offset = offset + exfat->disk_bitmap_size;
	bitmap_offset = 0;
	read_size = exfat->clus_size;
	write_size = exfat->sect_size;

	bd = exfat->buffer_desc;
	idx = 0;

	while (offset < last_offset) {
		len = MIN(read_size, last_offset - offset);
		if (exfat_read(exfat->blk_dev->dev_fd, bd[idx].buffer,
				len, offset) != (ssize_t)len)
			return -EIO;

		/* TODO: read-ahead */

		for (i = 0; i < len; i += write_size) {
			size = MIN(write_size, len - i);
			if (memcmp(&bd[idx].buffer[i],
					exfat->alloc_bitmap + bitmap_offset + i,
					size)) {
				if (exfat_write(exfat->blk_dev->dev_fd,
					exfat->alloc_bitmap + bitmap_offset + i,
					size, offset + i) != size)
					return -EIO;
			}
		}

		idx ^= 0x01;
		offset += len;
		bitmap_offset += len;
	}
	return 0;
}

static int reclaim_free_clusters(struct exfat *exfat)
{
	if (write_dirty_fat(exfat)) {
		exfat_err("failed to write fat entries\n");
		return -EIO;
	}
	if (write_dirty_bitmap(exfat)) {
		exfat_err("failed to write bitmap\n");
		return -EIO;
	}
	return 0;
}

/*
 * for each directory in @dir_list.
 * 1. read all dentries and allocate exfat_nodes for files and directories.
 *    and append directory exfat_nodes to the head of @dir_list
 * 2. free all of file exfat_nodes.
 * 3. if the directory does not have children, free its exfat_node.
 */
static int exfat_filesystem_check(struct exfat *exfat)
{
	struct exfat_inode *dir;
	int ret = 0, dir_errors;

	if (!exfat->root) {
		exfat_err("root is NULL\n");
		return -ENOENT;
	}

	list_add(&exfat->root->list, &exfat->dir_list);

	while (!list_empty(&exfat->dir_list)) {
		dir = list_entry(exfat->dir_list.next, struct exfat_inode, list);

		if (!(dir->attr & ATTR_SUBDIR)) {
			fsck_err(dir->parent, dir,
				"failed to travel directories. "
				"the node is not directory\n");
			ret = -EINVAL;
			goto out;
		}

		dir_errors = read_children(exfat, dir);
		if (dir_errors) {
			resolve_path(&path_resolve_ctx, dir);
			exfat_debug("failed to check dentries: %s\n",
					path_resolve_ctx.local_path);
			ret = dir_errors;
		}

		list_del(&dir->list);
		inode_free_file_children(dir);
		inode_free_ancestors(dir);
	}
out:
	exfat_free_dir_list(exfat);
	exfat->root = NULL;
	if (exfat->dirty_fat && reclaim_free_clusters(exfat))
		return -EIO;
	return ret;
}

static int exfat_root_dir_check(struct exfat *exfat)
{
	struct exfat_inode *root;
	clus_t clus_count;

	root = alloc_exfat_inode(ATTR_SUBDIR);
	if (!root) {
		exfat_err("failed to allocate memory\n");
		return -ENOMEM;
	}

	root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster);
	if (!root_get_clus_count(exfat, root, &clus_count)) {
		exfat_err("failed to follow the cluster chain of root\n");
		free_exfat_inode(root);
		return -EINVAL;
	}
	root->size = clus_count * exfat->clus_size;

	exfat->root = root;
	exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n",
		root->first_clus, root->size);
	return 0;
}

static char *bytes_to_human_readable(size_t bytes)
{
	static const char * const units[] = {"B", "KB", "MB", "GB", "TB", "PB"};
	static char buf[15*4];
	unsigned int i, shift, quoti, remain;

	shift = 0;
	for (i = 0; i < sizeof(units)/sizeof(units[0]); i++) {
		if (bytes / (1ULL << (shift + 10)) == 0)
			break;
		shift += 10;
	}

	if (i >= sizeof(units)/sizeof(units[0])) {
		i = i - 1;
		shift = shift - 10;
	}

	quoti = (unsigned int)(bytes / (1ULL << shift));
	remain = 0;
	if (shift > 0) {
		remain = (unsigned int)
			((bytes & ((1ULL << shift) - 1)) >> (shift - 10));
		remain = (remain * 100) / 1024;
	}

	snprintf(buf, sizeof(buf), "%u.%02u %s", quoti, remain, units[i]);
	return buf;
}

static void exfat_show_info(struct exfat *exfat, const char *dev_name,
			int errors)
{
	exfat_info("sector size:  %s\n",
		bytes_to_human_readable(1 << exfat->bs->bsx.sect_size_bits));
	exfat_info("cluster size: %s\n",
		bytes_to_human_readable(exfat->clus_size));
	exfat_info("volume size:  %s\n",
		bytes_to_human_readable(exfat->blk_dev->size));

	printf("%s: %s. directories %ld, files %ld\n", dev_name,
			errors ? "checking stopped" : "clean",
			exfat_stat.dir_count, exfat_stat.file_count);
	if (errors || exfat->dirty)
		printf("%s: files corrupted %ld, files fixed %ld\n", dev_name,
			exfat_stat.error_count, exfat_stat.fixed_count);
}

int main(int argc, char * const argv[])
{
	struct fsck_user_input ui;
	struct exfat_blk_dev bd;
	struct exfat *exfat = NULL;
	struct pbr *bs = NULL;
	int c, ret, exit_code;
	bool version_only = false;

	memset(&ui, 0, sizeof(ui));
	memset(&bd, 0, sizeof(bd));

	print_level = EXFAT_ERROR;

	if (!setlocale(LC_CTYPE, ""))
		exfat_err("failed to init locale/codeset\n");

	opterr = 0;
	while ((c = getopt_long(argc, argv, "arynpVvh", opts, NULL)) != EOF) {
		switch (c) {
		case 'n':
			if (ui.options & FSCK_OPTS_REPAIR_ALL)
				usage(argv[0]);
			ui.options |= FSCK_OPTS_REPAIR_NO;
			break;
		case 'r':
			if (ui.options & FSCK_OPTS_REPAIR_ALL)
				usage(argv[0]);
			ui.options |= FSCK_OPTS_REPAIR_ASK;
			break;
		case 'y':
			if (ui.options & FSCK_OPTS_REPAIR_ALL)
				usage(argv[0]);
			ui.options |= FSCK_OPTS_REPAIR_YES;
			break;
		case 'a':
		case 'p':
			if (ui.options & FSCK_OPTS_REPAIR_ALL)
				usage(argv[0]);
			ui.options |= FSCK_OPTS_REPAIR_AUTO;
			break;
		case 'V':
			version_only = true;
			break;
		case 'v':
			if (print_level < EXFAT_DEBUG)
				print_level++;
			break;
		case '?':
		case 'h':
		default:
			usage(argv[0]);
		}
	}

	show_version();
	if (optind != argc - 1)
		usage(argv[0]);

	if (version_only)
		exit(FSCK_EXIT_SYNTAX_ERROR);
	if (ui.options & FSCK_OPTS_REPAIR_WRITE)
		ui.ei.writeable = true;
	else {
		ui.options |= FSCK_OPTS_REPAIR_NO;
		ui.ei.writeable = false;
	}

	snprintf(ui.ei.dev_name, sizeof(ui.ei.dev_name), "%s", argv[optind]);
	ret = exfat_get_blk_dev_info(&ui.ei, &bd);
	if (ret < 0) {
		exfat_err("failed to open %s. %d\n", ui.ei.dev_name, ret);
		return FSCK_EXIT_OPERATION_ERROR;
	}

	exfat = (struct exfat *)calloc(1, sizeof(*exfat));
	if (!exfat) {
		exfat_err("failed to allocate exfat\n");
		ret = -ENOMEM;
		goto err;
	}
	exfat->blk_dev = &bd;
	exfat->options = ui.options;

	ret = exfat_boot_region_check(exfat, &bs);
	if (ret)
		goto err;

	ret = init_exfat(exfat, bs);
	if (ret) {
		exfat = NULL;
		goto err;
	}

	if (exfat_mark_volume_dirty(exfat, true)) {
		ret = -EIO;
		goto err;
	}

	exfat_debug("verifying root directory...\n");
	ret = exfat_root_dir_check(exfat);
	if (ret) {
		exfat_err("failed to verify root directory.\n");
		goto out;
	}

	exfat_debug("verifying directory entries...\n");
	ret = exfat_filesystem_check(exfat);
	if (ret)
		goto out;

	if (ui.ei.writeable && fsync(bd.dev_fd)) {
		exfat_err("failed to sync\n");
		ret = -EIO;
		goto out;
	}
	exfat_mark_volume_dirty(exfat, false);

out:
	exfat_show_info(exfat, ui.ei.dev_name, ret);
err:
	if (ret == -EINVAL)
		exit_code = FSCK_EXIT_ERRORS_LEFT;
	else if (ret)
		exit_code = FSCK_EXIT_OPERATION_ERROR;
	else if (exfat->dirty)
		exit_code = FSCK_EXIT_CORRECTED;
	else
		exit_code = FSCK_EXIT_NO_ERRORS;

	free_exfat(exfat);
	close(bd.dev_fd);
	return exit_code;
}
