blob: 9c2508f81198ac3e5d5fbdb41d0449e01636369b [file] [log] [blame]
// 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 "repair.h"
#include "exfat_fs.h"
#include "exfat_dir.h"
#include "fsck.h"
struct fsck_user_input {
struct exfat_user_input ei;
enum fsck_ui_options options;
};
#define EXFAT_MAX_UPCASE_CHARS 0x10000
#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 exfat_fsck exfat_fsck;
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' },
{"rescue", no_argument, NULL, 's' },
{"version", no_argument, NULL, 'V' },
{"verbose", no_argument, NULL, 'v' },
{"help", no_argument, NULL, 'h' },
{"?", no_argument, NULL, '?' },
{"ignore-bad-fs", no_argument, NULL, 'b' },
{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-b | --ignore-bad-fs Try to recover even if exfat is not found\n");
fprintf(stderr, "\t-s | --rescue Assign orphaned clusters to files\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, ...) \
({ \
exfat_resolve_path_parent(&path_resolve_ctx, \
parent, inode); \
exfat_err("ERROR: %s: " fmt, \
path_resolve_ctx.local_path, \
##__VA_ARGS__); \
})
#define repair_file_ask(iter, inode, code, fmt, ...) \
({ \
if (inode) \
exfat_resolve_path_parent(&path_resolve_ctx, \
(iter)->parent, inode); \
else \
exfat_resolve_path(&path_resolve_ctx, \
(iter)->parent); \
exfat_repair_ask(&exfat_fsck, code, \
"ERROR: %s: " fmt " at %#" PRIx64, \
path_resolve_ctx.local_path, \
##__VA_ARGS__, \
exfat_de_iter_device_offset(iter)); \
})
static int check_clus_chain(struct exfat_de_iter *de_iter,
struct exfat_inode *node)
{
struct exfat *exfat = de_iter->exfat;
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 && !exfat_heap_clus(exfat, node->first_clus))) {
if (repair_file_ask(de_iter, node,
ER_FILE_FIRST_CLUS,
"size %#" PRIx64 ", but the first cluster %#x",
node->size, node->first_clus))
goto truncate_file;
else
return -EINVAL;
}
while (clus != EXFAT_EOF_CLUSTER) {
if (count >= max_count) {
if (node->is_contiguous)
break;
if (repair_file_ask(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)) {
if (repair_file_ask(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)) {
if (!repair_file_ask(de_iter, node,
ER_FILE_INVALID_CLUS,
"cluster %#x is marked as free",
clus))
return -EINVAL;
}
/* This cluster is allocated or not */
if (exfat_get_inode_next_clus(exfat, node, clus, &next))
goto truncate_file;
if (next == EXFAT_BAD_CLUSTER) {
if (repair_file_ask(de_iter, node,
ER_FILE_INVALID_CLUS,
"BAD cluster. truncate to %"
PRIu64 " bytes",
count * exfat->clus_size))
goto truncate_file;
else
return -EINVAL;
} else if (!node->is_contiguous) {
if (next != EXFAT_EOF_CLUSTER &&
!exfat_heap_clus(exfat, next)) {
if (repair_file_ask(de_iter, node,
ER_FILE_INVALID_CLUS,
"broken cluster chain. truncate to %"
PRIu64 " bytes",
(count + 1) * exfat->clus_size)) {
count++;
prev = clus;
exfat_bitmap_set(exfat->alloc_bitmap,
clus);
goto truncate_file;
} else {
return -EINVAL;
}
}
}
count++;
exfat_bitmap_set(exfat->alloc_bitmap, clus);
prev = clus;
clus = next;
}
if (count < max_count) {
if (repair_file_ask(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 (!exfat_heap_clus(exfat, prev))
node->first_clus = EXFAT_FREE_CLUSTER;
exfat_de_iter_get_dirty(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 (!exfat_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 && exfat_heap_clus(exfat, prev)) {
if (exfat_set_fat(exfat, prev, EXFAT_EOF_CLUSTER))
return -EIO;
}
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 (!exfat_heap_clus(exfat, clus)) {
exfat_err("/: bad cluster. 0x%x\n", clus);
return false;
}
if (exfat_bitmap_get(exfat->alloc_bitmap, clus)) {
exfat_err("/: cluster is already allocated, or "
"there is a loop in cluster chain\n");
return false;
}
exfat_bitmap_set(exfat->alloc_bitmap, clus);
if (exfat_get_inode_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 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;
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_blk_dev *blkdev,
struct pbr **bs,
bool ignore_bad_fs_name)
{
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(blkdev->dev_fd, boot_sect,
sizeof(*boot_sect), 0) != (ssize_t)sizeof(*boot_sect)) {
exfat_err("failed to read Main boot sector\n");
free(boot_sect);
return -EIO;
}
if (memcmp(boot_sect->bpb.oem_name, "EXFAT ", 8) != 0 &&
!ignore_bad_fs_name) {
exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n");
free(boot_sect);
return -ENOTSUP;
}
sect_size = 1 << boot_sect->bsx.sect_size_bits;
free(boot_sect);
/* check boot regions */
ret = read_boot_region(blkdev, bs,
BOOT_SEC_IDX, sect_size, true);
if (ret == -EINVAL &&
exfat_repair_ask(&exfat_fsck, 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(blkdev, 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(blkdev, 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(blkdev, sect_size);
if (ret) {
exfat_err("failed to restore boot region from backup\n");
free(*bs);
*bs = NULL;
}
return ret;
}
static uint16_t file_calc_checksum(struct exfat_de_iter *iter)
{
uint16_t checksum;
struct exfat_dentry *file_de, *de;
int i;
checksum = 0;
exfat_de_iter_get(iter, 0, &file_de);
exfat_calc_dentry_checksum(file_de, &checksum, true);
for (i = 1; i <= file_de->file_num_ext; i++) {
exfat_de_iter_get(iter, i, &de);
exfat_calc_dentry_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(iter, 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")) {
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_dentry_set(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 = exfat_alloc_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);
exfat_free_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 = 1;
*new_node = NULL;
exfat_free_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_dentry_set(de_iter, &node, dentry_count);
if (ret)
return ret;
ret = check_inode(de_iter, node);
if (ret < 0) {
exfat_free_inode(node);
return -EINVAL;
}
if (node->attr & ATTR_SUBDIR)
exfat_stat.dir_count++;
else
exfat_stat.file_count++;
*new_node = node;
return ret;
}
static int read_volume_label(struct exfat *exfat)
{
struct exfat_dentry *dentry;
int err;
__le16 disk_label[VOLUME_LABEL_MAX_LEN];
struct exfat_lookup_filter filter = {
.in.type = EXFAT_VOLUME,
.in.filter = NULL,
};
err = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
if (err)
return err;
dentry = filter.out.dentry_set;
if (dentry->vol_char_cnt == 0)
goto out;
if (dentry->vol_char_cnt > VOLUME_LABEL_MAX_LEN) {
exfat_err("too long label. %d\n", dentry->vol_char_cnt);
err = -EINVAL;
goto out;
}
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");
err = -EINVAL;
goto out;
}
exfat_info("volume label [%s]\n", exfat->volume_label);
out:
free(filter.out.dentry_set);
return err;
}
static int read_bitmap(struct exfat *exfat)
{
struct exfat_lookup_filter filter = {
.in.type = EXFAT_BITMAP,
.in.filter = NULL,
.in.param = NULL,
};
struct exfat_dentry *dentry;
int retval;
retval = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
if (retval)
return retval;
dentry = filter.out.dentry_set;
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 -EINVAL;
}
if (!exfat_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 -EINVAL;
}
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, exfat->alloc_bitmap,
le64_to_cpu(dentry->bitmap_start_clu),
DIV_ROUND_UP(exfat->disk_bitmap_size,
exfat->clus_size));
free(filter.out.dentry_set);
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 -EIO;
return 0;
}
static int decompress_upcase_table(const __le16 *in_table, size_t in_len,
__u16 *out_table, size_t out_len)
{
size_t i, k;
uint16_t ch;
if (in_len > out_len)
return -E2BIG;
for (k = 0; k < out_len; k++)
out_table[k] = k;
for (i = 0, k = 0; i < in_len && k < out_len; i++) {
ch = le16_to_cpu(in_table[i]);
if (ch == 0xFFFF && i + 1 < in_len) {
uint16_t len = le16_to_cpu(in_table[++i]);
k += len;
} else {
out_table[k++] = ch;
}
}
return 0;
}
static int read_upcase_table(struct exfat *exfat)
{
struct exfat_lookup_filter filter = {
.in.type = EXFAT_UPCASE,
.in.filter = NULL,
.in.param = NULL,
};
struct exfat_dentry *dentry = NULL;
__le16 *upcase = NULL;
int retval;
ssize_t size;
__le32 checksum;
retval = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
if (retval)
return retval;
dentry = filter.out.dentry_set;
if (!exfat_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));
retval = -EINVAL;
goto out;
}
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));
retval = -EINVAL;
goto out;
}
upcase = (__le16 *)malloc(size);
if (!upcase) {
exfat_err("failed to allocate upcase table\n");
retval = -ENOMEM;
goto out;
}
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");
retval = -EIO;
goto out;
}
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));
retval = -EINVAL;
goto out;
}
exfat_bitmap_set_range(exfat, exfat->alloc_bitmap,
le32_to_cpu(dentry->upcase_start_clu),
DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size),
exfat->clus_size));
exfat->upcase_table = calloc(1,
sizeof(uint16_t) * EXFAT_UPCASE_TABLE_CHARS);
if (!exfat->upcase_table) {
retval = -EIO;
goto out;
}
decompress_upcase_table(upcase, size / 2,
exfat->upcase_table, EXFAT_UPCASE_TABLE_CHARS);
out:
if (dentry)
free(dentry);
if (upcase)
free(upcase);
return retval;
}
static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir)
{
struct exfat *exfat = fsck->exfat;
struct exfat_inode *node = NULL;
struct exfat_dentry *dentry;
struct exfat_de_iter *de_iter;
int dentry_count;
int ret;
de_iter = &fsck->de_iter;
ret = exfat_de_iter_init(de_iter, exfat, dir, fsck->buffer_desc);
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++;
break;
} 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
exfat_free_inode(node);
break;
case EXFAT_LAST:
goto out;
case EXFAT_VOLUME:
case EXFAT_BITMAP:
case EXFAT_UPCASE:
if (dir == exfat->root)
break;
/* fallthrough */
default:
if (IS_EXFAT_DELETED(dentry->type))
break;
if (repair_file_ask(de_iter, NULL, ER_DE_UNKNOWN,
"unknown entry type %#x at %07" PRIx64,
dentry->type,
exfat_de_iter_file_offset(de_iter))) {
struct exfat_dentry *dentry;
exfat_de_iter_get_dirty(de_iter, 0, &dentry);
dentry->type &= EXFAT_DELETE;
}
break;
}
exfat_de_iter_advance(de_iter, dentry_count);
}
out:
exfat_de_iter_flush(de_iter);
return 0;
err:
exfat_free_children(dir, false);
INIT_LIST_HEAD(&dir->children);
exfat_de_iter_flush(de_iter);
return ret;
}
/* write bitmap segments for clusters which are marked
* as free, but allocated to files.
*/
static int write_bitmap(struct exfat_fsck *fsck)
{
struct exfat *exfat = fsck->exfat;
bitmap_t *disk_b, *alloc_b, *ohead_b;
off_t dev_offset;
unsigned int i, bitmap_bytes, byte_offset, write_bytes;
dev_offset = exfat_c2o(exfat, exfat->disk_bitmap_clus);
bitmap_bytes = EXFAT_BITMAP_SIZE(le32_to_cpu(exfat->bs->bsx.clu_count));
disk_b = (bitmap_t *)exfat->disk_bitmap;
alloc_b = (bitmap_t *)exfat->alloc_bitmap;
ohead_b = (bitmap_t *)exfat->ohead_bitmap;
for (i = 0; i < bitmap_bytes / sizeof(bitmap_t); i++)
ohead_b[i] = alloc_b[i] | disk_b[i];
i = 0;
while (i < bitmap_bytes / sizeof(bitmap_t)) {
if (ohead_b[i] == disk_b[i]) {
i++;
continue;
}
byte_offset = ((i * sizeof(bitmap_t)) / 512) * 512;
write_bytes = MIN(512, bitmap_bytes - byte_offset);
if (exfat_write(exfat->blk_dev->dev_fd,
(char *)ohead_b + byte_offset, write_bytes,
dev_offset + byte_offset) != (ssize_t)write_bytes)
return -EIO;
i = (byte_offset + write_bytes) / sizeof(bitmap_t);
}
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_fsck *fsck)
{
struct exfat *exfat = fsck->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(fsck, dir);
if (dir_errors) {
exfat_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);
exfat_free_file_children(dir);
exfat_free_ancestors(dir);
}
out:
exfat_free_dir_list(exfat);
return ret;
}
static int exfat_root_dir_check(struct exfat *exfat)
{
struct exfat_inode *root;
clus_t clus_count;
int err;
root = exfat_alloc_inode(ATTR_SUBDIR);
if (!root)
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");
exfat_free_inode(root);
return -EINVAL;
}
root->size = clus_count * exfat->clus_size;
exfat->root = root;
exfat_stat.dir_count++;
exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n",
root->first_clus, root->size);
if (read_volume_label(exfat))
exfat_err("failed to read volume label\n");
err = read_bitmap(exfat);
if (err) {
exfat_err("failed to read bitmap\n");
return -EINVAL;
}
err = read_upcase_table(exfat);
if (err) {
exfat_err("failed to read upcase table\n");
return -EINVAL;
}
root->dev_offset = 0;
err = exfat_build_file_dentry_set(exfat, " ", ATTR_SUBDIR,
&root->dentry_set, &root->dentry_count);
if (err) {
exfat_free_inode(root);
return -ENOMEM;
}
return 0;
}
static int read_lostfound(struct exfat *exfat, struct exfat_inode **lostfound)
{
struct exfat_lookup_filter filter;
struct exfat_inode *inode;
int err;
err = exfat_lookup_file(exfat, exfat->root, "LOST+FOUND", &filter);
if (err)
return err;
inode = exfat_alloc_inode(ATTR_SUBDIR);
if (!inode) {
free(filter.out.dentry_set);
return -ENOMEM;
}
inode->dentry_set = filter.out.dentry_set;
inode->dentry_count = filter.out.dentry_count;
inode->dev_offset = filter.out.dev_offset;
inode->first_clus =
le32_to_cpu(filter.out.dentry_set[1].dentry.stream.start_clu);
inode->size =
le64_to_cpu(filter.out.dentry_set[1].dentry.stream.size);
*lostfound = inode;
return 0;
}
/* Create temporary files under LOST+FOUND and assign orphan
* chains of clusters to these files.
*/
static int rescue_orphan_clusters(struct exfat_fsck *fsck)
{
struct exfat *exfat = fsck->exfat;
struct exfat_inode *lostfound;
bitmap_t *disk_b, *alloc_b, *ohead_b;
struct exfat_dentry *dset;
clus_t clu_count, clu, s_clu, e_clu;
int err, dcount;
unsigned int i;
char name[] = "FILE0000000.CHK";
struct exfat_dentry_loc loc;
struct exfat_lookup_filter lf = {
.in.type = EXFAT_INVAL,
.in.filter = NULL,
};
err = read_lostfound(exfat, &lostfound);
if (err) {
exfat_err("failed to find LOST+FOUND\n");
return err;
}
/* get the last empty region of LOST+FOUND */
err = exfat_lookup_dentry_set(exfat, lostfound, &lf);
if (err && err != EOF) {
exfat_err("failed to find the last empty slot in LOST+FOUND\n");
goto out;
}
loc.parent = lostfound;
loc.file_offset = lf.out.file_offset;
loc.dev_offset = lf.out.dev_offset;
/* build a template dentry set */
err = exfat_build_file_dentry_set(exfat, name, 0, &dset, &dcount);
if (err) {
exfat_err("failed to create a temporary file in LOST+FOUNDn");
goto out;
}
dset[1].dentry.stream.flags |= EXFAT_SF_CONTIGUOUS;
clu_count = le32_to_cpu(exfat->bs->bsx.clu_count);
/* find clusters which are not marked as free, but not allocated to
* any files.
*/
disk_b = (bitmap_t *)exfat->disk_bitmap;
alloc_b = (bitmap_t *)exfat->alloc_bitmap;
ohead_b = (bitmap_t *)exfat->ohead_bitmap;
for (i = 0; i < EXFAT_BITMAP_SIZE(clu_count) / sizeof(bitmap_t); i++)
ohead_b[i] = disk_b[i] & ~alloc_b[i];
/* create temporary files and allocate contiguous orphan clusters
* to each file.
*/
for (clu = EXFAT_FIRST_CLUSTER; clu < clu_count + EXFAT_FIRST_CLUSTER &&
exfat_bitmap_find_one(exfat, exfat->ohead_bitmap, clu, &s_clu) == 0;) {
if (exfat_bitmap_find_zero(exfat, exfat->ohead_bitmap, s_clu, &e_clu))
e_clu = clu_count + EXFAT_FIRST_CLUSTER;
clu = e_clu;
snprintf(name, sizeof(name), "FILE%07d.CHK",
(unsigned int)(loc.file_offset >> 5));
err = exfat_update_file_dentry_set(exfat, dset, dcount,
name, s_clu, e_clu - s_clu);
if (err)
continue;
err = exfat_add_dentry_set(exfat, &loc, dset, dcount, true);
if (err)
continue;
}
free(dset);
err = 0;
out:
exfat_free_inode(lostfound);
return err;
}
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;
i = sizeof(units) / sizeof(units[0]) - 1;
while (i && (bytes >> i * 10) == 0)
i--;
shift = i * 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_fsck *fsck, const char *dev_name)
{
struct exfat *exfat = fsck->exfat;
bool clean;
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));
clean = exfat_stat.error_count == 0 ||
exfat_stat.error_count == exfat_stat.fixed_count;
printf("%s: %s. directories %ld, files %ld\n", dev_name,
clean ? "clean" : "corrupted",
exfat_stat.dir_count, exfat_stat.file_count);
if (exfat_stat.error_count)
printf("%s: files corrupted %ld, files fixed %ld\n", dev_name,
exfat_stat.error_count - exfat_stat.fixed_count,
exfat_stat.fixed_count);
}
int main(int argc, char * const argv[])
{
struct fsck_user_input ui;
struct exfat_blk_dev bd;
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, "arynpbsVvh", 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 'b':
ui.options |= FSCK_OPTS_IGNORE_BAD_FS_NAME;
break;
case 's':
ui.options |= FSCK_OPTS_RESCUE_CLUS;
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 {
if (ui.options & (FSCK_OPTS_IGNORE_BAD_FS_NAME |
FSCK_OPTS_RESCUE_CLUS))
usage(argv[0]);
ui.options |= FSCK_OPTS_REPAIR_NO;
ui.ei.writeable = false;
}
exfat_fsck.options = ui.options;
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;
}
ret = exfat_boot_region_check(&bd, &bs,
ui.options & FSCK_OPTS_IGNORE_BAD_FS_NAME ?
true : false);
if (ret)
goto err;
exfat_fsck.exfat = exfat_alloc_exfat(&bd, bs);
if (!exfat_fsck.exfat) {
ret = -ENOMEM;
goto err;
}
exfat_fsck.buffer_desc = exfat_alloc_buffer(2,
exfat_fsck.exfat->clus_size,
exfat_fsck.exfat->sect_size);
if (!exfat_fsck.buffer_desc) {
ret = -ENOMEM;
goto err;
}
if ((exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) &&
exfat_mark_volume_dirty(exfat_fsck.exfat, true)) {
ret = -EIO;
goto err;
}
exfat_debug("verifying root directory...\n");
ret = exfat_root_dir_check(exfat_fsck.exfat);
if (ret) {
exfat_err("failed to verify root directory.\n");
goto out;
}
if (exfat_fsck.options & FSCK_OPTS_RESCUE_CLUS) {
ret = exfat_create_file(exfat_fsck.exfat,
exfat_fsck.exfat->root,
"LOST+FOUND",
ATTR_SUBDIR);
if (ret) {
exfat_err("failed to create lost+found directory\n");
goto out;
}
if (fsync(exfat_fsck.exfat->blk_dev->dev_fd) != 0) {
ret = -EIO;
exfat_err("failed to sync()\n");
goto out;
}
}
exfat_debug("verifying directory entries...\n");
ret = exfat_filesystem_check(&exfat_fsck);
if (ret)
goto out;
if (exfat_fsck.options & FSCK_OPTS_RESCUE_CLUS) {
rescue_orphan_clusters(&exfat_fsck);
exfat_fsck.dirty = true;
exfat_fsck.dirty_fat = true;
}
if (exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) {
ret = write_bitmap(&exfat_fsck);
if (ret) {
exfat_err("failed to write bitmap\n");
goto out;
}
}
if (ui.ei.writeable && fsync(bd.dev_fd)) {
exfat_err("failed to sync\n");
ret = -EIO;
goto out;
}
if (exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE)
exfat_mark_volume_dirty(exfat_fsck.exfat, false);
out:
exfat_show_info(&exfat_fsck, ui.ei.dev_name);
err:
if (ret && ret != -EINVAL)
exit_code = FSCK_EXIT_OPERATION_ERROR;
else if (ret == -EINVAL ||
exfat_stat.error_count != exfat_stat.fixed_count)
exit_code = FSCK_EXIT_ERRORS_LEFT;
else if (exfat_fsck.dirty)
exit_code = FSCK_EXIT_CORRECTED;
else
exit_code = FSCK_EXIT_NO_ERRORS;
if (exfat_fsck.buffer_desc)
exfat_free_buffer(exfat_fsck.buffer_desc, 2);
if (exfat_fsck.exfat)
exfat_free_exfat(exfat_fsck.exfat);
close(bd.dev_fd);
return exit_code;
}