#include "exfat_ondisk.h"
#include "libexfat.h"
-#include "fsck.h"
#include "repair.h"
+#include "exfat_fs.h"
+#include "exfat_dir.h"
+#include "fsck.h"
struct fsck_user_input {
struct exfat_user_input ei;
#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
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_fsck exfat_fsck;
struct exfat_stat exfat_stat;
struct path_resolve_ctx path_resolve_ctx;
{"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 }
};
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");
#define fsck_err(parent, inode, fmt, ...) \
({ \
- resolve_path_parent(&path_resolve_ctx, \
+ exfat_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
- * ancesters 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__); \
+ 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 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)
+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;
+ clus_t clus, prev, next, new_clus;
uint64_t count, max_count;
+ int err;
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)
+ if (node->size == 0 && node->first_clus == EXFAT_FREE_CLUSTER) {
+ /* locate a cluster for the empty dir if the dir starts with EXFAT_FREE_CLUSTER */
+ if (node->attr & ATTR_SUBDIR) {
+ if (repair_file_ask(de_iter, node,
+ ER_DE_FIRST_CLUS,
+ "size %#" PRIx64 ", but the first cluster %#x",
+ node->size, node->first_clus))
+ goto allocate_cluster;
+ return -EINVAL;
+ }
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"))
+ (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;
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))
+ 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 - 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))
+ 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 - 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
+ 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 (get_next_clus(exfat, node, clus, &next))
+ if (exfat_get_inode_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))
+ 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
+ } else {
return -EINVAL;
+ }
}
}
count++;
- EXFAT_BITMAP_SET(exfat->alloc_bitmap,
- clus - EXFAT_FIRST_CLUSTER);
+ exfat_bitmap_set(exfat->alloc_bitmap, clus);
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))
+ 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;
+allocate_cluster:
+ exfat_de_iter_get_dirty(de_iter, 1, &stream_de);
+ err = exfat_find_free_cluster(exfat, exfat->start_clu, &new_clus);
+ if (err) {
+ exfat->start_clu = EXFAT_FIRST_CLUSTER;
+ exfat_err("failed to find a free cluster\n");
+ return -ENOSPC;
+ }
+ exfat->start_clu = new_clus;
+
+ if (exfat_set_fat(exfat, new_clus, EXFAT_EOF_CLUSTER))
+ return -EIO;
+
+ /* zero out the new cluster */
+ if (exfat_write(exfat->blk_dev->dev_fd, exfat->zero_cluster,
+ exfat->clus_size, exfat_c2o(exfat, new_clus)) !=
+ (ssize_t)exfat->clus_size) {
+ exfat_err("failed to fill new cluster with zeroes\n");
+ return -EIO;
+ }
+
+ /* modify the number of cluster form 0 to 1 */
+ count = 1;
+ stream_de->stream_start_clu = cpu_to_le32(new_clus);
+ stream_de->stream_size = cpu_to_le64(count * exfat->clus_size);
+ stream_de->stream_valid_size = cpu_to_le64(count * exfat->clus_size);
+ stream_de->dentry.stream.flags |= EXFAT_SF_CONTIGUOUS;
+ node->first_clus = new_clus;
+ node->size = count * exfat->clus_size;
+ node->is_contiguous = true;
+ exfat_bitmap_set(exfat->alloc_bitmap, new_clus);
+ return 1;
truncate_file:
node->size = count * exfat->clus_size;
- if (!heap_clus(exfat, prev))
+ if (!exfat_heap_clus(exfat, prev))
node->first_clus = EXFAT_FREE_CLUSTER;
- exfat_de_iter_get_dirty(&exfat->de_iter, 1, &stream_de);
+ exfat_de_iter_get_dirty(de_iter, 1, &stream_de);
if (count * exfat->clus_size <
- le64_to_cpu(stream_de->stream_valid_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))
+ 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);
+ 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);
+ 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)
+static int root_check_clus_chain(struct exfat *exfat,
+ struct exfat_inode *node,
+ clus_t *clus_count)
{
- clus_t clus;
+ clus_t clus, next, prev = EXFAT_EOF_CLUSTER;
+
+ if (!exfat_heap_clus(exfat, node->first_clus))
+ goto out_trunc;
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)) {
+ if (exfat_repair_ask(&exfat_fsck,
+ ER_FILE_DUPLICATED_CLUS,
+ "ERROR: the cluster chain of root is cyclic"))
+ goto out_trunc;
+ return -EINVAL;
}
- 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_BITMAP_SET(exfat->alloc_bitmap,
- clus - EXFAT_FIRST_CLUSTER);
+ if (exfat_get_inode_next_clus(exfat, node, clus, &next)) {
+ exfat_err("ERROR: failed to read the fat entry of root");
+ goto out_trunc;
+ }
- if (get_next_clus(exfat, node, clus, &clus) != 0) {
- exfat_err("/: broken cluster chain\n");
- return false;
+ if (next != EXFAT_EOF_CLUSTER && !exfat_heap_clus(exfat, next)) {
+ if (exfat_repair_ask(&exfat_fsck,
+ ER_FILE_INVALID_CLUS,
+ "ERROR: the cluster chain of root is broken")) {
+ if (next != EXFAT_BAD_CLUSTER) {
+ prev = clus;
+ (*clus_count)++;
+ }
+ goto out_trunc;
+ }
+ return -EINVAL;
}
+ prev = clus;
+ clus = next;
(*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));
+ return 0;
+out_trunc:
+ if (!exfat_heap_clus(exfat, prev)) {
+ exfat_err("ERROR: the start cluster of root is wrong\n");
+ return -EINVAL;
+ }
+ node->size = *clus_count * exfat->clus_size;
+ return exfat_set_fat(exfat, prev, EXFAT_EOF_CLUSTER);
}
-static int boot_region_checksum(struct exfat_blk_dev *bd, int bs_offset)
+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;
- unsigned int size;
- size = bd->sector_size;
- sect = malloc(size);
+ sect = malloc(sect_size);
if (!sect)
return -ENOMEM;
checksum = 0;
for (i = 0; i < 11; i++) {
- if (exfat_read(bd->dev_fd, sect, size,
- bs_offset * size + i * size) !=
- (ssize_t)size) {
+ 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, size, i == 0, &checksum);
+ boot_calc_checksum(sect, sect_size, i == 0, &checksum);
}
- if (exfat_read(bd->dev_fd, sect, size,
- bs_offset * size + 11 * size) !=
- (ssize_t)size) {
+ 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 < size/sizeof(checksum); i++) {
+ 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);
{
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;
}
static int read_boot_region(struct exfat_blk_dev *bd, struct pbr **pbr,
- int bs_offset)
+ int bs_offset, unsigned int sect_size,
+ bool verbose)
{
struct pbr *bs;
int ret = -EINVAL;
}
if (exfat_read(bd->dev_fd, bs, sizeof(*bs),
- bs_offset * bd->sector_size) != (ssize_t)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) {
- exfat_err("failed to find exfat file system.\n");
+ if (verbose)
+ exfat_err("failed to find exfat file system\n");
goto err;
}
- ret = boot_region_checksum(bd, bs_offset);
+ 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) {
- exfat_err("too small or big sector size: %d\n",
- EXFAT_SECTOR_SIZE(bs));
+ 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) {
- exfat_err("too big cluster size: %d\n", EXFAT_CLUSTER_SIZE(bs));
+ 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) {
- exfat_err("unsupported exfat version: %d.%d\n",
- bs->bsx.fs_version[1], bs->bsx.fs_version[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) {
- exfat_err("unsupported FAT count: %d\n", bs->bsx.num_fats);
+ 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) {
- exfat_err("too large sector count: %" PRIu64 ", expected: %llu\n",
- le64_to_cpu(bs->bsx.vol_length),
- bd->num_sectors);
+ 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) {
- exfat_err("too large cluster count: %u, expected: %u\n",
- le32_to_cpu(bs->bsx.clu_count),
- bd->num_clusters);
+ if (verbose)
+ exfat_err("too large cluster count: %u, expected: %u\n",
+ le32_to_cpu(bs->bsx.clu_count),
+ bd->num_clusters);
goto err;
}
return ret;
}
-static int restore_boot_region(struct exfat_blk_dev *bd)
+static int restore_boot_region(struct exfat_blk_dev *bd, unsigned int sect_size)
{
int i;
char *sector;
+ int ret;
- sector = malloc(bd->sector_size);
+ sector = malloc(sect_size);
if (!sector)
return -ENOMEM;
for (i = 0; i < 12; i++) {
- if (exfat_read(bd->dev_fd, sector, bd->sector_size,
- BACKUP_BOOT_SEC_IDX * bd->sector_size +
- i * bd->sector_size) !=
- (ssize_t)bd->sector_size)
- return -EIO;
+ 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, bd->sector_size,
- BOOT_SEC_IDX * bd->sector_size +
- i * bd->sector_size) !=
- (ssize_t)bd->sector_size)
- return -EIO;
+ 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))
- return -EIO;
+
+ if (fsync(bd->dev_fd)) {
+ ret = -EIO;
+ goto free_sector;
+ }
+ ret = 0;
+
+free_sector:
free(sector);
- return 0;
+ return ret;
}
-static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs)
+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;
- ret = read_boot_region(exfat->blk_dev, bs, BOOT_SEC_IDX);
- if (ret == -EINVAL && exfat_repair_ask(exfat, ER_BS_BOOT_REGION,
- "boot region is corrupted. try to restore the region from backup"
- )) {
- ret = read_boot_region(exfat->blk_dev, bs, BACKUP_BOOT_SEC_IDX);
- if (ret < 0) {
- exfat_err("backup boot region is also corrupted\n");
- return ret;
- }
- ret = restore_boot_region(exfat->blk_dev);
- if (ret < 0) {
- exfat_err("failed to restore boot region from backup\n");
- free(*bs);
- *bs = NULL;
- return 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;
}
- return ret;
-}
-static void dentry_calc_checksum(struct exfat_dentry *dentry,
- __le16 *checksum, bool primary)
-{
- unsigned int i;
- uint8_t *bytes;
+ 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);
- bytes = (uint8_t *)dentry;
+ /* 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;
+ }
- *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[0];
- *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[1];
+ 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");
+ }
- i = primary ? 4 : 2;
- for (; i < sizeof(*dentry); i++) {
- *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[i];
+ 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 __le16 file_calc_checksum(struct exfat_de_iter *iter)
+static uint16_t file_calc_checksum(struct exfat_de_iter *iter)
{
- __le16 checksum;
+ uint16_t 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);
+ 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);
- dentry_calc_checksum(de, &checksum, false);
+ exfat_calc_dentry_checksum(de, &checksum, false);
}
-
return checksum;
}
uint16_t checksum;
bool valid = true;
- ret = check_clus_chain(exfat, node);
+ ret = check_clus_chain(iter, node);
if (ret < 0)
return ret;
if (node->size == 0 && node->is_contiguous) {
if (repair_file_ask(iter, node, ER_FILE_ZERO_NOFAT,
- "empty, but has no Fat chain\n")) {
+ "empty, but has no Fat chain")) {
exfat_de_iter_get_dirty(iter, 1, &dentry);
dentry->stream_flags &= ~EXFAT_SF_CONTIGUOUS;
ret = 1;
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;
+ exfat_de_iter_get_dirty(iter, 0, &dentry);
+ dentry->file_checksum = cpu_to_le16(checksum);
+ ret = 1;
}
return valid ? ret : -EINVAL;
}
-static int read_file_dentries(struct exfat_de_iter *iter,
- struct exfat_inode **new_node, int *skip_dentries)
+static int check_name_dentry_set(struct exfat_de_iter *iter,
+ struct exfat_inode *inode)
{
- struct exfat_dentry *file_de, *stream_de, *name_de;
- struct exfat_inode *node;
- int i, ret;
+ struct exfat_dentry *stream_de;
+ size_t name_len;
+ __u16 hash;
+
+ exfat_de_iter_get(iter, 1, &stream_de);
+
+ name_len = exfat_utf16_len(inode->name, NAME_BUFFER_SIZE);
+ if (stream_de->stream_name_len != name_len) {
+ if (repair_file_ask(iter, NULL, ER_DE_NAME_LEN,
+ "the name length of a file is wrong")) {
+ exfat_de_iter_get_dirty(iter, 1, &stream_de);
+ stream_de->stream_name_len = (__u8)name_len;
+ } else {
+ return -EINVAL;
+ }
+ }
+
+ hash = exfat_calc_name_hash(iter->exfat, inode->name, (int)name_len);
+ if (cpu_to_le16(hash) != stream_de->stream_name_hash) {
+ if (repair_file_ask(iter, NULL, ER_DE_NAME_HASH,
+ "the name hash of a file is wrong")) {
+ exfat_de_iter_get_dirty(iter, 1, &stream_de);
+ stream_de->stream_name_hash = cpu_to_le16(hash);
+ } else {
+ return -EINVAL;
+ }
+ }
+ return 0;
+}
+
+static int check_bad_char(char w)
+{
+ return (w < 0x0020) || (w == '*') || (w == '?') || (w == '<') ||
+ (w == '>') || (w == '|') || (w == '"') || (w == ':') ||
+ (w == '/') || (w == '\\');
+}
+
+static char *get_rename_from_user(struct exfat_de_iter *iter)
+{
+ char *rename = malloc(ENTRY_NAME_MAX + 2);
- /* TODO: mtime, atime, ... */
+ if (!rename)
+ return NULL;
+
+retry:
+ /* +2 means LF(Line Feed) and NULL terminator */
+ memset(rename, 0x1, ENTRY_NAME_MAX + 2);
+ printf("New name: ");
+ if (fgets(rename, ENTRY_NAME_MAX + 2, stdin)) {
+ int i, len, err;
+ struct exfat_lookup_filter filter;
+
+ len = strlen(rename);
+ /* Remove LF in filename */
+ rename[len - 1] = '\0';
+ for (i = 0; i < len - 1; i++) {
+ if (check_bad_char(rename[i])) {
+ printf("filename contain invalid character(%c)\n", rename[i]);
+ goto retry;
+ }
+ }
+
+ exfat_de_iter_flush(iter);
+ err = exfat_lookup_file(iter->exfat, iter->parent, rename, &filter);
+ if (!err) {
+ printf("file(%s) already exists, retry to insert name\n", rename);
+ goto retry;
+ }
+ }
+
+ return rename;
+}
+
+static char *generate_rename(struct exfat_de_iter *iter)
+{
+ char *rename;
+
+ if (iter->dot_name_num > DOT_NAME_NUM_MAX)
+ return NULL;
+
+ rename = malloc(ENTRY_NAME_MAX + 1);
+ if (!rename)
+ return NULL;
+
+ while (1) {
+ struct exfat_lookup_filter filter;
+ int err;
+
+ snprintf(rename, ENTRY_NAME_MAX + 1, "FILE%07d.CHK",
+ iter->dot_name_num++);
+ err = exfat_lookup_file(iter->exfat, iter->parent, rename,
+ &filter);
+ if (!err)
+ continue;
+ break;
+ }
+
+ return rename;
+}
+
+const __le16 MSDOS_DOT[ENTRY_NAME_MAX] = {cpu_to_le16(46), 0, };
+const __le16 MSDOS_DOTDOT[ENTRY_NAME_MAX] = {cpu_to_le16(46), cpu_to_le16(46), 0, };
+
+static int handle_dot_dotdot_filename(struct exfat_de_iter *iter,
+ struct exfat_dentry *dentry,
+ int strm_name_len)
+{
+ char *filename;
+ char error_msg[150];
+ int num;
+
+ if (!memcmp(dentry->name_unicode, MSDOS_DOT, strm_name_len * 2))
+ filename = ".";
+ else if (!memcmp(dentry->name_unicode, MSDOS_DOTDOT,
+ strm_name_len * 2))
+ filename = "..";
+ else
+ return 0;
+
+ sprintf(error_msg, "ERROR: '%s' filename is not allowed.\n"
+ " [1] Insert the name you want to rename.\n"
+ " [2] Automatically renames filename.\n"
+ " [3] Bypass this check(No repair)\n", filename);
+ask_again:
+ num = exfat_repair_ask(&exfat_fsck, ER_DE_DOT_NAME,
+ error_msg);
+ if (num) {
+ __le16 utf16_name[ENTRY_NAME_MAX];
+ char *rename = NULL;
+ __u16 hash;
+ struct exfat_dentry *stream_de;
+ int name_len, ret;
+
+ switch (num) {
+ case 1:
+ rename = get_rename_from_user(iter);
+ break;
+ case 2:
+ rename = generate_rename(iter);
+ break;
+ case 3:
+ break;
+ default:
+ exfat_info("select 1 or 2 number instead of %d\n", num);
+ goto ask_again;
+ }
+
+ if (!rename)
+ return -EINVAL;
+
+ exfat_info("%s filename is renamed to %s\n", filename, rename);
+
+ exfat_de_iter_get_dirty(iter, 2, &dentry);
+
+ memset(utf16_name, 0, sizeof(utf16_name));
+ ret = exfat_utf16_enc(rename, utf16_name, sizeof(utf16_name));
+ free(rename);
+ if (ret < 0)
+ return ret;
+
+ memcpy(dentry->name_unicode, utf16_name, ENTRY_NAME_MAX * 2);
+ name_len = exfat_utf16_len(utf16_name, ENTRY_NAME_MAX * 2);
+ hash = exfat_calc_name_hash(iter->exfat, utf16_name, (int)name_len);
+ exfat_de_iter_get_dirty(iter, 1, &stream_de);
+ stream_de->stream_name_len = (__u8)name_len;
+ stream_de->stream_name_hash = cpu_to_le16(hash);
+ }
+
+ return 0;
+}
+
+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, *dentry;
+ struct exfat_inode *node = NULL;
+ int i, ret;
+ bool need_delete = false;
+ uint16_t checksum;
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);
+ exfat_err("failed to get file dentry\n");
return -EINVAL;
}
+
+ checksum = file_calc_checksum(iter);
+ if (checksum != le16_to_cpu(file_de->file_checksum)) {
+ if (repair_file_ask(iter, NULL, ER_DE_CHECKSUM,
+ "the checksum of a file is wrong"))
+ need_delete = true;
+ *skip_dentries = 1;
+ goto skip_dset;
+ }
+
+ if (file_de->file_num_ext < 2) {
+ if (repair_file_ask(iter, NULL, ER_DE_SECONDARY_COUNT,
+ "a file has too few secondary count. %d",
+ file_de->file_num_ext))
+ need_delete = true;
+ *skip_dentries = 1;
+ goto skip_dset;
+ }
+
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;
+ if (repair_file_ask(iter, NULL, ER_DE_STREAM,
+ "failed to get stream dentry"))
+ need_delete = true;
+ *skip_dentries = 2;
+ goto skip_dset;
}
*new_node = NULL;
- node = alloc_exfat_inode(le16_to_cpu(file_de->file_attr));
+ 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);
- 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;
+ ret = exfat_de_iter_get(iter, i, &dentry);
+ if (ret || dentry->type != EXFAT_NAME) {
+ if (i > 2 && repair_file_ask(iter, NULL, ER_DE_NAME,
+ "failed to get name dentry")) {
+ exfat_de_iter_get_dirty(iter, 0, &file_de);
+ file_de->file_num_ext = i - 1;
+ break;
+ }
+ *skip_dentries = i + 1;
+ goto skip_dset;
}
memcpy(node->name +
- (i-2) * ENTRY_NAME_MAX, name_de->name_unicode,
- sizeof(name_de->name_unicode));
+ (i - 2) * ENTRY_NAME_MAX, dentry->name_unicode,
+ sizeof(dentry->name_unicode));
+ }
+
+ ret = check_name_dentry_set(iter, node);
+ if (ret) {
+ *skip_dentries = file_de->file_num_ext + 1;
+ goto skip_dset;
+ }
+
+ if (file_de->file_num_ext == 2 && stream_de->stream_name_len <= 2) {
+ ret = handle_dot_dotdot_filename(iter, dentry,
+ stream_de->stream_name_len);
+ if (ret < 0) {
+ *skip_dentries = file_de->file_num_ext + 1;
+ goto skip_dset;
+ }
}
node->first_clus = le32_to_cpu(stream_de->stream_start_clu);
node->size = le64_to_cpu(stream_de->stream_size);
if (node->size < le64_to_cpu(stream_de->stream_valid_size)) {
+ *skip_dentries = file_de->file_num_ext + 1;
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)) {
+ "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;
+ goto skip_dset;
}
}
*skip_dentries = (file_de->file_num_ext + 1);
*new_node = node;
return 0;
-err:
- *skip_dentries = 0;
+skip_dset:
+ if (need_delete) {
+ exfat_de_iter_get_dirty(iter, 0, &dentry);
+ dentry->type &= EXFAT_DELETE;
+ }
+ for (i = 1; i < *skip_dentries; i++) {
+ exfat_de_iter_get(iter, i, &dentry);
+ if (dentry->type == EXFAT_FILE)
+ break;
+ if (need_delete) {
+ exfat_de_iter_get_dirty(iter, i, &dentry);
+ dentry->type &= EXFAT_DELETE;
+ }
+ }
+ *skip_dentries = i;
*new_node = NULL;
- free_exfat_inode(node);
- return ret;
+ exfat_free_inode(node);
+ return need_delete ? 1 : -EINVAL;
}
static int read_file(struct exfat_de_iter *de_iter,
*new_node = NULL;
- ret = read_file_dentries(de_iter, &node, dentry_count);
+ ret = read_file_dentry_set(de_iter, &node, dentry_count);
if (ret)
return ret;
ret = check_inode(de_iter, node);
if (ret < 0) {
- free_exfat_inode(node);
+ 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 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)
+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;
- struct exfat *exfat;
+ int retval;
- exfat = iter->exfat;
- if (exfat_de_iter_get(iter, 0, &dentry))
- return false;
+ 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));
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;
+ return -EINVAL;
}
- if (!heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) {
+ 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 false;
+ 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, le64_to_cpu(dentry->bitmap_start_clu),
- DIV_ROUND_UP(exfat->disk_bitmap_size,
- exfat->clus_size));
+ 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 false;
+ 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;
- return true;
+ 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 bool read_upcase_table(struct exfat_de_iter *iter)
+static int read_upcase_table(struct exfat *exfat)
{
- struct exfat_dentry *dentry;
- 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;
- __le16 *upcase;
__le32 checksum;
- exfat = iter->exfat;
+ retval = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
+ if (retval)
+ return retval;
- if (exfat_de_iter_get(iter, 0, &dentry))
- return false;
+ dentry = filter.out.dentry_set;
- if (!heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) {
+ 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));
- return false;
+ retval = -EINVAL;
+ goto out;
}
size = (ssize_t)le64_to_cpu(dentry->upcase_size);
size == 0 || size % sizeof(__le16)) {
exfat_err("invalid size of upcase table. 0x%" PRIx64 "\n",
le64_to_cpu(dentry->upcase_size));
- return false;
+ retval = -EINVAL;
+ goto out;
}
upcase = (__le16 *)malloc(size);
if (!upcase) {
exfat_err("failed to allocate upcase table\n");
- return false;
+ 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");
- free(upcase);
- return false;
+ retval = -EIO;
+ goto out;
}
checksum = 0;
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;
+ retval = -EINVAL;
+ goto out;
}
- exfat_bitmap_set_range(exfat, le32_to_cpu(dentry->upcase_start_clu),
- DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size),
- exfat->clus_size));
+ 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));
- free(upcase);
- return true;
+ 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 *exfat, struct exfat_inode *dir)
+static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir)
{
- int ret;
+ struct exfat *exfat = fsck->exfat;
struct exfat_inode *node = NULL;
struct exfat_dentry *dentry;
- int dentry_count;
struct exfat_de_iter *de_iter;
+ int dentry_count;
+ int ret;
- de_iter = &exfat->de_iter;
- ret = exfat_de_iter_init(de_iter, exfat, dir);
+ 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)
ret = read_file(de_iter, &node, &dentry_count);
if (ret < 0) {
exfat_stat.error_count++;
- goto err;
+ 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
- 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;
+ if (node) {
+ 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;
- exfat_err("unknown entry type. 0x%x\n", dentry->type);
- ret = -EINVAL;
- goto err;
+ 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);
exfat_de_iter_flush(de_iter);
return 0;
err:
- inode_free_children(dir, false);
+ exfat_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)
+/* write bitmap segments for clusters which are marked
+ * as free, but allocated to files.
+ */
+static int write_bitmap(struct exfat_fsck *fsck)
{
- 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;
- }
+ 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;
}
- idx ^= 0x01;
- clus = clus + clus_count;
- offset += len;
- }
- return 0;
-}
+ byte_offset = ((i * sizeof(bitmap_t)) / 512) * 512;
+ write_bytes = MIN(512, bitmap_bytes - byte_offset);
-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)
+ 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;
- /* 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;
+ i = (byte_offset + write_bytes) / sizeof(bitmap_t);
}
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;
}
/*
* 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)
+static int exfat_filesystem_check(struct exfat_fsck *fsck)
{
+ struct exfat *exfat = fsck->exfat;
struct exfat_inode *dir;
int ret = 0, dir_errors;
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);
+ dir = list_entry(exfat->dir_list.next,
+ struct exfat_inode, list);
if (!(dir->attr & ATTR_SUBDIR)) {
fsck_err(dir->parent, dir,
goto out;
}
- dir_errors = read_children(exfat, dir);
+ dir_errors = read_children(fsck, dir);
if (dir_errors) {
- resolve_path(&path_resolve_ctx, dir);
+ 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);
- inode_free_file_children(dir);
- inode_free_ancestors(dir);
+ exfat_free_file_children(dir);
+ exfat_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;
+ clus_t clus_count = 0;
+ int err;
- root = alloc_exfat_inode(ATTR_SUBDIR);
- if (!root) {
- exfat_err("failed to allocate memory\n");
+ root = exfat_alloc_inode(ATTR_SUBDIR);
+ if (!root)
return -ENOMEM;
- }
+ exfat->root = root;
root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster);
- if (!root_get_clus_count(exfat, root, &clus_count)) {
+ if (root_check_clus_chain(exfat, root, &clus_count)) {
exfat_err("failed to follow the cluster chain of root\n");
- free_exfat_inode(root);
+ exfat_free_inode(root);
+ exfat->root = NULL;
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);
+
+ err = exfat_read_volume_label(exfat);
+ if (err && err != EOF)
+ exfat_err("failed to read volume label\n");
+ err = 0;
+
+ 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;
- shift = 0;
- for (i = 0; i < sizeof(units)/sizeof(units[0]); i++) {
- if (bytes / (1ULL << (shift + 10)) == 0)
- break;
- shift += 10;
- }
+ while (i && (bytes >> i * 10) == 0)
+ i--;
+ shift = i * 10;
quoti = (unsigned int)(bytes / (1ULL << shift));
remain = 0;
if (shift > 0) {
return buf;
}
-static void exfat_show_info(struct exfat *exfat, const char *dev_name,
- int errors)
+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",
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,
- errors ? "checking stopped" : "clean",
+ clean ? "clean" : "corrupted",
exfat_stat.dir_count, exfat_stat.file_count);
- if (errors || exfat->dirty)
+ 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.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 exfat *exfat = NULL;
struct pbr *bs = NULL;
int c, ret, exit_code;
bool version_only = false;
exfat_err("failed to init locale/codeset\n");
opterr = 0;
- while ((c = getopt_long(argc, argv, "rynpVvh", opts, NULL)) != EOF) {
+ 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_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;
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) {
return FSCK_EXIT_OPERATION_ERROR;
}
- exfat = (struct exfat *)calloc(1, sizeof(*exfat));
- if (!exfat) {
- exfat_err("failed to allocate exfat\n");
- ret = -ENOMEM;
+ ret = exfat_boot_region_check(&bd, &bs,
+ ui.options & FSCK_OPTS_IGNORE_BAD_FS_NAME ?
+ true : false);
+ if (ret)
goto err;
- }
- exfat->blk_dev = &bd;
- exfat->options = ui.options;
- ret = exfat_boot_region_check(exfat, &bs);
- if (ret)
+ exfat_fsck.exfat = exfat_alloc_exfat(&bd, bs);
+ if (!exfat_fsck.exfat) {
+ ret = -ENOMEM;
goto err;
+ }
- ret = init_exfat(exfat, bs);
- if (ret)
+ 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_mark_volume_dirty(exfat, true)) {
+ 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);
+ 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);
+ 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;
}
- exfat_mark_volume_dirty(exfat, false);
+ if (exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE)
+ exfat_mark_volume_dirty(exfat_fsck.exfat, false);
out:
- exfat_show_info(exfat, ui.ei.dev_name, ret);
+ exfat_show_info(&exfat_fsck, ui.ei.dev_name);
err:
- if (ret == -EINVAL)
- exit_code = FSCK_EXIT_ERRORS_LEFT;
- else if (ret)
+ if (ret && ret != -EINVAL)
exit_code = FSCK_EXIT_OPERATION_ERROR;
- else if (exfat->dirty)
+ 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;
- free_exfat(exfat);
+ 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;
}