| // SPDX-License-Identifier: GPL-2.0-or-later |
| /* |
| * Copyright (C) 2019 Namjae Jeon <linkinjeon@kernel.org> |
| * Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com> |
| */ |
| |
| #include <unistd.h> |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <getopt.h> |
| #include <inttypes.h> |
| #include <string.h> |
| #include <errno.h> |
| #include <locale.h> |
| |
| #include "exfat_ondisk.h" |
| #include "libexfat.h" |
| #include "fsck.h" |
| #include "repair.h" |
| |
| struct fsck_user_input { |
| struct exfat_user_input ei; |
| enum fsck_ui_options options; |
| }; |
| |
| #define EXFAT_MAX_UPCASE_CHARS 0x10000 |
| |
| #ifdef WORDS_BIGENDIAN |
| typedef __u8 bitmap_t; |
| #else |
| typedef __u32 bitmap_t; |
| #endif |
| |
| #define BITS_PER (sizeof(bitmap_t) * 8) |
| #define BIT_MASK(__c) (1 << ((__c) % BITS_PER)) |
| #define BIT_ENTRY(__c) ((__c) / BITS_PER) |
| |
| #define EXFAT_BITMAP_SIZE(__c_count) \ |
| (DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t)) |
| #define EXFAT_BITMAP_GET(__bmap, __c) \ |
| (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] & BIT_MASK(__c)) |
| #define EXFAT_BITMAP_SET(__bmap, __c) \ |
| (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] |= \ |
| BIT_MASK(__c)) |
| |
| #define FSCK_EXIT_NO_ERRORS 0x00 |
| #define FSCK_EXIT_CORRECTED 0x01 |
| #define FSCK_EXIT_NEED_REBOOT 0x02 |
| #define FSCK_EXIT_ERRORS_LEFT 0x04 |
| #define FSCK_EXIT_OPERATION_ERROR 0x08 |
| #define FSCK_EXIT_SYNTAX_ERROR 0x10 |
| #define FSCK_EXIT_USER_CANCEL 0x20 |
| #define FSCK_EXIT_LIBRARY_ERROR 0x80 |
| |
| struct exfat_stat { |
| long dir_count; |
| long file_count; |
| long error_count; |
| long fixed_count; |
| }; |
| |
| struct path_resolve_ctx { |
| struct exfat_inode *ancestors[255]; |
| __le16 utf16_path[PATH_MAX + 2]; |
| char local_path[PATH_MAX * MB_LEN_MAX + 1]; |
| }; |
| |
| struct exfat_stat exfat_stat; |
| struct path_resolve_ctx path_resolve_ctx; |
| |
| static struct option opts[] = { |
| {"repair", no_argument, NULL, 'r' }, |
| {"repair-yes", no_argument, NULL, 'y' }, |
| {"repair-no", no_argument, NULL, 'n' }, |
| {"repair-auto", no_argument, NULL, 'p' }, |
| {"version", no_argument, NULL, 'V' }, |
| {"verbose", no_argument, NULL, 'v' }, |
| {"help", no_argument, NULL, 'h' }, |
| {"?", no_argument, NULL, '?' }, |
| {NULL, 0, NULL, 0 } |
| }; |
| |
| static void usage(char *name) |
| { |
| fprintf(stderr, "Usage: %s\n", name); |
| fprintf(stderr, "\t-r | --repair Repair interactively\n"); |
| fprintf(stderr, "\t-y | --repair-yes Repair without ask\n"); |
| fprintf(stderr, "\t-n | --repair-no No repair\n"); |
| fprintf(stderr, "\t-p | --repair-auto Repair automatically\n"); |
| fprintf(stderr, "\t-a Repair automatically\n"); |
| fprintf(stderr, "\t-V | --version Show version\n"); |
| fprintf(stderr, "\t-v | --verbose Print debug\n"); |
| fprintf(stderr, "\t-h | --help Show help\n"); |
| |
| exit(FSCK_EXIT_SYNTAX_ERROR); |
| } |
| |
| #define fsck_err(parent, inode, fmt, ...) \ |
| ({ \ |
| resolve_path_parent(&path_resolve_ctx, \ |
| parent, inode); \ |
| exfat_err("ERROR: %s: " fmt, \ |
| path_resolve_ctx.local_path, \ |
| ##__VA_ARGS__); \ |
| }) |
| |
| static struct exfat_inode *alloc_exfat_inode(__u16 attr) |
| { |
| struct exfat_inode *node; |
| int size; |
| |
| size = offsetof(struct exfat_inode, name) + NAME_BUFFER_SIZE; |
| node = (struct exfat_inode *)calloc(1, size); |
| if (!node) { |
| exfat_err("failed to allocate exfat_node\n"); |
| return NULL; |
| } |
| |
| node->parent = NULL; |
| INIT_LIST_HEAD(&node->children); |
| INIT_LIST_HEAD(&node->sibling); |
| INIT_LIST_HEAD(&node->list); |
| |
| node->last_pclus = EXFAT_EOF_CLUSTER; |
| node->attr = attr; |
| if (attr & ATTR_SUBDIR) |
| exfat_stat.dir_count++; |
| else |
| exfat_stat.file_count++; |
| return node; |
| } |
| |
| static void free_exfat_inode(struct exfat_inode *node) |
| { |
| free(node); |
| } |
| |
| static void inode_free_children(struct exfat_inode *dir, bool file_only) |
| { |
| struct exfat_inode *node, *i; |
| |
| list_for_each_entry_safe(node, i, &dir->children, sibling) { |
| if (file_only) { |
| if (!(node->attr & ATTR_SUBDIR)) { |
| list_del(&node->sibling); |
| free_exfat_inode(node); |
| } |
| } else { |
| list_del(&node->sibling); |
| list_del(&node->list); |
| free_exfat_inode(node); |
| } |
| } |
| } |
| |
| static void inode_free_file_children(struct exfat_inode *dir) |
| { |
| inode_free_children(dir, true); |
| } |
| |
| /* delete @child and all ancestors that does not have |
| * children |
| */ |
| static void inode_free_ancestors(struct exfat_inode *child) |
| { |
| struct exfat_inode *parent; |
| |
| if (!list_empty(&child->children)) |
| return; |
| |
| do { |
| if (!(child->attr & ATTR_SUBDIR)) { |
| exfat_err("not directory.\n"); |
| return; |
| } |
| |
| parent = child->parent; |
| list_del(&child->sibling); |
| free_exfat_inode(child); |
| |
| child = parent; |
| } while (child && list_empty(&child->children)); |
| |
| return; |
| } |
| |
| static void free_exfat(struct exfat *exfat) |
| { |
| int i; |
| |
| if (exfat) { |
| if (exfat->bs) |
| free(exfat->bs); |
| if (exfat->alloc_bitmap) |
| free(exfat->alloc_bitmap); |
| if (exfat->disk_bitmap) |
| free(exfat->disk_bitmap); |
| for (i = 0; i < 2; i++) { |
| if (exfat->buffer_desc[i].buffer) |
| free(exfat->buffer_desc[i].buffer); |
| if (exfat->buffer_desc[i].dirty) |
| free(exfat->buffer_desc[i].dirty); |
| } |
| free(exfat); |
| } |
| } |
| |
| static int init_exfat(struct exfat *exfat, struct pbr *bs) |
| { |
| int i; |
| |
| INIT_LIST_HEAD(&exfat->dir_list); |
| exfat->bs = bs; |
| exfat->clus_count = le32_to_cpu(bs->bsx.clu_count); |
| exfat->clus_size = EXFAT_CLUSTER_SIZE(bs); |
| exfat->sect_size = EXFAT_SECTOR_SIZE(bs); |
| |
| /* TODO: bitmap could be very large. */ |
| exfat->alloc_bitmap = (char *)calloc(1, |
| EXFAT_BITMAP_SIZE(exfat->clus_count)); |
| if (!exfat->alloc_bitmap) { |
| exfat_err("failed to allocate bitmap\n"); |
| goto err; |
| } |
| |
| exfat->disk_bitmap = (char *)malloc( |
| EXFAT_BITMAP_SIZE(exfat->clus_count)); |
| if (!exfat->disk_bitmap) { |
| exfat_err("failed to allocate bitmap\n"); |
| goto err; |
| } |
| |
| /* allocate cluster buffers */ |
| for (i = 0; i < 2; i++) { |
| exfat->buffer_desc[i].buffer = |
| (char *)malloc(exfat->clus_size); |
| if (!exfat->buffer_desc[i].buffer) |
| goto err; |
| exfat->buffer_desc[i].dirty = |
| (char *)calloc( |
| (exfat->clus_size / exfat->sect_size), 1); |
| if (!exfat->buffer_desc[i].dirty) |
| goto err; |
| } |
| return 0; |
| err: |
| free_exfat(exfat); |
| return -ENOMEM; |
| } |
| |
| static void exfat_free_dir_list(struct exfat *exfat) |
| { |
| struct exfat_inode *dir, *i; |
| |
| list_for_each_entry_safe(dir, i, &exfat->dir_list, list) { |
| inode_free_file_children(dir); |
| list_del(&dir->list); |
| free_exfat_inode(dir); |
| } |
| } |
| |
| /* |
| * get references of ancestors that include @child until the count of |
| * 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__); \ |
| }) |
| |
| static inline bool heap_clus(struct exfat *exfat, clus_t clus) |
| { |
| return clus >= EXFAT_FIRST_CLUSTER && |
| (clus - EXFAT_FIRST_CLUSTER) < exfat->clus_count; |
| } |
| |
| int get_next_clus(struct exfat *exfat, struct exfat_inode *node, |
| clus_t clus, clus_t *next) |
| { |
| off_t offset; |
| |
| *next = EXFAT_EOF_CLUSTER; |
| |
| if (!heap_clus(exfat, clus)) |
| return -EINVAL; |
| |
| if (node->is_contiguous) { |
| *next = clus + 1; |
| return 0; |
| } |
| |
| offset = (off_t)le32_to_cpu(exfat->bs->bsx.fat_offset) << |
| exfat->bs->bsx.sect_size_bits; |
| offset += sizeof(clus_t) * clus; |
| |
| if (exfat_read(exfat->blk_dev->dev_fd, next, sizeof(*next), offset) |
| != sizeof(*next)) |
| return -EIO; |
| *next = le32_to_cpu(*next); |
| return 0; |
| } |
| |
| static int set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus) |
| { |
| off_t offset; |
| |
| offset = le32_to_cpu(exfat->bs->bsx.fat_offset) << |
| exfat->bs->bsx.sect_size_bits; |
| offset += sizeof(clus_t) * clus; |
| |
| if (exfat_write(exfat->blk_dev->dev_fd, &next_clus, sizeof(next_clus), |
| offset) != sizeof(next_clus)) |
| return -EIO; |
| return 0; |
| } |
| |
| static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) |
| { |
| struct exfat_dentry *stream_de; |
| clus_t clus, prev, next; |
| uint64_t count, max_count; |
| |
| clus = node->first_clus; |
| prev = EXFAT_EOF_CLUSTER; |
| count = 0; |
| max_count = DIV_ROUND_UP(node->size, exfat->clus_size); |
| |
| if (node->size == 0 && node->first_clus == EXFAT_FREE_CLUSTER) |
| return 0; |
| |
| /* the first cluster is wrong */ |
| if ((node->size == 0 && node->first_clus != EXFAT_FREE_CLUSTER) || |
| (node->size > 0 && !heap_clus(exfat, node->first_clus))) { |
| if (repair_file_ask(&exfat->de_iter, node, |
| ER_FILE_FIRST_CLUS, "first cluster is wrong")) |
| goto truncate_file; |
| else |
| return -EINVAL; |
| } |
| |
| while (clus != EXFAT_EOF_CLUSTER) { |
| if (count >= max_count) { |
| if (node->is_contiguous) |
| break; |
| if (repair_file_ask(&exfat->de_iter, node, |
| ER_FILE_SMALLER_SIZE, |
| "more clusters are allocated. " |
| "truncate to %" PRIu64 " bytes", |
| count * exfat->clus_size)) |
| goto truncate_file; |
| else |
| return -EINVAL; |
| } |
| |
| /* |
| * This cluster is already allocated. it may be shared with |
| * the other file, or there is a loop in cluster chain. |
| */ |
| if (EXFAT_BITMAP_GET(exfat->alloc_bitmap, |
| clus - EXFAT_FIRST_CLUSTER)) { |
| if (repair_file_ask(&exfat->de_iter, node, |
| ER_FILE_DUPLICATED_CLUS, |
| "cluster is already allocated for " |
| "the other file. truncated to %" |
| PRIu64 " bytes", |
| count * exfat->clus_size)) |
| goto truncate_file; |
| else |
| return -EINVAL; |
| } |
| |
| if (!EXFAT_BITMAP_GET(exfat->disk_bitmap, |
| clus - EXFAT_FIRST_CLUSTER)) { |
| if (repair_file_ask(&exfat->de_iter, node, |
| ER_FILE_INVALID_CLUS, |
| "cluster is marked as free. truncate to %" PRIu64 " bytes", |
| count * exfat->clus_size)) |
| goto truncate_file; |
| |
| else |
| return -EINVAL; |
| } |
| |
| /* This cluster is allocated or not */ |
| if (get_next_clus(exfat, node, clus, &next)) |
| goto truncate_file; |
| if (!node->is_contiguous) { |
| if (!heap_clus(exfat, next) && |
| next != EXFAT_EOF_CLUSTER) { |
| if (repair_file_ask(&exfat->de_iter, node, |
| ER_FILE_INVALID_CLUS, |
| "broken cluster chain. " |
| "truncate to %" |
| PRIu64 " bytes", |
| count * exfat->clus_size)) |
| goto truncate_file; |
| |
| else |
| return -EINVAL; |
| } |
| } |
| |
| count++; |
| EXFAT_BITMAP_SET(exfat->alloc_bitmap, |
| clus - EXFAT_FIRST_CLUSTER); |
| prev = clus; |
| clus = next; |
| } |
| |
| if (count < max_count) { |
| if (repair_file_ask(&exfat->de_iter, node, |
| ER_FILE_LARGER_SIZE, "less clusters are allocated. " |
| "truncates to %" PRIu64 " bytes", |
| count * exfat->clus_size)) |
| goto truncate_file; |
| else |
| return -EINVAL; |
| } |
| |
| return 0; |
| truncate_file: |
| node->size = count * exfat->clus_size; |
| if (!heap_clus(exfat, prev)) |
| node->first_clus = EXFAT_FREE_CLUSTER; |
| |
| exfat_de_iter_get_dirty(&exfat->de_iter, 1, &stream_de); |
| if (count * exfat->clus_size < |
| le64_to_cpu(stream_de->stream_valid_size)) |
| stream_de->stream_valid_size = cpu_to_le64( |
| count * exfat->clus_size); |
| if (!heap_clus(exfat, prev)) |
| stream_de->stream_start_clu = EXFAT_FREE_CLUSTER; |
| stream_de->stream_size = cpu_to_le64( |
| count * exfat->clus_size); |
| |
| /* remaining clusters will be freed while FAT is compared with |
| * alloc_bitmap. |
| */ |
| if (!node->is_contiguous && heap_clus(exfat, prev)) |
| return set_fat(exfat, prev, EXFAT_EOF_CLUSTER); |
| return 1; |
| } |
| |
| static bool root_get_clus_count(struct exfat *exfat, struct exfat_inode *node, |
| clus_t *clus_count) |
| { |
| clus_t clus; |
| |
| clus = node->first_clus; |
| *clus_count = 0; |
| |
| do { |
| if (!heap_clus(exfat, clus)) { |
| exfat_err("/: bad cluster. 0x%x\n", clus); |
| return false; |
| } |
| |
| if (EXFAT_BITMAP_GET(exfat->alloc_bitmap, |
| clus - EXFAT_FIRST_CLUSTER)) { |
| exfat_err("/: cluster is already allocated, or " |
| "there is a loop in cluster chain\n"); |
| return false; |
| } |
| |
| EXFAT_BITMAP_SET(exfat->alloc_bitmap, |
| clus - EXFAT_FIRST_CLUSTER); |
| |
| if (get_next_clus(exfat, node, clus, &clus) != 0) { |
| exfat_err("/: broken cluster chain\n"); |
| return false; |
| } |
| |
| (*clus_count)++; |
| } while (clus != EXFAT_EOF_CLUSTER); |
| return true; |
| } |
| |
| static off_t exfat_s2o(struct exfat *exfat, off_t sect) |
| { |
| return sect << exfat->bs->bsx.sect_size_bits; |
| } |
| |
| off_t exfat_c2o(struct exfat *exfat, unsigned int clus) |
| { |
| if (clus < EXFAT_FIRST_CLUSTER) |
| return ~0L; |
| |
| return exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset) + |
| ((off_t)(clus - EXFAT_FIRST_CLUSTER) << |
| exfat->bs->bsx.sect_per_clus_bits)); |
| } |
| |
| static int boot_region_checksum(struct exfat_blk_dev *bd, int bs_offset) |
| { |
| void *sect; |
| unsigned int i; |
| uint32_t checksum; |
| int ret = 0; |
| unsigned int size; |
| |
| size = bd->sector_size; |
| sect = malloc(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) { |
| exfat_err("failed to read boot region\n"); |
| ret = -EIO; |
| goto out; |
| } |
| boot_calc_checksum(sect, size, i == 0, &checksum); |
| } |
| |
| if (exfat_read(bd->dev_fd, sect, size, |
| bs_offset * size + 11 * size) != |
| (ssize_t)size) { |
| exfat_err("failed to read a boot checksum sector\n"); |
| ret = -EIO; |
| goto out; |
| } |
| |
| for (i = 0; i < size/sizeof(checksum); i++) { |
| if (le32_to_cpu(((__le32 *)sect)[i]) != checksum) { |
| exfat_err("checksum of boot region is not correct. %#x, but expected %#x\n", |
| le32_to_cpu(((__le32 *)sect)[i]), checksum); |
| ret = -EINVAL; |
| goto out; |
| } |
| } |
| out: |
| free(sect); |
| return ret; |
| } |
| |
| static int exfat_mark_volume_dirty(struct exfat *exfat, bool dirty) |
| { |
| uint16_t flags; |
| |
| if (!(exfat->options & FSCK_OPTS_REPAIR_WRITE)) |
| return 0; |
| |
| flags = le16_to_cpu(exfat->bs->bsx.vol_flags); |
| if (dirty) |
| flags |= 0x02; |
| else |
| flags &= ~0x02; |
| |
| exfat->bs->bsx.vol_flags = cpu_to_le16(flags); |
| if (exfat_write(exfat->blk_dev->dev_fd, exfat->bs, |
| sizeof(struct pbr), 0) != (ssize_t)sizeof(struct pbr)) { |
| exfat_err("failed to set VolumeDirty\n"); |
| return -EIO; |
| } |
| |
| if (fsync(exfat->blk_dev->dev_fd) != 0) { |
| exfat_err("failed to set VolumeDirty\n"); |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| static int read_boot_region(struct exfat_blk_dev *bd, struct pbr **pbr, |
| int bs_offset) |
| { |
| 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 * bd->sector_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"); |
| goto err; |
| } |
| |
| ret = boot_region_checksum(bd, bs_offset); |
| 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)); |
| goto err; |
| } |
| |
| if (EXFAT_CLUSTER_SIZE(bs) > 32 * MB) { |
| 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]); |
| goto err; |
| } |
| |
| if (bs->bsx.num_fats != 1) { |
| 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); |
| 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); |
| goto err; |
| } |
| |
| *pbr = bs; |
| return 0; |
| err: |
| free(bs); |
| return ret; |
| } |
| |
| static int restore_boot_region(struct exfat_blk_dev *bd) |
| { |
| int i; |
| char *sector; |
| int ret; |
| |
| sector = malloc(bd->sector_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) { |
| 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) { |
| ret = -EIO; |
| goto free_sector; |
| } |
| } |
| |
| if (fsync(bd->dev_fd)) { |
| ret = -EIO; |
| goto free_sector; |
| } |
| ret = 0; |
| |
| free_sector: |
| free(sector); |
| return ret; |
| } |
| |
| static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) |
| { |
| 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; |
| } |
| } |
| return ret; |
| } |
| |
| static void dentry_calc_checksum(struct exfat_dentry *dentry, |
| __le16 *checksum, bool primary) |
| { |
| unsigned int i; |
| uint8_t *bytes; |
| |
| bytes = (uint8_t *)dentry; |
| |
| *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[0]; |
| *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[1]; |
| |
| i = primary ? 4 : 2; |
| for (; i < sizeof(*dentry); i++) { |
| *checksum = ((*checksum << 15) | (*checksum >> 1)) + bytes[i]; |
| } |
| } |
| |
| static __le16 file_calc_checksum(struct exfat_de_iter *iter) |
| { |
| __le16 checksum; |
| struct exfat_dentry *file_de, *de; |
| int i; |
| |
| checksum = 0; |
| exfat_de_iter_get(iter, 0, &file_de); |
| |
| dentry_calc_checksum(file_de, &checksum, true); |
| for (i = 1; i <= file_de->file_num_ext; i++) { |
| exfat_de_iter_get(iter, i, &de); |
| dentry_calc_checksum(de, &checksum, false); |
| } |
| |
| return checksum; |
| } |
| |
| /* |
| * return 0 if there are no errors, or 1 if errors are fixed, or |
| * an error code |
| */ |
| static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node) |
| { |
| struct exfat *exfat = iter->exfat; |
| struct exfat_dentry *dentry; |
| int ret = 0; |
| uint16_t checksum; |
| bool valid = true; |
| |
| ret = check_clus_chain(exfat, node); |
| if (ret < 0) |
| return ret; |
| |
| if (node->size > le32_to_cpu(exfat->bs->bsx.clu_count) * |
| (uint64_t)exfat->clus_size) { |
| fsck_err(iter->parent, node, |
| "size %" PRIu64 " is greater than cluster heap\n", |
| node->size); |
| valid = false; |
| } |
| |
| if (node->size == 0 && node->is_contiguous) { |
| if (repair_file_ask(iter, node, ER_FILE_ZERO_NOFAT, |
| "empty, but has no Fat chain\n")) { |
| exfat_de_iter_get_dirty(iter, 1, &dentry); |
| dentry->stream_flags &= ~EXFAT_SF_CONTIGUOUS; |
| ret = 1; |
| } else |
| valid = false; |
| } |
| |
| if ((node->attr & ATTR_SUBDIR) && |
| node->size % exfat->clus_size != 0) { |
| fsck_err(iter->parent, node, |
| "directory size %" PRIu64 " is not divisible by %d\n", |
| node->size, exfat->clus_size); |
| valid = false; |
| } |
| |
| checksum = file_calc_checksum(iter); |
| exfat_de_iter_get(iter, 0, &dentry); |
| if (checksum != le16_to_cpu(dentry->file_checksum)) { |
| if (repair_file_ask(iter, node, ER_DE_CHECKSUM, |
| "the checksum of a file is wrong")) { |
| exfat_de_iter_get_dirty(iter, 0, &dentry); |
| dentry->file_checksum = cpu_to_le16(checksum); |
| ret = 1; |
| } else |
| valid = false; |
| } |
| |
| return valid ? ret : -EINVAL; |
| } |
| |
| static int read_file_dentries(struct exfat_de_iter *iter, |
| struct exfat_inode **new_node, int *skip_dentries) |
| { |
| struct exfat_dentry *file_de, *stream_de, *name_de; |
| struct exfat_inode *node; |
| int i, ret; |
| |
| /* TODO: mtime, atime, ... */ |
| |
| ret = exfat_de_iter_get(iter, 0, &file_de); |
| if (ret || file_de->type != EXFAT_FILE) { |
| exfat_err("failed to get file dentry. %d\n", ret); |
| return -EINVAL; |
| } |
| ret = exfat_de_iter_get(iter, 1, &stream_de); |
| if (ret || stream_de->type != EXFAT_STREAM) { |
| exfat_err("failed to get stream dentry. %d\n", ret); |
| return -EINVAL; |
| } |
| |
| *new_node = NULL; |
| node = alloc_exfat_inode(le16_to_cpu(file_de->file_attr)); |
| if (!node) |
| return -ENOMEM; |
| |
| if (file_de->file_num_ext < 2) { |
| exfat_err("too few secondary count. %d\n", |
| file_de->file_num_ext); |
| free_exfat_inode(node); |
| return -EINVAL; |
| } |
| |
| for (i = 2; i <= file_de->file_num_ext; i++) { |
| ret = exfat_de_iter_get(iter, i, &name_de); |
| if (ret || name_de->type != EXFAT_NAME) { |
| exfat_err("failed to get name dentry. %d\n", ret); |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| memcpy(node->name + |
| (i-2) * ENTRY_NAME_MAX, name_de->name_unicode, |
| sizeof(name_de->name_unicode)); |
| } |
| |
| node->first_clus = le32_to_cpu(stream_de->stream_start_clu); |
| node->is_contiguous = |
| ((stream_de->stream_flags & EXFAT_SF_CONTIGUOUS) != 0); |
| node->size = le64_to_cpu(stream_de->stream_size); |
| |
| if (node->size < le64_to_cpu(stream_de->stream_valid_size)) { |
| if (repair_file_ask(iter, node, ER_FILE_VALID_SIZE, |
| "valid size %" PRIu64 " greater than size %" PRIu64, |
| le64_to_cpu(stream_de->stream_valid_size), |
| node->size)) { |
| exfat_de_iter_get_dirty(iter, 1, &stream_de); |
| stream_de->stream_valid_size = |
| stream_de->stream_size; |
| } else { |
| ret = -EINVAL; |
| goto err; |
| } |
| } |
| |
| *skip_dentries = (file_de->file_num_ext + 1); |
| *new_node = node; |
| return 0; |
| err: |
| *skip_dentries = 0; |
| *new_node = NULL; |
| free_exfat_inode(node); |
| return ret; |
| } |
| |
| static int read_file(struct exfat_de_iter *de_iter, |
| struct exfat_inode **new_node, int *dentry_count) |
| { |
| struct exfat_inode *node; |
| int ret; |
| |
| *new_node = NULL; |
| |
| ret = read_file_dentries(de_iter, &node, dentry_count); |
| if (ret) |
| return ret; |
| |
| ret = check_inode(de_iter, node); |
| if (ret < 0) { |
| free_exfat_inode(node); |
| return -EINVAL; |
| } |
| |
| *new_node = node; |
| return ret; |
| } |
| |
| static bool read_volume_label(struct exfat_de_iter *iter) |
| { |
| struct exfat *exfat; |
| struct exfat_dentry *dentry; |
| __le16 disk_label[VOLUME_LABEL_MAX_LEN]; |
| |
| exfat = iter->exfat; |
| if (exfat_de_iter_get(iter, 0, &dentry)) |
| return false; |
| |
| if (dentry->vol_char_cnt == 0) |
| return true; |
| |
| if (dentry->vol_char_cnt > VOLUME_LABEL_MAX_LEN) { |
| exfat_err("too long label. %d\n", dentry->vol_char_cnt); |
| return false; |
| } |
| |
| memcpy(disk_label, dentry->vol_label, sizeof(disk_label)); |
| if (exfat_utf16_dec(disk_label, dentry->vol_char_cnt*2, |
| exfat->volume_label, sizeof(exfat->volume_label)) < 0) { |
| exfat_err("failed to decode volume label\n"); |
| return false; |
| } |
| |
| exfat_info("volume label [%s]\n", exfat->volume_label); |
| return true; |
| } |
| |
| static void exfat_bitmap_set_range(struct exfat *exfat, |
| clus_t start_clus, clus_t count) |
| { |
| clus_t clus; |
| |
| if (!heap_clus(exfat, start_clus) || |
| !heap_clus(exfat, start_clus + count)) |
| return; |
| |
| clus = start_clus; |
| while (clus < start_clus + count) { |
| EXFAT_BITMAP_SET(exfat->alloc_bitmap, |
| clus - EXFAT_FIRST_CLUSTER); |
| clus++; |
| } |
| } |
| |
| static bool read_bitmap(struct exfat_de_iter *iter) |
| { |
| struct exfat_dentry *dentry; |
| struct exfat *exfat; |
| |
| exfat = iter->exfat; |
| if (exfat_de_iter_get(iter, 0, &dentry)) |
| return false; |
| |
| exfat_debug("start cluster %#x, size %#" PRIx64 "\n", |
| le32_to_cpu(dentry->bitmap_start_clu), |
| le64_to_cpu(dentry->bitmap_size)); |
| |
| if (le64_to_cpu(dentry->bitmap_size) < |
| DIV_ROUND_UP(exfat->clus_count, 8)) { |
| exfat_err("invalid size of allocation bitmap. 0x%" PRIx64 "\n", |
| le64_to_cpu(dentry->bitmap_size)); |
| return false; |
| } |
| if (!heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) { |
| exfat_err("invalid start cluster of allocate bitmap. 0x%x\n", |
| le32_to_cpu(dentry->bitmap_start_clu)); |
| return false; |
| } |
| |
| exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu); |
| exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8); |
| |
| exfat_bitmap_set_range(exfat, le64_to_cpu(dentry->bitmap_start_clu), |
| DIV_ROUND_UP(exfat->disk_bitmap_size, |
| exfat->clus_size)); |
| |
| if (exfat_read(exfat->blk_dev->dev_fd, exfat->disk_bitmap, |
| exfat->disk_bitmap_size, |
| exfat_c2o(exfat, exfat->disk_bitmap_clus)) != |
| (ssize_t)exfat->disk_bitmap_size) |
| return false; |
| |
| return true; |
| } |
| |
| static bool read_upcase_table(struct exfat_de_iter *iter) |
| { |
| struct exfat_dentry *dentry; |
| struct exfat *exfat; |
| ssize_t size; |
| __le16 *upcase; |
| __le32 checksum; |
| |
| exfat = iter->exfat; |
| |
| if (exfat_de_iter_get(iter, 0, &dentry)) |
| return false; |
| |
| if (!heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) { |
| exfat_err("invalid start cluster of upcase table. 0x%x\n", |
| le32_to_cpu(dentry->upcase_start_clu)); |
| return false; |
| } |
| |
| size = (ssize_t)le64_to_cpu(dentry->upcase_size); |
| if (size > (ssize_t)(EXFAT_MAX_UPCASE_CHARS * sizeof(__le16)) || |
| size == 0 || size % sizeof(__le16)) { |
| exfat_err("invalid size of upcase table. 0x%" PRIx64 "\n", |
| le64_to_cpu(dentry->upcase_size)); |
| return false; |
| } |
| |
| upcase = (__le16 *)malloc(size); |
| if (!upcase) { |
| exfat_err("failed to allocate upcase table\n"); |
| return false; |
| } |
| |
| if (exfat_read(exfat->blk_dev->dev_fd, upcase, size, |
| exfat_c2o(exfat, |
| le32_to_cpu(dentry->upcase_start_clu))) != size) { |
| exfat_err("failed to read upcase table\n"); |
| free(upcase); |
| return false; |
| } |
| |
| checksum = 0; |
| boot_calc_checksum((unsigned char *)upcase, size, false, &checksum); |
| if (le32_to_cpu(dentry->upcase_checksum) != checksum) { |
| exfat_err("corrupted upcase table %#x (expected: %#x)\n", |
| checksum, le32_to_cpu(dentry->upcase_checksum)); |
| free(upcase); |
| return false; |
| } |
| |
| exfat_bitmap_set_range(exfat, le32_to_cpu(dentry->upcase_start_clu), |
| DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size), |
| exfat->clus_size)); |
| |
| free(upcase); |
| return true; |
| } |
| |
| static int read_children(struct exfat *exfat, struct exfat_inode *dir) |
| { |
| int ret; |
| struct exfat_inode *node = NULL; |
| struct exfat_dentry *dentry; |
| int dentry_count; |
| struct exfat_de_iter *de_iter; |
| |
| de_iter = &exfat->de_iter; |
| ret = exfat_de_iter_init(de_iter, exfat, dir); |
| if (ret == EOF) |
| return 0; |
| else if (ret) |
| return ret; |
| |
| while (1) { |
| ret = exfat_de_iter_get(de_iter, 0, &dentry); |
| if (ret == EOF) { |
| break; |
| } else if (ret) { |
| fsck_err(dir->parent, dir, |
| "failed to get a dentry. %d\n", ret); |
| goto err; |
| } |
| |
| dentry_count = 1; |
| |
| switch (dentry->type) { |
| case EXFAT_FILE: |
| ret = read_file(de_iter, &node, &dentry_count); |
| if (ret < 0) { |
| exfat_stat.error_count++; |
| goto err; |
| } else if (ret) { |
| exfat_stat.error_count++; |
| exfat_stat.fixed_count++; |
| } |
| |
| if ((node->attr & ATTR_SUBDIR) && node->size) { |
| node->parent = dir; |
| list_add_tail(&node->sibling, &dir->children); |
| list_add_tail(&node->list, &exfat->dir_list); |
| } else |
| free_exfat_inode(node); |
| break; |
| case EXFAT_VOLUME: |
| if (!read_volume_label(de_iter)) { |
| exfat_err("failed to verify volume label\n"); |
| ret = -EINVAL; |
| goto err; |
| } |
| break; |
| case EXFAT_BITMAP: |
| if (!read_bitmap(de_iter)) { |
| exfat_err( |
| "failed to verify allocation bitmap\n"); |
| ret = -EINVAL; |
| goto err; |
| } |
| break; |
| case EXFAT_UPCASE: |
| if (!read_upcase_table(de_iter)) { |
| exfat_err( |
| "failed to verify upcase table\n"); |
| ret = -EINVAL; |
| goto err; |
| } |
| break; |
| case EXFAT_LAST: |
| goto out; |
| default: |
| if (IS_EXFAT_DELETED(dentry->type)) |
| break; |
| exfat_err("unknown entry type. 0x%x\n", dentry->type); |
| ret = -EINVAL; |
| goto err; |
| } |
| |
| exfat_de_iter_advance(de_iter, dentry_count); |
| } |
| out: |
| exfat_de_iter_flush(de_iter); |
| return 0; |
| err: |
| inode_free_children(dir, false); |
| INIT_LIST_HEAD(&dir->children); |
| exfat_de_iter_flush(de_iter); |
| return ret; |
| } |
| |
| static int write_dirty_fat(struct exfat *exfat) |
| { |
| struct buffer_desc *bd; |
| off_t offset; |
| ssize_t len; |
| size_t read_size, write_size; |
| clus_t clus, last_clus, clus_count, i; |
| unsigned int idx; |
| |
| clus = 0; |
| last_clus = le32_to_cpu(exfat->bs->bsx.clu_count) + 2; |
| bd = exfat->buffer_desc; |
| idx = 0; |
| offset = le32_to_cpu(exfat->bs->bsx.fat_offset) * |
| exfat->sect_size; |
| read_size = exfat->clus_size; |
| write_size = exfat->sect_size; |
| |
| while (clus < last_clus) { |
| clus_count = MIN(read_size / sizeof(clus_t), last_clus - clus); |
| len = exfat_read(exfat->blk_dev->dev_fd, bd[idx].buffer, |
| clus_count * sizeof(clus_t), offset); |
| if (len != (ssize_t)(sizeof(clus_t) * clus_count)) { |
| exfat_err("failed to read fat entries, %zd\n", len); |
| return -EIO; |
| } |
| |
| /* TODO: read ahead */ |
| |
| for (i = clus ? clus : EXFAT_FIRST_CLUSTER; |
| i < clus + clus_count; i++) { |
| if (!EXFAT_BITMAP_GET(exfat->alloc_bitmap, |
| i - EXFAT_FIRST_CLUSTER) && |
| ((clus_t *)bd[idx].buffer)[i - clus] != |
| EXFAT_FREE_CLUSTER) { |
| ((clus_t *)bd[idx].buffer)[i - clus] = |
| EXFAT_FREE_CLUSTER; |
| bd[idx].dirty[(i - clus) / |
| (write_size / sizeof(clus_t))] = true; |
| } |
| } |
| |
| for (i = 0; i < read_size; i += write_size) { |
| if (bd[idx].dirty[i / write_size]) { |
| if (exfat_write(exfat->blk_dev->dev_fd, |
| &bd[idx].buffer[i], write_size, |
| offset + i) != |
| (ssize_t)write_size) { |
| exfat_err("failed to write " |
| "fat entries\n"); |
| return -EIO; |
| |
| } |
| bd[idx].dirty[i / write_size] = false; |
| } |
| } |
| |
| idx ^= 0x01; |
| clus = clus + clus_count; |
| offset += len; |
| } |
| return 0; |
| } |
| |
| static int write_dirty_bitmap(struct exfat *exfat) |
| { |
| struct buffer_desc *bd; |
| off_t offset, last_offset, bitmap_offset; |
| ssize_t len; |
| ssize_t read_size, write_size, i, size; |
| int idx; |
| |
| offset = exfat_c2o(exfat, exfat->disk_bitmap_clus); |
| last_offset = offset + exfat->disk_bitmap_size; |
| bitmap_offset = 0; |
| read_size = exfat->clus_size; |
| write_size = exfat->sect_size; |
| |
| bd = exfat->buffer_desc; |
| idx = 0; |
| |
| while (offset < last_offset) { |
| len = MIN(read_size, last_offset - offset); |
| if (exfat_read(exfat->blk_dev->dev_fd, bd[idx].buffer, |
| len, offset) != (ssize_t)len) |
| return -EIO; |
| |
| /* TODO: read-ahead */ |
| |
| for (i = 0; i < len; i += write_size) { |
| size = MIN(write_size, len - i); |
| if (memcmp(&bd[idx].buffer[i], |
| exfat->alloc_bitmap + bitmap_offset + i, |
| size)) { |
| if (exfat_write(exfat->blk_dev->dev_fd, |
| exfat->alloc_bitmap + bitmap_offset + i, |
| size, offset + i) != size) |
| return -EIO; |
| } |
| } |
| |
| idx ^= 0x01; |
| offset += len; |
| bitmap_offset += len; |
| } |
| return 0; |
| } |
| |
| static int reclaim_free_clusters(struct exfat *exfat) |
| { |
| if (write_dirty_fat(exfat)) { |
| exfat_err("failed to write fat entries\n"); |
| return -EIO; |
| } |
| if (write_dirty_bitmap(exfat)) { |
| exfat_err("failed to write bitmap\n"); |
| return -EIO; |
| } |
| return 0; |
| } |
| |
| /* |
| * for each directory in @dir_list. |
| * 1. read all dentries and allocate exfat_nodes for files and directories. |
| * and append directory exfat_nodes to the head of @dir_list |
| * 2. free all of file exfat_nodes. |
| * 3. if the directory does not have children, free its exfat_node. |
| */ |
| static int exfat_filesystem_check(struct exfat *exfat) |
| { |
| struct exfat_inode *dir; |
| int ret = 0, dir_errors; |
| |
| if (!exfat->root) { |
| exfat_err("root is NULL\n"); |
| return -ENOENT; |
| } |
| |
| list_add(&exfat->root->list, &exfat->dir_list); |
| |
| while (!list_empty(&exfat->dir_list)) { |
| dir = list_entry(exfat->dir_list.next, struct exfat_inode, list); |
| |
| if (!(dir->attr & ATTR_SUBDIR)) { |
| fsck_err(dir->parent, dir, |
| "failed to travel directories. " |
| "the node is not directory\n"); |
| ret = -EINVAL; |
| goto out; |
| } |
| |
| dir_errors = read_children(exfat, dir); |
| if (dir_errors) { |
| resolve_path(&path_resolve_ctx, dir); |
| exfat_debug("failed to check dentries: %s\n", |
| path_resolve_ctx.local_path); |
| ret = dir_errors; |
| } |
| |
| list_del(&dir->list); |
| inode_free_file_children(dir); |
| inode_free_ancestors(dir); |
| } |
| out: |
| exfat_free_dir_list(exfat); |
| exfat->root = NULL; |
| if (exfat->dirty_fat && reclaim_free_clusters(exfat)) |
| return -EIO; |
| return ret; |
| } |
| |
| static int exfat_root_dir_check(struct exfat *exfat) |
| { |
| struct exfat_inode *root; |
| clus_t clus_count; |
| |
| root = alloc_exfat_inode(ATTR_SUBDIR); |
| if (!root) { |
| exfat_err("failed to allocate memory\n"); |
| return -ENOMEM; |
| } |
| |
| root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster); |
| if (!root_get_clus_count(exfat, root, &clus_count)) { |
| exfat_err("failed to follow the cluster chain of root\n"); |
| free_exfat_inode(root); |
| return -EINVAL; |
| } |
| root->size = clus_count * exfat->clus_size; |
| |
| exfat->root = root; |
| exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n", |
| root->first_clus, root->size); |
| return 0; |
| } |
| |
| static char *bytes_to_human_readable(size_t bytes) |
| { |
| static const char * const units[] = {"B", "KB", "MB", "GB", "TB", "PB"}; |
| static char buf[15*4]; |
| unsigned int i, shift, quoti, remain; |
| |
| shift = 0; |
| for (i = 0; i < sizeof(units)/sizeof(units[0]); i++) { |
| if (bytes / (1ULL << (shift + 10)) == 0) |
| break; |
| shift += 10; |
| } |
| |
| quoti = (unsigned int)(bytes / (1ULL << shift)); |
| remain = 0; |
| if (shift > 0) { |
| remain = (unsigned int) |
| ((bytes & ((1ULL << shift) - 1)) >> (shift - 10)); |
| remain = (remain * 100) / 1024; |
| } |
| |
| snprintf(buf, sizeof(buf), "%u.%02u %s", quoti, remain, units[i]); |
| return buf; |
| } |
| |
| static void exfat_show_info(struct exfat *exfat, const char *dev_name, |
| int errors) |
| { |
| exfat_info("sector size: %s\n", |
| bytes_to_human_readable(1 << exfat->bs->bsx.sect_size_bits)); |
| exfat_info("cluster size: %s\n", |
| bytes_to_human_readable(exfat->clus_size)); |
| exfat_info("volume size: %s\n", |
| bytes_to_human_readable(exfat->blk_dev->size)); |
| |
| printf("%s: %s. directories %ld, files %ld\n", dev_name, |
| errors ? "checking stopped" : "clean", |
| exfat_stat.dir_count, exfat_stat.file_count); |
| if (errors || exfat->dirty) |
| printf("%s: files corrupted %ld, files fixed %ld\n", dev_name, |
| exfat_stat.error_count, exfat_stat.fixed_count); |
| } |
| |
| int main(int argc, char * const argv[]) |
| { |
| struct fsck_user_input ui; |
| struct exfat_blk_dev bd; |
| struct exfat *exfat = NULL; |
| struct pbr *bs = NULL; |
| int c, ret, exit_code; |
| bool version_only = false; |
| |
| memset(&ui, 0, sizeof(ui)); |
| memset(&bd, 0, sizeof(bd)); |
| |
| print_level = EXFAT_ERROR; |
| |
| if (!setlocale(LC_CTYPE, "")) |
| exfat_err("failed to init locale/codeset\n"); |
| |
| opterr = 0; |
| while ((c = getopt_long(argc, argv, "arynpVvh", opts, NULL)) != EOF) { |
| switch (c) { |
| case 'n': |
| if (ui.options & FSCK_OPTS_REPAIR_ALL) |
| usage(argv[0]); |
| ui.options |= FSCK_OPTS_REPAIR_NO; |
| break; |
| case 'r': |
| if (ui.options & FSCK_OPTS_REPAIR_ALL) |
| usage(argv[0]); |
| ui.options |= FSCK_OPTS_REPAIR_ASK; |
| break; |
| case 'y': |
| if (ui.options & FSCK_OPTS_REPAIR_ALL) |
| usage(argv[0]); |
| ui.options |= FSCK_OPTS_REPAIR_YES; |
| break; |
| case 'a': |
| case 'p': |
| if (ui.options & FSCK_OPTS_REPAIR_ALL) |
| usage(argv[0]); |
| ui.options |= FSCK_OPTS_REPAIR_AUTO; |
| break; |
| case 'V': |
| version_only = true; |
| break; |
| case 'v': |
| if (print_level < EXFAT_DEBUG) |
| print_level++; |
| break; |
| case '?': |
| case 'h': |
| default: |
| usage(argv[0]); |
| } |
| } |
| |
| show_version(); |
| if (optind != argc - 1) |
| usage(argv[0]); |
| |
| if (version_only) |
| exit(FSCK_EXIT_SYNTAX_ERROR); |
| if (ui.options & FSCK_OPTS_REPAIR_WRITE) |
| ui.ei.writeable = true; |
| else { |
| ui.options |= FSCK_OPTS_REPAIR_NO; |
| ui.ei.writeable = false; |
| } |
| |
| snprintf(ui.ei.dev_name, sizeof(ui.ei.dev_name), "%s", argv[optind]); |
| ret = exfat_get_blk_dev_info(&ui.ei, &bd); |
| if (ret < 0) { |
| exfat_err("failed to open %s. %d\n", ui.ei.dev_name, ret); |
| return FSCK_EXIT_OPERATION_ERROR; |
| } |
| |
| exfat = (struct exfat *)calloc(1, sizeof(*exfat)); |
| if (!exfat) { |
| exfat_err("failed to allocate exfat\n"); |
| ret = -ENOMEM; |
| goto err; |
| } |
| exfat->blk_dev = &bd; |
| exfat->options = ui.options; |
| |
| ret = exfat_boot_region_check(exfat, &bs); |
| if (ret) |
| goto err; |
| |
| ret = init_exfat(exfat, bs); |
| if (ret) |
| goto err; |
| |
| if (exfat_mark_volume_dirty(exfat, true)) { |
| ret = -EIO; |
| goto err; |
| } |
| |
| exfat_debug("verifying root directory...\n"); |
| ret = exfat_root_dir_check(exfat); |
| if (ret) { |
| exfat_err("failed to verify root directory.\n"); |
| goto out; |
| } |
| |
| exfat_debug("verifying directory entries...\n"); |
| ret = exfat_filesystem_check(exfat); |
| if (ret) |
| goto out; |
| |
| if (ui.ei.writeable && fsync(bd.dev_fd)) { |
| exfat_err("failed to sync\n"); |
| ret = -EIO; |
| goto out; |
| } |
| exfat_mark_volume_dirty(exfat, false); |
| |
| out: |
| exfat_show_info(exfat, ui.ei.dev_name, ret); |
| err: |
| if (ret == -EINVAL) |
| exit_code = FSCK_EXIT_ERRORS_LEFT; |
| else if (ret) |
| exit_code = FSCK_EXIT_OPERATION_ERROR; |
| else if (exfat->dirty) |
| exit_code = FSCK_EXIT_CORRECTED; |
| else |
| exit_code = FSCK_EXIT_NO_ERRORS; |
| |
| free_exfat(exfat); |
| close(bd.dev_fd); |
| return exit_code; |
| } |