From: Sven Höxter Date: Fri, 31 Jul 2020 08:09:09 +0000 (+0200) Subject: New upstream version 1.0.4 X-Git-Tag: upstream/1.0.4^0 X-Git-Url: https://git.sven.stormbind.net/?p=sven%2Fexfatprogs.git;a=commitdiff_plain;h=ca9b0f7353eba0cfbee98236d3487bbb6a8ec26f New upstream version 1.0.4 --- diff --git a/Makefile.in b/Makefile.in index 2ad6099..78cd1e6 100644 --- a/Makefile.in +++ b/Makefile.in @@ -198,7 +198,7 @@ am__DIST_COMMON = $(dist_man8_MANS) $(srcdir)/Makefile.in \ $(top_srcdir)/build-aux/install-sh \ $(top_srcdir)/build-aux/ltmain.sh \ $(top_srcdir)/build-aux/missing COPYING NEWS build-aux/compile \ - build-aux/config.guess build-aux/config.sub build-aux/depcomp \ + build-aux/config.guess build-aux/config.sub \ build-aux/install-sh build-aux/ltmain.sh build-aux/missing DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) distdir = $(PACKAGE)-$(VERSION) diff --git a/NEWS b/NEWS index 064ba7b..dd48c5f 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,21 @@ +exfatprogs 1.0.4 - released 2020-07-31 +====================================== + +CHANGES : + * fsck.exfat: display sector, cluster, and volume sizes in the human + readable format. + * fsck.exfat: reduce the elapsed time using read-ahead. + +NEW FEATURES : + * mkfs.exfat: generate pseudo unique serials while creating filesystems. + * mkfs.exfat: add the "-b" option to align the start offset of FAT and + data clusters. + * fsck.exfat: repair zero-byte files which have the NoFatChain attribute. + +BUG FIXES : + * Fix memory leaks on error handling paths. + * fsck.exfat: fix the bug that cannot access space beyond 2TB. + exfatprogs 1.0.3 - released 2020-05-12 ====================================== diff --git a/README.md b/README.md index 48861d9..af6a32c 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,10 @@ Usage example: mkfs.exfat -f /dev/sda1 4. For set volume label, use -l option with string user want. mkfs.exfat -L "my usb" /dev/sda1 + 5. To change boundary alignment(KB or MB or Byte) user want + mkfs.exfat -b 16777216 /dev/sda1 + mkfs.exfat -b 16384K /dev/sda1 + mkfs.exfat -b 16M /dev/sda1 - fsck.exfat: Check the consistency of your exfat filesystem and optionally repair a corrupted device formatted by exfat. @@ -61,6 +65,23 @@ Usage example: tune.exfat -L "new label" /dev/sda1 ``` +## Benchmarks + +Some fsck implementations were tested and compared for Samsung 64GB Pro +microSDXC UHS-I Class 10 which was filled up to 35GB with 9948 directories +and 16506 files by fsstress. + +The difference in the execution time for each testing is very small. + + +| Implementation | version | execution time (seconds) | +|----------------------|-----------------|--------------------------| +| **exfatprogs fsck** | 1.0.4 | 11.561 | +| Windows fsck | Windows 10 1809 | 11.449 | +| [exfat-fuse fsck] | 1.3.0 | 68.977 | + +[exfat-fuse fsck]: https://github.com/relan/exfat + ## Sending feedback If you have any issues, please create [issues][1] or contact to [Namjae Jeon](mailto:linkinjeon@kernel.org) and [Hyunchul Lee](mailto:hyc.lee@gmail.com). @@ -68,3 +89,7 @@ If you have any issues, please create [issues][1] or contact to [Namjae Jeon](ma [1]: https://github.com/exfatprogs/exfatprogs/issues [2]: https://github.com/exfatprogs/exfatprogs/pulls + +## Contributor information +* Please base your pull requests on the `exfat-next` branch. +* Make sure you add 'Signed-Off' information to your commits (e. g. `git commit --signoff`). diff --git a/configure b/configure index 6cea5d3..68a3273 100755 --- a/configure +++ b/configure @@ -1,6 +1,6 @@ #! /bin/sh # Guess values for system-dependent variables and create Makefiles. -# Generated by GNU Autoconf 2.69 for exfatprogs 1.0.3. +# Generated by GNU Autoconf 2.69 for exfatprogs 1.0.4. # # Report bugs to . # @@ -590,8 +590,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='exfatprogs' PACKAGE_TARNAME='exfatprogs' -PACKAGE_VERSION='1.0.3' -PACKAGE_STRING='exfatprogs 1.0.3' +PACKAGE_VERSION='1.0.4' +PACKAGE_STRING='exfatprogs 1.0.4' PACKAGE_BUGREPORT='linkinjeon@kernel.org' PACKAGE_URL='https://github.com/exfatprogs/exfatprogs' @@ -1325,7 +1325,7 @@ if test "$ac_init_help" = "long"; then # Omit some internal or obsolete options to make the list less imposing. # This message is too long to be a string in the A/UX 3.1 sh. cat <<_ACEOF -\`configure' configures exfatprogs 1.0.3 to adapt to many kinds of systems. +\`configure' configures exfatprogs 1.0.4 to adapt to many kinds of systems. Usage: $0 [OPTION]... [VAR=VALUE]... @@ -1396,7 +1396,7 @@ fi if test -n "$ac_init_help"; then case $ac_init_help in - short | recursive ) echo "Configuration of exfatprogs 1.0.3:";; + short | recursive ) echo "Configuration of exfatprogs 1.0.4:";; esac cat <<\_ACEOF @@ -1508,7 +1508,7 @@ fi test -n "$ac_init_help" && exit $ac_status if $ac_init_version; then cat <<\_ACEOF -exfatprogs configure 1.0.3 +exfatprogs configure 1.0.4 generated by GNU Autoconf 2.69 Copyright (C) 2012 Free Software Foundation, Inc. @@ -1786,7 +1786,7 @@ cat >config.log <<_ACEOF This file contains any messages produced by compilers while running configure, to aid debugging if configure makes a mistake. -It was created by exfatprogs $as_me 1.0.3, which was +It was created by exfatprogs $as_me 1.0.4, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2654,7 +2654,7 @@ fi # Define the identity of the package. PACKAGE='exfatprogs' - VERSION='1.0.3' + VERSION='1.0.4' cat >>confdefs.h <<_ACEOF @@ -13253,7 +13253,7 @@ cat >>$CONFIG_STATUS <<\_ACEOF || ac_write_fail=1 # report actual input values of CONFIG_FILES etc. instead of their # values after options handling. ac_log=" -This file was extended by exfatprogs $as_me 1.0.3, which was +This file was extended by exfatprogs $as_me 1.0.4, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -13320,7 +13320,7 @@ _ACEOF cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 ac_cs_config="`$as_echo "$ac_configure_args" | sed 's/^ //; s/[\\""\`\$]/\\\\&/g'`" ac_cs_version="\\ -exfatprogs config.status 1.0.3 +exfatprogs config.status 1.0.4 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" diff --git a/fsck/Android.bp b/fsck/Android.bp index 7473fea..f267ad4 100644 --- a/fsck/Android.bp +++ b/fsck/Android.bp @@ -4,6 +4,7 @@ cc_binary { name: "fsck.exfat", srcs: [ + "de_iter.c", "fsck.c", "repair.c", ], diff --git a/fsck/Makefile.am b/fsck/Makefile.am index 31b0b70..57a0ede 100644 --- a/fsck/Makefile.am +++ b/fsck/Makefile.am @@ -3,4 +3,4 @@ fsck_exfat_LDADD = $(top_builddir)/lib/libexfat.a sbin_PROGRAMS = fsck.exfat -fsck_exfat_SOURCES = fsck.c repair.c fsck.h repair.h +fsck_exfat_SOURCES = fsck.c repair.c fsck.h de_iter.c repair.h diff --git a/fsck/Makefile.in b/fsck/Makefile.in index a73337f..c339e11 100644 --- a/fsck/Makefile.in +++ b/fsck/Makefile.in @@ -104,7 +104,8 @@ CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(sbindir)" PROGRAMS = $(sbin_PROGRAMS) -am_fsck_exfat_OBJECTS = fsck.$(OBJEXT) repair.$(OBJEXT) +am_fsck_exfat_OBJECTS = fsck.$(OBJEXT) repair.$(OBJEXT) \ + de_iter.$(OBJEXT) fsck_exfat_OBJECTS = $(am_fsck_exfat_OBJECTS) fsck_exfat_DEPENDENCIES = $(top_builddir)/lib/libexfat.a AM_V_lt = $(am__v_lt_@AM_V@) @@ -292,7 +293,7 @@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common fsck_exfat_LDADD = $(top_builddir)/lib/libexfat.a -fsck_exfat_SOURCES = fsck.c repair.c fsck.h repair.h +fsck_exfat_SOURCES = fsck.c repair.c fsck.h de_iter.c repair.h all: all-am .SUFFIXES: @@ -386,6 +387,7 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/de_iter.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fsck.Po@am__quote@ @AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/repair.Po@am__quote@ diff --git a/fsck/de_iter.c b/fsck/de_iter.c new file mode 100644 index 0000000..bc95c49 --- /dev/null +++ b/fsck/de_iter.c @@ -0,0 +1,313 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2020 Hyunchul Lee + */ +#include +#include +#include +#include + +#include "exfat_ondisk.h" +#include "libexfat.h" +#include "fsck.h" + +static ssize_t write_block(struct exfat_de_iter *iter, unsigned int block) +{ + off_t device_offset; + struct exfat *exfat = iter->exfat; + struct buffer_desc *desc; + unsigned int i; + + desc = &iter->buffer_desc[block & 0x01]; + device_offset = exfat_c2o(exfat, desc->p_clus) + desc->offset; + + for (i = 0; i < iter->read_size / iter->write_size; i++) { + if (desc->dirty[i]) { + if (exfat_write(exfat->blk_dev->dev_fd, + desc->buffer + i * iter->write_size, + iter->write_size, + device_offset + i * iter->write_size) + != (ssize_t)iter->write_size) + return -EIO; + desc->dirty[i] = 0; + } + } + return 0; +} + +static int read_ahead_first_blocks(struct exfat_de_iter *iter) +{ +#ifdef POSIX_FADV_WILLNEED + struct exfat *exfat = iter->exfat; + clus_t clus_count; + unsigned int size; + + clus_count = iter->parent->size / exfat->clus_size; + + if (clus_count > 1) { + iter->ra_begin_offset = 0; + iter->ra_next_clus = 1; + size = exfat->clus_size; + } else { + iter->ra_begin_offset = 0; + iter->ra_next_clus = 0; + size = iter->ra_partial_size; + } + return posix_fadvise(exfat->blk_dev->dev_fd, + exfat_c2o(exfat, iter->parent->first_clus), size, + POSIX_FADV_WILLNEED); +#else + return -ENOTSUP; +#endif +} + +/** + * read the next fragment in advance, and assume the fragment + * which covers @clus is already read. + */ +static int read_ahead_next_blocks(struct exfat_de_iter *iter, + clus_t clus, unsigned int offset, clus_t p_clus) +{ +#ifdef POSIX_FADV_WILLNEED + struct exfat *exfat = iter->exfat; + off_t device_offset; + clus_t clus_count, ra_clus, ra_p_clus; + unsigned int size; + int ret = 0; + + clus_count = iter->parent->size / exfat->clus_size; + if (clus + 1 < clus_count) { + ra_clus = clus + 1; + if (ra_clus == iter->ra_next_clus && + offset >= iter->ra_begin_offset) { + ret = get_next_clus(exfat, iter->parent, + p_clus, &ra_p_clus); + if (ra_p_clus == EXFAT_EOF_CLUSTER) + return -EIO; + + device_offset = exfat_c2o(exfat, ra_p_clus); + size = ra_clus + 1 < clus_count ? + exfat->clus_size : iter->ra_partial_size; + ret = posix_fadvise(exfat->blk_dev->dev_fd, + device_offset, size, + POSIX_FADV_WILLNEED); + iter->ra_next_clus = ra_clus + 1; + iter->ra_begin_offset = 0; + } + } else { + if (offset >= iter->ra_begin_offset && + offset + iter->ra_partial_size <= + exfat->clus_size) { + device_offset = exfat_c2o(exfat, p_clus) + + offset + iter->ra_partial_size; + ret = posix_fadvise(exfat->blk_dev->dev_fd, + device_offset, iter->ra_partial_size, + POSIX_FADV_WILLNEED); + iter->ra_begin_offset = + offset + iter->ra_partial_size; + } + } + + return ret; +#else + return -ENOTSUP; +#endif +} + +static int read_ahead_next_dir_blocks(struct exfat_de_iter *iter) +{ +#ifdef POSIX_FADV_WILLNEED + struct exfat *exfat = iter->exfat; + struct list_head *current; + struct exfat_inode *next_inode; + off_t offset; + + if (list_empty(&exfat->dir_list)) + return -EINVAL; + + current = exfat->dir_list.next; + if (iter->parent == list_entry(current, struct exfat_inode, list) && + current->next != &exfat->dir_list) { + next_inode = list_entry(current->next, struct exfat_inode, + list); + offset = exfat_c2o(exfat, next_inode->first_clus); + return posix_fadvise(exfat->blk_dev->dev_fd, offset, + iter->ra_partial_size, + POSIX_FADV_WILLNEED); + } + + return 0; +#else + return -ENOTSUP; +#endif +} + +static ssize_t read_block(struct exfat_de_iter *iter, unsigned int block) +{ + struct exfat *exfat = iter->exfat; + struct buffer_desc *desc, *prev_desc; + off_t device_offset; + ssize_t ret; + + desc = &iter->buffer_desc[block & 0x01]; + if (block == 0) { + desc->p_clus = iter->parent->first_clus; + desc->offset = 0; + } + + /* if the buffer already contains dirty dentries, write it */ + if (write_block(iter, block)) + return -EIO; + + if (block > 0) { + if (block > iter->parent->size / iter->read_size) + return EOF; + + prev_desc = &iter->buffer_desc[(block-1) & 0x01]; + if (prev_desc->offset + 2 * iter->read_size <= + exfat->clus_size) { + desc->p_clus = prev_desc->p_clus; + desc->offset = prev_desc->offset + iter->read_size; + } else { + ret = get_next_clus(exfat, iter->parent, + prev_desc->p_clus, &desc->p_clus); + desc->offset = 0; + if (!ret && desc->p_clus == EXFAT_EOF_CLUSTER) + return EOF; + else if (ret) + return ret; + } + } + + device_offset = exfat_c2o(exfat, desc->p_clus) + desc->offset; + ret = exfat_read(exfat->blk_dev->dev_fd, desc->buffer, + iter->read_size, device_offset); + if (ret <= 0) + return ret; + + /* + * if a buffer is filled with dentries, read blocks ahead of time, + * otherwise read blocks of the next directory in advance. + */ + if (desc->buffer[iter->read_size - 32] != EXFAT_LAST) + read_ahead_next_blocks(iter, + (block * iter->read_size) / exfat->clus_size, + (block * iter->read_size) % exfat->clus_size, + desc->p_clus); + else + read_ahead_next_dir_blocks(iter); + return ret; +} + +int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, + struct exfat_inode *dir) +{ + iter->exfat = exfat; + iter->parent = dir; + iter->write_size = exfat->sect_size; + iter->read_size = exfat->clus_size <= 4*KB ? exfat->clus_size : 4*KB; + if (exfat->clus_size <= 32 * KB) + iter->ra_partial_size = MAX(4 * KB, exfat->clus_size / 2); + else + iter->ra_partial_size = exfat->clus_size / 4; + iter->ra_partial_size = MIN(iter->ra_partial_size, 8 * KB); + + if (!iter->buffer_desc) + iter->buffer_desc = exfat->buffer_desc; + + if (iter->parent->size == 0) + return EOF; + + read_ahead_first_blocks(iter); + if (read_block(iter, 0) != (ssize_t)iter->read_size) { + exfat_err("failed to read directory entries.\n"); + return -EIO; + } + + iter->de_file_offset = 0; + iter->next_read_offset = iter->read_size; + iter->max_skip_dentries = 0; + return 0; +} + +int exfat_de_iter_get(struct exfat_de_iter *iter, + int ith, struct exfat_dentry **dentry) +{ + off_t next_de_file_offset; + ssize_t ret; + unsigned int block; + + next_de_file_offset = iter->de_file_offset + + ith * sizeof(struct exfat_dentry); + block = (unsigned int)(next_de_file_offset / iter->read_size); + + if (next_de_file_offset + sizeof(struct exfat_dentry) > + iter->parent->size) + return EOF; + /* the dentry must be in current, or next block which will be read */ + if (block > iter->de_file_offset / iter->read_size + 1) + return -ERANGE; + + /* read next cluster if needed */ + if (next_de_file_offset >= iter->next_read_offset) { + ret = read_block(iter, block); + if (ret != (ssize_t)iter->read_size) + return ret; + iter->next_read_offset += iter->read_size; + } + + if (ith + 1 > iter->max_skip_dentries) + iter->max_skip_dentries = ith + 1; + + *dentry = (struct exfat_dentry *) + (iter->buffer_desc[block & 0x01].buffer + + next_de_file_offset % iter->read_size); + return 0; +} + +int exfat_de_iter_get_dirty(struct exfat_de_iter *iter, + int ith, struct exfat_dentry **dentry) +{ + off_t next_file_offset; + unsigned int block; + int ret, sect_idx; + + ret = exfat_de_iter_get(iter, ith, dentry); + if (!ret) { + next_file_offset = iter->de_file_offset + + ith * sizeof(struct exfat_dentry); + block = (unsigned int)(next_file_offset / iter->read_size); + sect_idx = (int)((next_file_offset % iter->read_size) / + iter->write_size); + iter->buffer_desc[block & 0x01].dirty[sect_idx] = 1; + } + + return ret; +} + +int exfat_de_iter_flush(struct exfat_de_iter *iter) +{ + if (write_block(iter, 0) || write_block(iter, 1)) + return -EIO; + return 0; +} + +/* + * @skip_dentries must be the largest @ith + 1 of exfat_de_iter_get + * since the last call of exfat_de_iter_advance + */ +int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries) +{ + if (skip_dentries != iter->max_skip_dentries) + return -EINVAL; + + iter->max_skip_dentries = 0; + iter->de_file_offset = iter->de_file_offset + + skip_dentries * sizeof(struct exfat_dentry); + return 0; +} + +off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter) +{ + return iter->de_file_offset; +} diff --git a/fsck/fsck.c b/fsck/fsck.c index 2a13f43..9732afb 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -38,9 +38,10 @@ typedef __u32 bitmap_t; #define EXFAT_BITMAP_SIZE(__c_count) \ (DIV_ROUND_UP(__c_count, BITS_PER) * sizeof(bitmap_t)) #define EXFAT_BITMAP_GET(__bmap, __c) \ - ((__bmap)[BIT_ENTRY(__c)] & BIT_MASK(__c)) + (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] & BIT_MASK(__c)) #define EXFAT_BITMAP_SET(__bmap, __c) \ - ((__bmap)[BIT_ENTRY(__c)] |= BIT_MASK(__c)) + (((bitmap_t *)(__bmap))[BIT_ENTRY(__c)] |= \ + BIT_MASK(__c)) #define FSCK_EXIT_NO_ERRORS 0x00 #define FSCK_EXIT_CORRECTED 0x01 @@ -54,8 +55,8 @@ typedef __u32 bitmap_t; struct exfat_stat { long dir_count; long file_count; - long dir_free_count; - long file_free_count; + long error_count; + long fixed_count; }; struct path_resolve_ctx { @@ -71,6 +72,7 @@ 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' }, @@ -84,6 +86,7 @@ static void usage(char *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-V | --version Show version\n"); fprintf(stderr, "\t-v | --verbose Print debug\n"); fprintf(stderr, "\t-h | --help Show help\n"); @@ -91,6 +94,15 @@ static void usage(char *name) 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; @@ -119,10 +131,6 @@ static struct exfat_inode *alloc_exfat_inode(__u16 attr) static void free_exfat_inode(struct exfat_inode *node) { - if (node->attr & ATTR_SUBDIR) - exfat_stat.dir_free_count++; - else - exfat_stat.file_free_count++; free(node); } @@ -175,341 +183,88 @@ static void inode_free_ancestors(struct exfat_inode *child) return; } -static struct exfat *alloc_exfat(struct exfat_blk_dev *bd) -{ - struct exfat *exfat; - - exfat = (struct exfat *)calloc(1, sizeof(*exfat)); - if (!exfat) { - exfat_err("failed to allocate exfat\n"); - return NULL; - } - - exfat->blk_dev = bd; - INIT_LIST_HEAD(&exfat->dir_list); - return exfat; -} - static void free_exfat(struct exfat *exfat) { + int i; + if (exfat) { if (exfat->bs) free(exfat->bs); - if (exfat->de_iter.dentries) - free(exfat->de_iter.dentries); if (exfat->alloc_bitmap) free(exfat->alloc_bitmap); - free(exfat); - } -} - -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); - } -} - -static inline bool exfat_invalid_clus(struct exfat *exfat, clus_t clus) -{ - return clus < EXFAT_FIRST_CLUSTER || - (clus - EXFAT_FIRST_CLUSTER) > le32_to_cpu(exfat->bs->bsx.clu_count); -} - -static int inode_get_clus_next(struct exfat *exfat, struct exfat_inode *node, - clus_t clus, clus_t *next) -{ - off_t offset; - - if (exfat_invalid_clus(exfat, clus)) - return -EINVAL; - - if (node->is_contiguous) { - *next = clus + 1; - return 0; - } - - offset = 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 bool inode_check_clus_chain(struct exfat *exfat, struct exfat_inode *node) -{ - clus_t clus; - clus_t clus_count; - - clus = node->first_clus; - clus_count = DIV_ROUND_UP(node->size, EXFAT_CLUSTER_SIZE(exfat->bs)); - - while (clus_count--) { - if (exfat_invalid_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 allocated, but not in bitmap. 0x%x\n", - clus); - return false; - } - - if (inode_get_clus_next(exfat, node, clus, &clus) != 0) { - exfat_err( - "broken cluster chain. (previous cluster 0x%x)\n", - clus); - return false; - } - } - return true; -} - -static bool inode_get_clus_count(struct exfat *exfat, struct exfat_inode *node, - clus_t *clus_count) -{ - clus_t clus; - - clus = node->first_clus; - *clus_count = 0; - - do { - if (exfat_invalid_clus(exfat, clus)) { - exfat_err("bad cluster. 0x%x\n", clus); - return false; - } - - if (inode_get_clus_next(exfat, node, clus, &clus) != 0) { - exfat_err( - "broken cluster chain. (previous cluster 0x%x)\n", - clus); - 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; -} - -static 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) + - ((clus - EXFAT_FIRST_CLUSTER) << - exfat->bs->bsx.sect_per_clus_bits)); -} - -static ssize_t exfat_file_read(struct exfat *exfat, struct exfat_inode *node, - void *buf, size_t total_size, off_t file_offset) -{ - size_t clus_size; - clus_t start_l_clus, l_clus, p_clus; - unsigned int clus_offset; - int ret; - off_t device_offset; - ssize_t read_size; - size_t remain_size; - - if (file_offset >= (off_t)node->size) - return EOF; - - clus_size = EXFAT_CLUSTER_SIZE(exfat->bs); - total_size = MIN(total_size, node->size - file_offset); - remain_size = total_size; - - if (remain_size == 0) - return 0; - - start_l_clus = file_offset / clus_size; - clus_offset = file_offset % clus_size; - if (start_l_clus >= node->last_lclus && - node->last_pclus != EXFAT_EOF_CLUSTER) { - l_clus = node->last_lclus; - p_clus = node->last_pclus; - } else { - l_clus = 0; - p_clus = node->first_clus; - } - - while (p_clus != EXFAT_EOF_CLUSTER) { - if (exfat_invalid_clus(exfat, p_clus)) - return -EINVAL; - if (l_clus < start_l_clus) - goto next_clus; - - read_size = MIN(remain_size, clus_size - clus_offset); - device_offset = exfat_c2o(exfat, p_clus) + clus_offset; - if (exfat_read(exfat->blk_dev->dev_fd, buf, read_size, - device_offset) != read_size) - return -EIO; - - clus_offset = 0; - buf = (char *)buf + read_size; - remain_size -= read_size; - if (remain_size == 0) - goto out; - -next_clus: - l_clus++; - ret = inode_get_clus_next(exfat, node, p_clus, &p_clus); - if (ret) - return ret; - } -out: - node->last_lclus = l_clus; - node->last_pclus = p_clus; - return total_size - remain_size; -} - -static int boot_region_checksum(struct exfat *exfat) -{ - __le32 checksum; - unsigned short size; - void *sect; - unsigned int i; - - size = EXFAT_SECTOR_SIZE(exfat->bs); - sect = malloc(size); - if (!sect) - return -ENOMEM; - - checksum = 0; - - boot_calc_checksum((unsigned char *)exfat->bs, size, true, &checksum); - for (i = 1; i < 11; i++) { - if (exfat_read(exfat->blk_dev->dev_fd, sect, size, i * size) != - (ssize_t)size) { - free(sect); - return -EIO; - } - boot_calc_checksum(sect, size, false, &checksum); - } - - if (exfat_read(exfat->blk_dev->dev_fd, sect, size, i * size) != - (ssize_t)size) { - free(sect); - return -EIO; - } - for (i = 0; i < size/sizeof(checksum); i++) { - if (le32_to_cpu(((__le32 *)sect)[i]) != checksum) { - union exfat_repair_context rctx = { - .bs_checksum.checksum = checksum, - .bs_checksum.checksum_sect = sect, - }; - if (!exfat_repair(exfat, ER_BS_CHECKSUM, &rctx)) { - exfat_err("invalid checksum. 0x%x\n", - le32_to_cpu(((__le32 *)sect)[i])); - free(sect); - return -EIO; - } + 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); } - - free(sect); - return 0; } -static bool exfat_boot_region_check(struct exfat *exfat) +static struct exfat *alloc_exfat(struct exfat_blk_dev *bd, struct pbr *bs) { - struct pbr *bs; - ssize_t ret; + struct exfat *exfat; + int i; - bs = (struct pbr *)malloc(sizeof(struct pbr)); - if (!bs) { - exfat_err("failed to allocate memory\n"); - return false; + exfat = (struct exfat *)calloc(1, sizeof(*exfat)); + if (!exfat) { + free(bs); + exfat_err("failed to allocate exfat\n"); + return NULL; } + INIT_LIST_HEAD(&exfat->dir_list); + exfat->blk_dev = bd; 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); - ret = exfat_read(exfat->blk_dev->dev_fd, bs, sizeof(*bs), 0); - if (ret != sizeof(*bs)) { - exfat_err("failed to read a boot sector. %zd\n", ret); - goto err; - } - - if (memcmp(bs->bpb.oem_name, "EXFAT ", 8) != 0) { - exfat_err("failed to find exfat file system.\n"); - goto err; - } - - 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; - } - - ret = boot_region_checksum(exfat); - if (ret) { - exfat_err("failed to verify the checksum of a boot region. %zd\n", - ret); - 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); + /* 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; } - if (le64_to_cpu(bs->bsx.vol_length) * EXFAT_SECTOR_SIZE(bs) > - exfat->blk_dev->size) { - exfat_err("too large sector count: %" PRIu64 "\n, expected: %llu\n", - le64_to_cpu(bs->bsx.vol_length), - exfat->blk_dev->num_sectors); + exfat->disk_bitmap = (char *)malloc( + EXFAT_BITMAP_SIZE(exfat->clus_count)); + if (!exfat->disk_bitmap) { + exfat_err("failed to allocate bitmap\n"); goto err; } - if (le32_to_cpu(bs->bsx.clu_count) * EXFAT_CLUSTER_SIZE(bs) > - exfat->blk_dev->size) { - exfat_err("too large cluster count: %u, expected: %u\n", - le32_to_cpu(bs->bsx.clu_count), - exfat->blk_dev->num_clusters); - 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 true; + return exfat; err: - free(bs); - exfat->bs = NULL; - return false; + free_exfat(exfat); + return NULL; } -static size_t utf16_len(const __le16 *str, size_t max_size) +static void exfat_free_dir_list(struct exfat *exfat) { - size_t i = 0; + struct exfat_inode *dir, *i; - while (le16_to_cpu(str[i]) && i < max_size) - i++; - return 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); + } } /* @@ -533,7 +288,7 @@ bool get_ancestors(struct exfat_inode *child, dir = child; while (dir) { - name_len = utf16_len(dir->name, NAME_BUFFER_SIZE); + name_len = exfat_utf16_len(dir->name, NAME_BUFFER_SIZE); if (char_len + name_len > max_char_len) break; @@ -558,7 +313,8 @@ static int resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child) int depth, i; int name_len; __le16 *utf16_path; - const __le16 utf16_slash = cpu_to_le16(0x002F); + 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'; @@ -571,7 +327,8 @@ static int resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child) utf16_path = ctx->utf16_path; for (i = 0; i < depth; i++) { - name_len = utf16_len(ctx->ancestors[i]->name, NAME_BUFFER_SIZE); + 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; @@ -581,6 +338,9 @@ static int resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child) 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)); @@ -600,157 +360,394 @@ static int resolve_path_parent(struct path_resolve_ctx *ctx, return ret; } -static int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, - struct exfat_inode *dir) -{ - ssize_t ret; - - if (!iter->dentries) { - iter->read_size = EXFAT_CLUSTER_SIZE(exfat->bs); - iter->dentries = malloc(iter->read_size * 2); - if (!iter->dentries) { - exfat_err("failed to allocate memory\n"); - return -ENOMEM; +#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; } - } - ret = exfat_file_read(exfat, dir, iter->dentries, iter->read_size, 0); - if (ret != iter->read_size) { - exfat_err("failed to read directory entries.\n"); - return -EIO; - } + (*clus_count)++; + } while (clus != EXFAT_EOF_CLUSTER); + return true; +} - iter->exfat = exfat; - iter->parent = dir; - iter->de_file_offset = 0; - iter->next_read_offset = iter->read_size; - iter->max_skip_dentries = 0; - return 0; +static off_t exfat_s2o(struct exfat *exfat, off_t sect) +{ + return sect << exfat->bs->bsx.sect_size_bits; } -static int exfat_de_iter_get(struct exfat_de_iter *iter, - int ith, struct exfat_dentry **dentry) +off_t exfat_c2o(struct exfat *exfat, unsigned int clus) { - off_t de_next_file_offset; - unsigned int de_next_offset; - bool need_read_1_clus = false; - int ret; + if (clus < EXFAT_FIRST_CLUSTER) + return ~0L; - de_next_file_offset = iter->de_file_offset + - ith * sizeof(struct exfat_dentry); + 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)); +} - if (de_next_file_offset + sizeof(struct exfat_dentry) > - round_down(iter->parent->size, sizeof(struct exfat_dentry))) - return EOF; +static int boot_region_checksum(struct exfat *exfat) +{ + void *sect; + unsigned int i; + uint32_t checksum; + int ret = 0; + unsigned short size; - /* - * dentry must be in current cluster, or next cluster which - * will be read - */ - if (de_next_file_offset - - (iter->de_file_offset / iter->read_size) * iter->read_size >= - iter->read_size * 2) - return -ERANGE; + size = EXFAT_SECTOR_SIZE(exfat->bs); + sect = malloc(size); + if (!sect) + return -ENOMEM; - de_next_offset = de_next_file_offset % (iter->read_size * 2); + checksum = 0; - /* read a cluster if needed */ - if (de_next_file_offset >= iter->next_read_offset) { - void *buf; + boot_calc_checksum((unsigned char *)exfat->bs, size, true, &checksum); + for (i = 1; i < 11; i++) { + if (exfat_read(exfat->blk_dev->dev_fd, sect, size, i * size) != + (ssize_t)size) { + exfat_err("failed to read boot regions\n"); + ret = -EIO; + goto out; + } + boot_calc_checksum(sect, size, false, &checksum); + } - need_read_1_clus = de_next_offset < iter->read_size; - buf = need_read_1_clus ? - iter->dentries : iter->dentries + iter->read_size; + if (exfat_read(exfat->blk_dev->dev_fd, sect, size, 11 * size) != + (ssize_t)size) { + exfat_err("failed to read a boot checksum sector\n"); + ret = -EIO; + goto out; + } - ret = exfat_file_read(iter->exfat, iter->parent, buf, - iter->read_size, iter->next_read_offset); - if (ret == EOF) { - return EOF; - } else if (ret <= 0) { - exfat_err("failed to read a cluster. %d\n", ret); - return ret; + for (i = 0; i < size/sizeof(checksum); i++) { + if (le32_to_cpu(((__le32 *)sect)[i]) != checksum) { + if (exfat_repair_ask(exfat, ER_BS_CHECKSUM, + "checksums of boot sector are not correct. " + "%#x, but expected %#x", + le32_to_cpu(((__le32 *)sect)[i]), checksum)) { + goto out_write; + } else { + ret = -EINVAL; + goto out; + } } - iter->next_read_offset += iter->read_size; } +out: + free(sect); + return ret; - if (ith + 1 > iter->max_skip_dentries) - iter->max_skip_dentries = ith + 1; +out_write: + for (i = 0; i < size/sizeof(checksum); i++) + ((__le32 *)sect)[i] = cpu_to_le32(checksum); - *dentry = (struct exfat_dentry *) (iter->dentries + de_next_offset); + if (exfat_write(exfat->blk_dev->dev_fd, + sect, size, size * 11) != size) { + exfat_err("failed to write checksum sector\n"); + free(sect); + return -EIO; + } + free(sect); return 0; } -/* - * @skip_dentries must be the largest @ith + 1 of exfat_de_iter_get - * since the last call of exfat_de_iter_advance - */ -static int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries) +static int exfat_mark_volume_dirty(struct exfat *exfat, bool dirty) { - if (skip_dentries != iter->max_skip_dentries) - return -EINVAL; + 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; + } - iter->max_skip_dentries = 0; - iter->de_file_offset = iter->de_file_offset + - skip_dentries * sizeof(struct exfat_dentry); + if (fsync(exfat->blk_dev->dev_fd) != 0) { + exfat_err("failed to set VolumeDirty\n"); + return -EIO; + } return 0; } -static off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter) +static int read_boot_region(struct exfat_blk_dev *bd, struct pbr **pbr) { - return iter->de_file_offset; -} + struct pbr *bs; -static bool check_inode(struct exfat *exfat, struct exfat_inode *parent, - struct exfat_inode *node) -{ - bool ret = false; + bs = (struct pbr *)malloc(sizeof(struct pbr)); + if (!bs) { + exfat_err("failed to allocate memory\n"); + return -ENOMEM; + } - if (node->size == 0 && node->first_clus != EXFAT_FREE_CLUSTER) { - resolve_path_parent(&path_resolve_ctx, parent, node); - exfat_err("file is empty, but first cluster is %#x: %s\n", - node->first_clus, path_resolve_ctx.local_path); - ret = false; + if (exfat_read(bd->dev_fd, bs, sizeof(*bs), 0) != + (ssize_t)sizeof(*bs)) { + exfat_err("failed to read a boot sector\n"); + free(bs); + return -EIO; } - if (node->size > 0 && exfat_invalid_clus(exfat, node->first_clus)) { - resolve_path_parent(&path_resolve_ctx, parent, node); - exfat_err("first cluster %#x is invalid: %s\n", - node->first_clus, path_resolve_ctx.local_path); - ret = false; + if (memcmp(bs->bpb.oem_name, "EXFAT ", 8) != 0) { + exfat_err("failed to find exfat file system.\n"); + goto err; } - if (node->size > le32_to_cpu(exfat->bs->bsx.clu_count) * - EXFAT_CLUSTER_SIZE(exfat->bs)) { - resolve_path_parent(&path_resolve_ctx, parent, node); - exfat_err("size %" PRIu64 " is greater than cluster heap: %s\n", - node->size, path_resolve_ctx.local_path); - ret = false; + 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 (node->size == 0 && node->is_contiguous) { - resolve_path_parent(&path_resolve_ctx, parent, node); - exfat_err("empty, but marked as contiguous: %s\n", - path_resolve_ctx.local_path); - ret = false; + if (EXFAT_CLUSTER_SIZE(bs) > 32 * MB) { + exfat_err("too big cluster size: %d\n", EXFAT_CLUSTER_SIZE(bs)); + goto err; } - if ((node->attr & ATTR_SUBDIR) && - node->size % EXFAT_CLUSTER_SIZE(exfat->bs) != 0) { - resolve_path_parent(&path_resolve_ctx, parent, node); - exfat_err("directory size %" PRIu64 " is not divisible by %d: %s\n", - node->size, EXFAT_CLUSTER_SIZE(exfat->bs), - path_resolve_ctx.local_path); - ret = false; + 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 "\n, expected: %llu\n", + le64_to_cpu(bs->bsx.vol_length), + bd->num_sectors); + goto err; } - if (!node->is_contiguous && !inode_check_clus_chain(exfat, node)) { - resolve_path_parent(&path_resolve_ctx, parent, node); - exfat_err("corrupted cluster chain: %s\n", - path_resolve_ctx.local_path); - ret = false; + 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; } - return ret; + *pbr = bs; + return 0; +err: + free(bs); + return -EINVAL; } static void dentry_calc_checksum(struct exfat_dentry *dentry, @@ -788,13 +785,68 @@ static __le16 file_calc_checksum(struct exfat_de_iter *iter) 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; + + 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); + ret = -EINVAL; + } + + 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 + ret = -EINVAL; + } + + 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); + ret = -EINVAL; + } + + 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 + ret = -EINVAL; + } + + return ret; +} + 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; - __le16 checksum; /* TODO: mtime, atime, ... */ @@ -834,27 +886,23 @@ static int read_file_dentries(struct exfat_de_iter *iter, sizeof(name_de->name_unicode)); } - checksum = file_calc_checksum(iter); - if (le16_to_cpu(file_de->file_checksum) != checksum) { - exfat_err("invalid checksum. 0x%x != 0x%x\n", - le16_to_cpu(file_de->file_checksum), - le16_to_cpu(checksum)); - ret = -EINVAL; - goto err; - } - - node->size = le64_to_cpu(stream_de->stream_size); 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 (le64_to_cpu(stream_de->stream_valid_size) > node->size) { - resolve_path_parent(&path_resolve_ctx, iter->parent, node); - exfat_err("valid size %" PRIu64 " greater than size %" PRIu64 ": %s\n", - le64_to_cpu(stream_de->stream_valid_size), node->size, - path_resolve_ctx.local_path); - ret = -EINVAL; - goto err; + 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); @@ -876,21 +924,17 @@ static int read_file(struct exfat_de_iter *de_iter, *new_node = NULL; ret = read_file_dentries(de_iter, &node, dentry_count); - if (ret) { - exfat_err("corrupted file directory entries.\n"); + if (ret) return ret; - } - ret = check_inode(de_iter->exfat, de_iter->parent, node); - if (ret) { - exfat_err("corrupted file directory entries.\n"); + ret = check_inode(de_iter, node); + if (ret < 0) { free_exfat_inode(node); - return ret; + return -EINVAL; } - node->dentry_file_offset = exfat_de_iter_file_offset(de_iter); *new_node = node; - return 0; + return ret; } static bool read_volume_label(struct exfat_de_iter *iter) @@ -922,11 +966,27 @@ static bool read_volume_label(struct exfat_de_iter *iter) return true; } -static bool read_alloc_bitmap(struct exfat_de_iter *iter) +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; - ssize_t alloc_bitmap_size; exfat = iter->exfat; if (exfat_de_iter_get(iter, 0, &dentry)) @@ -936,38 +996,30 @@ static bool read_alloc_bitmap(struct exfat_de_iter *iter) le32_to_cpu(dentry->bitmap_start_clu), le64_to_cpu(dentry->bitmap_size)); - exfat->bit_count = le32_to_cpu(exfat->bs->bsx.clu_count); - if (le64_to_cpu(dentry->bitmap_size) < - DIV_ROUND_UP(exfat->bit_count, 8)) { + 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 (exfat_invalid_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) { + 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; } - /* TODO: bitmap could be very large. */ - alloc_bitmap_size = EXFAT_BITMAP_SIZE(exfat->bit_count); - exfat->alloc_bitmap = (__u32 *)malloc(alloc_bitmap_size); - if (!exfat->alloc_bitmap) { - exfat_err("failed to allocate bitmap\n"); - return false; - } + exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu); + exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8); - if (exfat_read(exfat->blk_dev->dev_fd, - exfat->alloc_bitmap, alloc_bitmap_size, - exfat_c2o(exfat, - le32_to_cpu(dentry->bitmap_start_clu))) != - alloc_bitmap_size) { - exfat_err("failed to read bitmap\n"); - free(exfat->alloc_bitmap); - exfat->alloc_bitmap = NULL; + 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; } @@ -985,7 +1037,7 @@ static bool read_upcase_table(struct exfat_de_iter *iter) if (exfat_de_iter_get(iter, 0, &dentry)) return false; - if (exfat_invalid_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) { + 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; @@ -1022,6 +1074,10 @@ static bool read_upcase_table(struct exfat_de_iter *iter) 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; } @@ -1032,11 +1088,8 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir) struct exfat_inode *node = NULL; struct exfat_dentry *dentry; int dentry_count; - struct list_head sub_dir_list; struct exfat_de_iter *de_iter; - INIT_LIST_HEAD(&sub_dir_list); - de_iter = &exfat->de_iter; ret = exfat_de_iter_init(de_iter, exfat, dir); if (ret == EOF) @@ -1049,7 +1102,8 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir) if (ret == EOF) { break; } else if (ret) { - exfat_err("failed to get a dentry. %d\n", ret); + fsck_err(dir->parent, dir, + "failed to get a dentry. %d\n", ret); goto err; } @@ -1058,15 +1112,18 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir) switch (dentry->type) { case EXFAT_FILE: ret = read_file(de_iter, &node, &dentry_count); - if (ret) { - exfat_err("failed to verify file. %d\n", ret); + 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, &sub_dir_list); + list_add_tail(&node->list, &exfat->dir_list); } else free_exfat_inode(node); break; @@ -1078,7 +1135,7 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir) } break; case EXFAT_BITMAP: - if (!read_alloc_bitmap(de_iter)) { + if (!read_bitmap(de_iter)) { exfat_err( "failed to verify allocation bitmap\n"); ret = -EINVAL; @@ -1093,9 +1150,10 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir) goto err; } break; + case EXFAT_LAST: + goto out; default: - if (IS_EXFAT_DELETED(dentry->type) || - (dentry->type == EXFAT_UNUSED)) + if (IS_EXFAT_DELETED(dentry->type)) break; exfat_err("unknown entry type. 0x%x\n", dentry->type); ret = -EINVAL; @@ -1104,14 +1162,137 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir) exfat_de_iter_advance(de_iter, dentry_count); } - list_splice(&sub_dir_list, &exfat->dir_list); +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. @@ -1119,14 +1300,14 @@ err: * 2. free all of file exfat_nodes. * 3. if the directory does not have children, free its exfat_node. */ -static bool exfat_filesystem_check(struct exfat *exfat) +static int exfat_filesystem_check(struct exfat *exfat) { struct exfat_inode *dir; - bool ret = true; + int ret = 0, dir_errors; if (!exfat->root) { exfat_err("root is NULL\n"); - return false; + return -ENOENT; } list_add(&exfat->root->list, &exfat->dir_list); @@ -1135,20 +1316,19 @@ static bool exfat_filesystem_check(struct exfat *exfat) dir = list_entry(exfat->dir_list.next, struct exfat_inode, list); if (!(dir->attr & ATTR_SUBDIR)) { - resolve_path(&path_resolve_ctx, dir); - ret = false; - exfat_err("failed to travel directories. " - "the node is not directory: %s\n", - path_resolve_ctx.local_path); + fsck_err(dir->parent, dir, + "failed to travel directories. " + "the node is not directory\n"); + ret = -EINVAL; goto out; } - if (read_children(exfat, dir)) { + dir_errors = read_children(exfat, dir); + if (dir_errors) { resolve_path(&path_resolve_ctx, dir); - ret = false; - exfat_err("failed to check dentries: %s\n", + exfat_debug("failed to check dentries: %s\n", path_resolve_ctx.local_path); - goto out; + ret = dir_errors; } list_del(&dir->list); @@ -1158,10 +1338,12 @@ static bool exfat_filesystem_check(struct exfat *exfat) out: exfat_free_dir_list(exfat); exfat->root = NULL; + if (exfat->dirty_fat && reclaim_free_clusters(exfat)) + return -EIO; return ret; } -static bool exfat_root_dir_check(struct exfat *exfat) +static int exfat_root_dir_check(struct exfat *exfat) { struct exfat_inode *root; clus_t clus_count; @@ -1169,55 +1351,73 @@ static bool exfat_root_dir_check(struct exfat *exfat) root = alloc_exfat_inode(ATTR_SUBDIR); if (!root) { exfat_err("failed to allocate memory\n"); - return false; + return -ENOMEM; } root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster); - if (!inode_get_clus_count(exfat, root, &clus_count)) { + if (!root_get_clus_count(exfat, root, &clus_count)) { exfat_err("failed to follow the cluster chain of root\n"); - goto err; + free_exfat_inode(root); + return -EINVAL; } - root->size = clus_count * EXFAT_CLUSTER_SIZE(exfat->bs); + 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 true; -err: - free_exfat_inode(root); - exfat->root = NULL; - return false; + return 0; } -void exfat_show_info(struct exfat *exfat) +static char *bytes_to_human_readable(size_t bytes) { - exfat_info("Bytes per sector: %d\n", - 1 << exfat->bs->bsx.sect_size_bits); - exfat_info("Sectors per cluster: %d\n", - 1 << exfat->bs->bsx.sect_per_clus_bits); - exfat_info("Cluster heap count: %d(0x%x)\n", - le32_to_cpu(exfat->bs->bsx.clu_count), - le32_to_cpu(exfat->bs->bsx.clu_count)); - exfat_info("Cluster heap offset: %#x\n", - le32_to_cpu(exfat->bs->bsx.clu_offset)); + 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; } -void exfat_show_stat(void) +static void exfat_show_info(struct exfat *exfat, const char *dev_name, + int errors) { - exfat_debug("Found directories: %ld\n", exfat_stat.dir_count); - exfat_debug("Found files: %ld\n", exfat_stat.file_count); - exfat_debug("Found leak directories: %ld\n", - exfat_stat.dir_count - exfat_stat.dir_free_count); - exfat_debug("Found leak files: %ld\n", - exfat_stat.file_count - exfat_stat.file_free_count); + 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[]) { - int c, ret; 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)); @@ -1229,25 +1429,27 @@ int main(int argc, char * const argv[]) exfat_err("failed to init locale/codeset\n"); opterr = 0; - while ((c = getopt_long(argc, argv, "rynVvh", opts, NULL)) != EOF) { + while ((c = getopt_long(argc, argv, "rynpVvh", opts, NULL)) != EOF) { switch (c) { case 'n': - if (ui.options & FSCK_OPTS_REPAIR) + if (ui.options & FSCK_OPTS_REPAIR_ALL) usage(argv[0]); ui.options |= FSCK_OPTS_REPAIR_NO; - ui.ei.writeable = false; break; case 'r': - if (ui.options & FSCK_OPTS_REPAIR) + if (ui.options & FSCK_OPTS_REPAIR_ALL) usage(argv[0]); ui.options |= FSCK_OPTS_REPAIR_ASK; - ui.ei.writeable = true; break; case 'y': - if (ui.options & FSCK_OPTS_REPAIR) + if (ui.options & FSCK_OPTS_REPAIR_ALL) usage(argv[0]); ui.options |= FSCK_OPTS_REPAIR_YES; - ui.ei.writeable = true; + break; + case 'p': + if (ui.options & FSCK_OPTS_REPAIR_ALL) + usage(argv[0]); + ui.options |= FSCK_OPTS_REPAIR_AUTO; break; case 'V': version_only = true; @@ -1264,12 +1466,18 @@ int main(int argc, char * const argv[]) } show_version(); - if (version_only) - exit(FSCK_EXIT_SYNTAX_ERROR); - 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) { @@ -1277,44 +1485,58 @@ int main(int argc, char * const argv[]) return FSCK_EXIT_OPERATION_ERROR; } - exfat = alloc_exfat(&bd); + ret = read_boot_region(&bd, &bs); + if (ret) + goto err; + + exfat = alloc_exfat(&bd, bs); if (!exfat) { - ret = FSCK_EXIT_OPERATION_ERROR; + ret = -ENOMEM; goto err; } exfat->options = ui.options; - exfat_debug("verifying boot regions...\n"); - if (!exfat_boot_region_check(exfat)) { - exfat_err("failed to verify boot regions.\n"); - ret = FSCK_EXIT_ERRORS_LEFT; + if (exfat_mark_volume_dirty(exfat, true)) { + ret = -EIO; goto err; } - exfat_show_info(exfat); + ret = boot_region_checksum(exfat); + if (ret) + goto err; exfat_debug("verifying root directory...\n"); - if (!exfat_root_dir_check(exfat)) { + ret = exfat_root_dir_check(exfat); + if (ret) { exfat_err("failed to verify root directory.\n"); - ret = FSCK_EXIT_ERRORS_LEFT; goto out; } exfat_debug("verifying directory entries...\n"); - if (!exfat_filesystem_check(exfat)) { - exfat_err("failed to verify directory entries.\n"); - ret = FSCK_EXIT_ERRORS_LEFT; + 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); - if (ui.ei.writeable) - fsync(bd.dev_fd); - printf("%s: clean\n", ui.ei.dev_name); - ret = FSCK_EXIT_NO_ERRORS; out: - exfat_show_stat(); + 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 ret; + return exit_code; } diff --git a/fsck/fsck.h b/fsck/fsck.h index ef46fa7..6c91fac 100644 --- a/fsck/fsck.h +++ b/fsck/fsck.h @@ -20,19 +20,29 @@ struct exfat_inode { __u16 attr; uint64_t size; bool is_contiguous; - off_t dentry_file_offset; __le16 name[0]; /* only for directory */ }; #define EXFAT_NAME_MAX 255 #define NAME_BUFFER_SIZE ((EXFAT_NAME_MAX+1)*2) +struct buffer_desc { + clus_t p_clus; + unsigned int offset; + char *buffer; + char *dirty; +}; + struct exfat_de_iter { struct exfat *exfat; struct exfat_inode *parent; - unsigned char *dentries; /* cluster * 2 allocated */ - unsigned int read_size; /* cluster size */ - off_t de_file_offset; /* offset in dentries buffer */ + struct buffer_desc *buffer_desc; /* cluster * 2 */ + clus_t ra_next_clus; + unsigned int ra_begin_offset; + unsigned int ra_partial_size; + unsigned int read_size; /* cluster size */ + unsigned int write_size; /* sector size */ + off_t de_file_offset; off_t next_read_offset; int max_skip_dentries; }; @@ -41,23 +51,49 @@ enum fsck_ui_options { FSCK_OPTS_REPAIR_ASK = 0x01, FSCK_OPTS_REPAIR_YES = 0x02, FSCK_OPTS_REPAIR_NO = 0x04, - FSCK_OPTS_REPAIR = 0x07, + FSCK_OPTS_REPAIR_AUTO = 0x08, + FSCK_OPTS_REPAIR_WRITE = 0x0b, + FSCK_OPTS_REPAIR_ALL = 0x0f, }; struct exfat { enum fsck_ui_options options; + bool dirty:1; + bool dirty_fat:1; struct exfat_blk_dev *blk_dev; struct pbr *bs; char volume_label[VOLUME_LABEL_BUFFER_SIZE]; struct exfat_inode *root; struct list_head dir_list; + clus_t clus_count; + unsigned int clus_size; + unsigned int sect_size; struct exfat_de_iter de_iter; - __u32 *alloc_bitmap; - __u64 bit_count; + struct buffer_desc buffer_desc[2]; /* cluster * 2 */ + char *alloc_bitmap; + char *disk_bitmap; + clus_t disk_bitmap_clus; + unsigned int disk_bitmap_size; }; #define EXFAT_CLUSTER_SIZE(pbr) (1 << ((pbr)->bsx.sect_size_bits + \ (pbr)->bsx.sect_per_clus_bits)) #define EXFAT_SECTOR_SIZE(pbr) (1 << (pbr)->bsx.sect_size_bits) +/* fsck.c */ +off_t exfat_c2o(struct exfat *exfat, unsigned int clus); +int get_next_clus(struct exfat *exfat, struct exfat_inode *node, + clus_t clus, clus_t *next); + +/* de_iter.c */ +int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, + struct exfat_inode *dir); +int exfat_de_iter_get(struct exfat_de_iter *iter, + int ith, struct exfat_dentry **dentry); +int exfat_de_iter_get_dirty(struct exfat_de_iter *iter, + int ith, struct exfat_dentry **dentry); +int exfat_de_iter_flush(struct exfat_de_iter *iter); +int exfat_de_iter_advance(struct exfat_de_iter *iter, int skip_dentries); +off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter); + #endif diff --git a/fsck/repair.c b/fsck/repair.c index bbfb75d..19a2b0d 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -4,6 +4,7 @@ */ #include #include +#include #include "exfat_ondisk.h" #include "libexfat.h" @@ -12,37 +13,35 @@ struct exfat_repair_problem { er_problem_code_t prcode; - const char *description; - bool (*fix_problem)(struct exfat *exfat, - union exfat_repair_context *rctx); + unsigned int flags; + unsigned int prompt_type; }; -static bool fix_bs_checksum(struct exfat *exfat, - union exfat_repair_context *rctx) -{ - unsigned int size; - unsigned int i; +/* Problem flags */ +#define ERF_PREEN_YES 0x00000001 +#define ERF_DEFAULT_YES 0x00000002 +#define ERF_DEFAULT_NO 0x00000004 - size = EXFAT_SECTOR_SIZE(exfat->bs); - for (i = 0; i < size/sizeof(__le32); i++) { - ((__le32 *)rctx->bs_checksum.checksum_sect)[i] = - rctx->bs_checksum.checksum; - } +/* Prompt types */ +#define ERP_FIX 0x00000001 +#define ERP_TRUNCATE 0x00000002 - if (exfat_write(exfat->blk_dev->dev_fd, - rctx->bs_checksum.checksum_sect, - size, size * 11) != size) { - exfat_err("failed to write checksum sector\n"); - return false; - } - - return true; -} +static const char *prompts[] = { + "Repair", + "Fix", + "Truncate", +}; static struct exfat_repair_problem problems[] = { - {ER_BS_CHECKSUM, - "the checksum of boot sector is not correct", - fix_bs_checksum}, + {ER_BS_CHECKSUM, ERF_DEFAULT_YES | ERF_PREEN_YES, ERP_FIX}, + {ER_DE_CHECKSUM, ERF_DEFAULT_YES | ERF_PREEN_YES, ERP_FIX}, + {ER_FILE_VALID_SIZE, ERF_DEFAULT_YES | ERF_PREEN_YES, ERP_FIX}, + {ER_FILE_INVALID_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE}, + {ER_FILE_FIRST_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE}, + {ER_FILE_SMALLER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE}, + {ER_FILE_LARGER_SIZE, ERF_DEFAULT_NO, ERP_TRUNCATE}, + {ER_FILE_DUPLICATED_CLUS, ERF_DEFAULT_NO, ERP_TRUNCATE}, + {ER_FILE_ZERO_NOFAT, ERF_DEFAULT_YES | ERF_PREEN_YES, ERP_FIX}, }; static struct exfat_repair_problem *find_problem(er_problem_code_t prcode) @@ -59,42 +58,45 @@ static struct exfat_repair_problem *find_problem(er_problem_code_t prcode) static bool ask_repair(struct exfat *exfat, struct exfat_repair_problem *pr) { + bool repair = false; char answer[8]; - switch (exfat->options & FSCK_OPTS_REPAIR) { - case FSCK_OPTS_REPAIR_ASK: - do { - printf("%s: Fix (y/N)?", pr->description); - fflush(stdout); - - if (fgets(answer, sizeof(answer), stdin)) { - if (strcasecmp(answer, "Y\n") == 0) - return true; - else if (strcasecmp(answer, "\n") == 0 || - strcasecmp(answer, "N\n") == 0) - return false; - } - } while (1); - return false; - case FSCK_OPTS_REPAIR_YES: - return true; - case FSCK_OPTS_REPAIR_NO: - case 0: - default: - return false; + if (exfat->options & FSCK_OPTS_REPAIR_NO || + pr->flags & ERF_DEFAULT_NO) + repair = false; + else if (exfat->options & FSCK_OPTS_REPAIR_YES || + pr->flags & ERF_DEFAULT_YES) + repair = true; + else { + if (exfat->options & FSCK_OPTS_REPAIR_ASK) { + do { + printf(". %s (y/N)? ", + prompts[pr->prompt_type]); + fflush(stdout); + + if (fgets(answer, sizeof(answer), stdin)) { + if (strcasecmp(answer, "Y\n") == 0) + return true; + else if (strcasecmp(answer, "\n") == 0 + || strcasecmp(answer, "N\n") == 0) + return false; + } + } while (1); + } else if (exfat->options & FSCK_OPTS_REPAIR_AUTO && + pr->flags & ERF_PREEN_YES) + repair = true; } - return false; + + printf(". %s (y/N)? %c\n", prompts[pr->prompt_type], + repair ? 'y' : 'n'); + return repair; } -bool exfat_repair(struct exfat *exfat, er_problem_code_t prcode, - union exfat_repair_context *rctx) +bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode, + const char *desc, ...) { struct exfat_repair_problem *pr = NULL; - int need_repair; - - need_repair = ask_repair(exfat, pr); - if (!need_repair) - return false; + va_list ap; pr = find_problem(prcode); if (!pr) { @@ -102,5 +104,15 @@ bool exfat_repair(struct exfat *exfat, er_problem_code_t prcode, return false; } - return pr->fix_problem(exfat, rctx); + va_start(ap, desc); + vprintf(desc, ap); + va_end(ap); + + if (ask_repair(exfat, pr)) { + if (pr->prompt_type & ERP_TRUNCATE) + exfat->dirty_fat = true; + exfat->dirty = true; + return true; + } else + return false; } diff --git a/fsck/repair.h b/fsck/repair.h index 4c56763..6a34b9b 100644 --- a/fsck/repair.h +++ b/fsck/repair.h @@ -6,17 +6,18 @@ #define _REPAIR_H #define ER_BS_CHECKSUM 0x00000001 +#define ER_DE_CHECKSUM 0x00001001 +#define ER_FILE_VALID_SIZE 0x00002001 +#define ER_FILE_INVALID_CLUS 0x00002002 +#define ER_FILE_FIRST_CLUS 0x00002003 +#define ER_FILE_SMALLER_SIZE 0x00002004 +#define ER_FILE_LARGER_SIZE 0x00002005 +#define ER_FILE_DUPLICATED_CLUS 0x00002006 +#define ER_FILE_ZERO_NOFAT 0x00002007 typedef unsigned int er_problem_code_t; -union exfat_repair_context { - struct { - __le32 checksum; - void *checksum_sect; - } bs_checksum; -}; - -bool exfat_repair(struct exfat *exfat, er_problem_code_t prcode, - union exfat_repair_context *rctx); +bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode, + const char *fmt, ...); #endif diff --git a/include/exfat_ondisk.h b/include/exfat_ondisk.h index 70546a3..163bef0 100644 --- a/include/exfat_ondisk.h +++ b/include/exfat_ondisk.h @@ -44,7 +44,7 @@ #define MSDOS_DELETED 0xE5 /* deleted mark */ #define MSDOS_UNUSED 0x00 /* end of directory */ -#define EXFAT_UNUSED 0x00 /* end of directory */ +#define EXFAT_LAST 0x00 /* end of directory */ #define EXFAT_DELETE ~(0x80) #define IS_EXFAT_DELETED(x) ((x) < 0x80) /* deleted file (0x01~0x7F) */ #define EXFAT_INVAL 0x80 /* invalid value */ diff --git a/include/libexfat.h b/include/libexfat.h index 56053b4..6a3ecb8 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -28,6 +28,8 @@ #define EXFAT_MAX_NUM_CLUSTER (0xFFFFFFF5) +#define DEFAULT_BOUNDARY_ALIGNMENT (1024*1024) + #define DEFAULT_SECTOR_SIZE (512) #define VOLUME_LABEL_BUFFER_SIZE (VOLUME_LABEL_MAX_LEN*MB_LEN_MAX+1) @@ -59,6 +61,7 @@ struct exfat_user_input { bool writeable; unsigned int cluster_size; unsigned int sec_per_clu; + unsigned int boundary_align; bool quick; __u16 volume_label[VOLUME_LABEL_MAX_LEN]; int volume_label_len; @@ -79,6 +82,7 @@ int exfat_get_blk_dev_info(struct exfat_user_input *ui, ssize_t exfat_read(int fd, void *buf, size_t size, off_t offset); ssize_t exfat_write(int fd, void *buf, size_t size, off_t offset); +size_t exfat_utf16_len(const __le16 *str, size_t max_size); ssize_t exfat_utf16_enc(const char *in_str, __u16 *out_str, size_t out_size); ssize_t exfat_utf16_dec(const __u16 *in_str, size_t in_len, char *out_str, size_t out_size); @@ -93,19 +97,19 @@ extern unsigned int print_level; #define EXFAT_INFO (2) #define EXFAT_DEBUG (3) -#define exfat_msg(level, fmt, ...) \ - do { \ - if (print_level >= level) { \ - if (print_level == EXFAT_INFO) \ - printf(fmt, ##__VA_ARGS__); \ - else \ - printf("[%s:%4d] " fmt, \ - __func__, __LINE__, ##__VA_ARGS__); \ - } \ - } while (0) \ - -#define exfat_err(fmt, ...) exfat_msg(EXFAT_ERROR, fmt, ##__VA_ARGS__) -#define exfat_info(fmt, ...) exfat_msg(EXFAT_INFO, fmt, ##__VA_ARGS__) -#define exfat_debug(fmt, ...) exfat_msg(EXFAT_DEBUG, fmt, ##__VA_ARGS__) +#define exfat_msg(level, dir, fmt, ...) \ + do { \ + if (print_level >= level) { \ + fprintf(dir, fmt, ##__VA_ARGS__); \ + } \ + } while (0) \ + +#define exfat_err(fmt, ...) exfat_msg(EXFAT_ERROR, stderr, \ + fmt, ##__VA_ARGS__) +#define exfat_info(fmt, ...) exfat_msg(EXFAT_INFO, stdout, \ + fmt, ##__VA_ARGS__) +#define exfat_debug(fmt, ...) exfat_msg(EXFAT_DEBUG, stdout, \ + "[%s:%4d] " fmt, __func__, \ + __LINE__, ##__VA_ARGS__) #endif /* !_LIBEXFAT_H */ diff --git a/include/version.h b/include/version.h index 1ec8a63..e463903 100644 --- a/include/version.h +++ b/include/version.h @@ -5,6 +5,6 @@ #ifndef _VERSION_H -#define EXFAT_PROGS_VERSION "1.0.3" +#define EXFAT_PROGS_VERSION "1.0.4" #endif /* !_VERSION_H */ diff --git a/lib/libexfat.c b/lib/libexfat.c index 24f682b..1c0bbc0 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -165,6 +165,9 @@ int exfat_get_blk_dev_info(struct exfat_user_input *ui, if (!ui->cluster_size) exfat_set_default_cluster_size(bd, ui); + if (!ui->boundary_align) + ui->boundary_align = DEFAULT_BOUNDARY_ALIGNMENT; + if (ioctl(fd, BLKSSZGET, &bd->sector_size) < 0) bd->sector_size = DEFAULT_SECTOR_SIZE; bd->sector_size_bits = sector_size_bits(bd->sector_size); @@ -195,6 +198,15 @@ ssize_t exfat_write(int fd, void *buf, size_t size, off_t offset) return pwrite(fd, buf, size, offset); } +size_t exfat_utf16_len(const __le16 *str, size_t max_size) +{ + size_t i = 0; + + while (le16_to_cpu(str[i]) && i < max_size) + i++; + return i; +} + ssize_t exfat_utf16_enc(const char *in_str, __u16 *out_str, size_t out_size) { size_t mbs_len, out_len, i; @@ -215,6 +227,7 @@ ssize_t exfat_utf16_enc(const char *in_str, __u16 *out_str, size_t out_size) if (mbstowcs(wcs, in_str, mbs_len+1) == (size_t)-1) { if (errno == EINVAL || errno == EILSEQ) exfat_err("invalid character sequence in current locale\n"); + free(wcs); return -errno; } @@ -278,7 +291,7 @@ ssize_t exfat_utf16_dec(const __u16 *in_str, size_t in_len, memset(&ps, 0, sizeof(ps)); /* And then convert wchar_t* string to multibyte char* string */ - for (i = 0, out_len = 0, c_len = 0; i < wcs_len; i++) { + for (i = 0, out_len = 0, c_len = 0; i <= wcs_len; i++) { c_len = wcrtomb(c_str, wcs[i], &ps); /* * If character is non-representable in current locale then @@ -307,7 +320,7 @@ ssize_t exfat_utf16_dec(const __u16 *in_str, size_t in_len, free(wcs); /* Last iteration of above loop should have produced null byte */ - if (c_len == 0 || out_str[out_len] != 0) { + if (c_len == 0 || out_str[out_len-1] != 0) { exfat_err("invalid UTF-16 sequence\n"); return -errno; } diff --git a/manpages/fsck.exfat.8 b/manpages/fsck.exfat.8 index b6cf3c8..b02a056 100644 --- a/manpages/fsck.exfat.8 +++ b/manpages/fsck.exfat.8 @@ -19,7 +19,7 @@ fsck.exfat \- check an exFAT filesystem .B fsck.exfat \-V .SH DESCRIPTION .B fsck.exfat -checks an exFAT filesystem and repairs the filesytem +checks an exFAT filesystem and repairs the filesystem depending on the options passed. .PP .SH OPTIONS diff --git a/manpages/mkfs.exfat.8 b/manpages/mkfs.exfat.8 index bc7ab4d..0bd8ffc 100644 --- a/manpages/mkfs.exfat.8 +++ b/manpages/mkfs.exfat.8 @@ -4,6 +4,9 @@ mkfs.exfat \- create an exFAT filesystem .SH SYNOPSIS .B mkfs.exfat [ +.B \-b +.I boundary_alignment +] [ .B \-c .I cluster_size ] [ @@ -36,6 +39,12 @@ SCSI disk, use: .PP .SH OPTIONS .TP +.BI \-b " boundary_alignment" +Specify the alignment for FAT and start of cluster. +Boundary alignment can be specified in m/M for megabytes +and k/K for kilobytes. It should be a power of two. +Some media like sdcard need this. +.TP .BI \-c " cluster_size" Specify the cluster size. Cluster size can be specified in m/M for megabytes and k/K for kilobytes. diff --git a/manpages/tune.exfat.8 b/manpages/tune.exfat.8 index 12dd6d2..c834ed9 100644 --- a/manpages/tune.exfat.8 +++ b/manpages/tune.exfat.8 @@ -31,4 +31,3 @@ Prints verbose debugging information while extracting or tuning parameters of th .TP .B \-V Prints the version number and exits. -.TP diff --git a/mkfs/Makefile.am b/mkfs/Makefile.am index 201ba3b..7b3e35b 100644 --- a/mkfs/Makefile.am +++ b/mkfs/Makefile.am @@ -1,5 +1,4 @@ AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common -LIBS = -lm mkfs_exfat_LDADD = $(top_builddir)/lib/libexfat.a sbin_PROGRAMS = mkfs.exfat diff --git a/mkfs/Makefile.in b/mkfs/Makefile.in index b18897a..d43dece 100644 --- a/mkfs/Makefile.in +++ b/mkfs/Makefile.in @@ -208,7 +208,7 @@ INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ LD = @LD@ LDFLAGS = @LDFLAGS@ LIBOBJS = @LIBOBJS@ -LIBS = -lm +LIBS = @LIBS@ LIBTOOL = @LIBTOOL@ LIPO = @LIPO@ LN_S = @LN_S@ diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c index 4cafb3e..9fff978 100644 --- a/mkfs/mkfs.c +++ b/mkfs/mkfs.c @@ -15,8 +15,8 @@ #include #include #include -#include #include +#include #include "exfat_ondisk.h" #include "libexfat.h" @@ -24,11 +24,26 @@ struct exfat_mkfs_info finfo; +/* random serial generator based on current time */ +static unsigned int get_new_serial(void) +{ + struct timespec ts; + + if (clock_gettime(CLOCK_REALTIME, &ts)) { + /* set 0000-0000 on error */ + ts.tv_sec = 0; + ts.tv_nsec = 0; + } + + return (unsigned int)(ts.tv_nsec << 12 | ts.tv_sec); +} + static void exfat_setup_boot_sector(struct pbr *ppbr, struct exfat_blk_dev *bd, struct exfat_user_input *ui) { struct bpb64 *pbpb = &ppbr->bpb; struct bsx64 *pbsx = &ppbr->bsx; + unsigned int i; /* Fill exfat BIOS paramemter block */ pbpb->jmp_boot[0] = 0xeb; @@ -45,10 +60,13 @@ static void exfat_setup_boot_sector(struct pbr *ppbr, pbsx->clu_offset = cpu_to_le32(finfo.clu_byte_off / bd->sector_size); pbsx->clu_count = cpu_to_le32(finfo.total_clu_cnt); pbsx->root_cluster = cpu_to_le32(finfo.root_start_clu); - pbsx->vol_serial = cpu_to_le32(1234); + pbsx->vol_serial = cpu_to_le32(finfo.volume_serial); pbsx->vol_flags = 0; pbsx->sect_size_bits = bd->sector_size_bits; - pbsx->sect_per_clus_bits = log2(ui->cluster_size / bd->sector_size); + pbsx->sect_per_clus_bits = 0; + /* Compute base 2 logarithm of ui->cluster_size / bd->sector_size */ + for (i = ui->cluster_size / bd->sector_size; i > 1; i /= 2) + pbsx->sect_per_clus_bits++; pbsx->num_fats = 1; /* fs_version[0] : minor and fs_version[1] : major */ pbsx->fs_version[0] = 0; @@ -387,6 +405,7 @@ static void usage(void) fprintf(stderr, "Usage: mkfs.exfat\n"); fprintf(stderr, "\t-L | --volume-label=label Set volume label\n"); fprintf(stderr, "\t-c | --cluster-size=size(or suffixed by 'K' or 'M') Specify cluster size\n"); + fprintf(stderr, "\t-b | --boundary-align=size(or suffixed by 'K' or 'M') Specify boundary alignment\n"); fprintf(stderr, "\t-f | --full-format Full format\n"); fprintf(stderr, "\t-V | --version Show version\n"); fprintf(stderr, "\t-v | --verbose Print debug\n"); @@ -398,6 +417,7 @@ static void usage(void) static struct option opts[] = { {"volume-label", required_argument, NULL, 'L' }, {"cluster-size", required_argument, NULL, 'c' }, + {"boundary-align", required_argument, NULL, 'b' }, {"full-format", no_argument, NULL, 'f' }, {"version", no_argument, NULL, 'V' }, {"verbose", no_argument, NULL, 'v' }, @@ -409,14 +429,23 @@ static struct option opts[] = { static int exfat_build_mkfs_info(struct exfat_blk_dev *bd, struct exfat_user_input *ui) { - if (ui->cluster_size > DEFAULT_CLUSTER_SIZE) - finfo.fat_byte_off = ui->cluster_size; - else - finfo.fat_byte_off = DEFAULT_CLUSTER_SIZE; + int clu_len; + + if (ui->boundary_align < bd->sector_size) { + exfat_err("boundary alignment is too small (min %d)\n", + bd->sector_size); + return -1; + } + finfo.fat_byte_off = round_up(24 * bd->sector_size, + ui->boundary_align); finfo.fat_byte_len = round_up((bd->num_clusters * sizeof(int)), ui->cluster_size); finfo.clu_byte_off = round_up(finfo.fat_byte_off + finfo.fat_byte_len, - DEFAULT_CLUSTER_SIZE); + ui->boundary_align); + if (bd->size <= finfo.clu_byte_off) { + exfat_err("boundary alignment is too big\n"); + return -1; + } finfo.total_clu_cnt = (bd->size - finfo.clu_byte_off) / ui->cluster_size; if (finfo.total_clu_cnt > EXFAT_MAX_NUM_CLUSTER) { @@ -426,17 +455,17 @@ static int exfat_build_mkfs_info(struct exfat_blk_dev *bd, finfo.bitmap_byte_off = finfo.clu_byte_off; finfo.bitmap_byte_len = round_up(finfo.total_clu_cnt, 8) / 8; - finfo.ut_start_clu = round_up(EXFAT_REVERVED_CLUSTERS * - ui->cluster_size + finfo.bitmap_byte_len, ui->cluster_size) / - ui->cluster_size; - finfo.ut_byte_off = round_up(finfo.bitmap_byte_off + - finfo.bitmap_byte_len, ui->cluster_size); + clu_len = round_up(finfo.bitmap_byte_len, ui->cluster_size); + + finfo.ut_start_clu = EXFAT_FIRST_CLUSTER + clu_len / ui->cluster_size; + finfo.ut_byte_off = finfo.bitmap_byte_off + clu_len; finfo.ut_byte_len = EXFAT_UPCASE_TABLE_SIZE; - finfo.root_start_clu = round_up(finfo.ut_start_clu * ui->cluster_size - + finfo.ut_byte_len, ui->cluster_size) / ui->cluster_size; - finfo.root_byte_off = round_up(finfo.ut_byte_off + finfo.ut_byte_len, - ui->cluster_size); + clu_len = round_up(finfo.ut_byte_len, ui->cluster_size); + + finfo.root_start_clu = finfo.ut_start_clu + clu_len / ui->cluster_size; + finfo.root_byte_off = finfo.ut_byte_off + clu_len; finfo.root_byte_len = sizeof(struct exfat_dentry) * 3; + finfo.volume_serial = get_new_serial(); return 0; } @@ -525,7 +554,7 @@ static int make_exfat(struct exfat_blk_dev *bd, struct exfat_user_input *ui) return 0; } -static long long parse_cluster_size(const char *size) +static long long parse_size(const char *size) { char *data_unit; unsigned long long byte_size = strtoull(size, &data_unit, 0); @@ -539,8 +568,10 @@ static long long parse_cluster_size(const char *size) case 'k': byte_size <<= 10; break; + case '\0': + break; default: - exfat_err("Wrong unit input('%c') for cluster size\n", + exfat_err("Wrong unit input('%c') for size\n", *data_unit); return -EINVAL; } @@ -562,7 +593,7 @@ int main(int argc, char *argv[]) exfat_err("failed to init locale/codeset\n"); opterr = 0; - while ((c = getopt_long(argc, argv, "n:L:c:fVvh", opts, NULL)) != EOF) + while ((c = getopt_long(argc, argv, "n:L:c:b:fVvh", opts, NULL)) != EOF) switch (c) { /* * Make 'n' option fallthrough to 'L' option for for backward @@ -580,16 +611,31 @@ int main(int argc, char *argv[]) break; } case 'c': - ret = parse_cluster_size(optarg); + ret = parse_size(optarg); if (ret < 0) goto out; - else if (ret > EXFAT_MAX_CLUSTER_SIZE) { + else if (ret & (ret - 1)) { + exfat_err("cluster size(%d) is not a power of 2)\n", + ret); + goto out; + } else if (ret > EXFAT_MAX_CLUSTER_SIZE) { exfat_err("cluster size(%d) exceeds max cluster size(%d)\n", ui.cluster_size, EXFAT_MAX_CLUSTER_SIZE); goto out; } ui.cluster_size = ret; break; + case 'b': + ret = parse_size(optarg); + if (ret < 0) + goto out; + else if (ret & (ret - 1)) { + exfat_err("boundary align(%d) is not a power of 2)\n", + ret); + goto out; + } + ui.boundary_align = ret; + break; case 'f': ui.quick = false; break; @@ -622,23 +668,24 @@ int main(int argc, char *argv[]) ret = exfat_build_mkfs_info(&bd, &ui); if (ret) - goto out; + goto close; ret = exfat_zero_out_disk(&bd, &ui); if (ret) - goto out; + goto close; ret = make_exfat(&bd, &ui); if (ret) - goto out; + goto close; exfat_info("Synchronizing...\n"); ret = fsync(bd.dev_fd); +close: + close(bd.dev_fd); out: if (!ret) exfat_info("\nexFAT format complete!\n"); else exfat_info("\nexFAT format fail!\n"); - close(bd.dev_fd); return ret; } diff --git a/mkfs/mkfs.h b/mkfs/mkfs.h index df31894..ffd56e3 100644 --- a/mkfs/mkfs.h +++ b/mkfs/mkfs.h @@ -5,9 +5,8 @@ #ifndef _MKFS_H -#define DEFAULT_CLUSTER_SIZE (1024*1024) -#define MIN_NUM_SECTOR (2048) -#define EXFAT_MAX_CLUSTER_SIZE (32*1024*1024) +#define MIN_NUM_SECTOR (2048) +#define EXFAT_MAX_CLUSTER_SIZE (32*1024*1024) struct exfat_mkfs_info { unsigned int total_clu_cnt; @@ -24,6 +23,7 @@ struct exfat_mkfs_info { unsigned int root_byte_off; unsigned int root_byte_len; unsigned int root_start_clu; + unsigned int volume_serial; }; extern struct exfat_mkfs_info finfo;