From: Sven Hoexter Date: Tue, 31 Oct 2023 18:52:55 +0000 (+0100) Subject: Add CVE ID to debian changelog X-Git-Url: https://git.sven.stormbind.net/?p=sven%2Fexfatprogs.git;a=commitdiff_plain;h=HEAD;hp=54d129d80c76f1f4e5de9e3e54c19aa851eb5469 Add CVE ID to debian changelog --- diff --git a/Android.bp b/Android.bp index 9f8716b..d0e2594 100644 --- a/Android.bp +++ b/Android.bp @@ -9,6 +9,7 @@ cc_library_headers { "tune", "label", "dump", + "exfat2img", ], } diff --git a/COPYING b/COPYING index 941c87d..d159169 100644 --- a/COPYING +++ b/COPYING @@ -56,7 +56,7 @@ patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. - GNU GENERAL PUBLIC LICENSE + GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains @@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS + END OF TERMS AND CONDITIONS - How to Apply These Terms to Your New Programs + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it diff --git a/Makefile.am b/Makefile.am index 44f8635..3e36f55 100644 --- a/Makefile.am +++ b/Makefile.am @@ -2,7 +2,7 @@ ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = lib mkfs fsck tune label dump +SUBDIRS = lib mkfs fsck tune label dump exfat2img # manpages dist_man8_MANS = \ @@ -10,7 +10,8 @@ dist_man8_MANS = \ manpages/tune.exfat.8 \ manpages/mkfs.exfat.8 \ manpages/exfatlabel.8 \ - manpages/dump.exfat.8 + manpages/dump.exfat.8 \ + manpages/exfat2img.8 # other stuff EXTRA_DIST = \ @@ -22,4 +23,5 @@ EXTRA_DIST = \ fsck/Android.bp \ label/Android.bp \ dump/Android.bp \ + exfat2img/Android.bp \ README.md diff --git a/Makefile.in b/Makefile.in index 5149426..01a27d7 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.16.1 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2018 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -168,7 +168,7 @@ am__recursive_targets = \ $(RECURSIVE_CLEAN_TARGETS) \ $(am__extra_recursive_targets) AM_RECURSIVE_TARGETS = $(am__recursive_targets:-recursive=) TAGS CTAGS \ - cscope distdir distdir-am dist dist-all distcheck + cscope distdir dist dist-all distcheck am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) \ $(LISP)config.h.in # Read a list of newline-separated strings from the standard input, @@ -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/config.guess build-aux/config.sub build-aux/depcomp \ build-aux/install-sh build-aux/ltmain.sh build-aux/missing DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) distdir = $(PACKAGE)-$(VERSION) @@ -359,7 +359,7 @@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ ACLOCAL_AMFLAGS = -I m4 -SUBDIRS = lib mkfs fsck tune label dump +SUBDIRS = lib mkfs fsck tune label dump exfat2img # manpages dist_man8_MANS = \ @@ -367,7 +367,8 @@ dist_man8_MANS = \ manpages/tune.exfat.8 \ manpages/mkfs.exfat.8 \ manpages/exfatlabel.8 \ - manpages/dump.exfat.8 + manpages/dump.exfat.8 \ + manpages/exfat2img.8 # other stuff @@ -380,6 +381,7 @@ EXTRA_DIST = \ fsck/Android.bp \ label/Android.bp \ dump/Android.bp \ + exfat2img/Android.bp \ README.md all: config.h @@ -407,8 +409,8 @@ Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status echo ' $(SHELL) ./config.status'; \ $(SHELL) ./config.status;; \ *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__maybe_remake_depfiles);; \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $@ $(am__depfiles_maybe);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) @@ -591,10 +593,7 @@ distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags -rm -f cscope.out cscope.in.out cscope.po.out cscope.files -distdir: $(BUILT_SOURCES) - $(MAKE) $(AM_MAKEFLAGS) distdir-am - -distdir-am: $(DISTFILES) +distdir: $(DISTFILES) $(am__remove_distdir) test -d "$(distdir)" || mkdir "$(distdir)" @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ diff --git a/NEWS b/NEWS index e2d53dc..055343a 100644 --- a/NEWS +++ b/NEWS @@ -1,3 +1,63 @@ +exfatprogs 1.2.2 - released 2023-10-26 +====================================== + +CHANGES : + * exfat2img: Allow dumps for read-only devices. + * fsck.exfat: Revert Repairing zero size directory. + +NEW FEATURES : + * fsck.exfat: Repair duplicated filename. + * mkfs.exfat: Add the option "q" to print only error messages. + * mkfs.exfat: Add the option "U" to set volume GUID. + * tune.exfat: Add the option "U" / "-u" to set or print volume GUID. + +BUG FIXES: + * fsck.exfat: Fix some out-of-bounds memory accesses. + * fsck.exfat: Change not to delete volume GUID directory entry. + +exfatprogs 1.2.1 - released 2023-05-17 +====================================== + +CHANGES : + * fsck.exfat: Repair zero size directory. + * fsck.exfat: Four small clean-ups. + +exfatprogs 1.2.0 - released 2022-10-28 +====================================== + +CHANGES : + * fsck.exfat: Keep traveling files even if there is a corrupted +directory entry set. + * fsck.exfat: Introduce the option "b" to recover a boot sector even +if an exFAT filesystem is not found. + * fsck.exfat: Introduce the option "s" to create files in +"/LOST+FOUND", which have clusters allocated but was not belonged to +any files. + * fsck.exfat: Rename '.' and '..' entry name to the one user want. + +NEW FEATURES : + * fsck.exfat: Repair corruptions of an exFAT filesystem. Please refer +to fsck.exfat manpage to see what kind of corruptions can be repaired. + * exfat2img: Dump metadata of an exFAT filesystem. Please refer to +exfat2img manpage to see how to use it. + +BUG FIXES: + * fsck.exfat: Fix an infinite loop while traveling files. + * tune.exfat: Fix bitmap entry corruption when adding new volume lablel. + +exfatprogs 1.1.3 - released 2021-11-11 +====================================== + +CHANGES : + * mkfs.exfat: ensure that the cluster size is greater than or + equal than the sector size. + * mkfs.exfat: replace lseek() + write() with pwrite(). + +BUG FIXES : + * mkfs.exfat: prevent an integer overflow when computing the FAT + length. + * fsck.exfat: fix a double free memory error. + exfatprogs 1.1.2 - released 2021-05-20 ====================================== diff --git a/README.md b/README.md index 4db98fd..d640c06 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,10 @@ Usage example: Usage example: 1. check the consistency. fsck.exfat /dev/sda1 - 2. repair and fix.(preparing) + 2. repair a corrupted device and create files in /LOST+FOUND, which have clusters allocated but not belonged to any files when reparing the device. + fsck.exfat -p -s /dev/sda1 + 3. repair a corrupted device in the same way above, but answering yes to all questions. + fsck.exfat -y -s /dev/sda1 - tune.exfat: Adjust tunable filesystem parameters on an exFAT filesystem @@ -87,6 +90,12 @@ Usage example: Usage example: dump.exfat /dev/sda1 +- exfat2img: + Dump metadata of an exFAT filesystem + +Usage example: + exfat2img -o sda1.dump /dev/sda1 + ``` ## Benchmarks @@ -116,4 +125,5 @@ If you have any issues, please create [issues][1] or contact to [Namjae Jeon](ma ## 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`). +* Make sure you add 'Signed-Off' information to your commits (e.g. `git commit --signoff`). +* Please check your code contribution using kernel dev-tool script [checkpatch](https://docs.kernel.org/dev-tools/checkpatch.html). diff --git a/aclocal.m4 b/aclocal.m4 index 9f28080..3610979 100644 --- a/aclocal.m4 +++ b/aclocal.m4 @@ -1,6 +1,6 @@ -# generated automatically by aclocal 1.16.1 -*- Autoconf -*- +# generated automatically by aclocal 1.15.1 -*- Autoconf -*- -# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# Copyright (C) 1996-2017 Free Software Foundation, Inc. # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -20,7 +20,7 @@ You have another version of autoconf. It may work, but is not guaranteed to. If you have problems, you may need to regenerate the build system entirely. To do so, use the procedure documented by the package, typically 'autoreconf'.])]) -# Copyright (C) 2002-2018 Free Software Foundation, Inc. +# Copyright (C) 2002-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -32,10 +32,10 @@ To do so, use the procedure documented by the package, typically 'autoreconf'.]) # generated from the m4 files accompanying Automake X.Y. # (This private macro should not be called outside this file.) AC_DEFUN([AM_AUTOMAKE_VERSION], -[am__api_version='1.16' +[am__api_version='1.15' dnl Some users find AM_AUTOMAKE_VERSION and mistake it for a way to dnl require some minimum version. Point them to the right macro. -m4_if([$1], [1.16.1], [], +m4_if([$1], [1.15.1], [], [AC_FATAL([Do not call $0, use AM_INIT_AUTOMAKE([$1]).])])dnl ]) @@ -51,14 +51,14 @@ m4_define([_AM_AUTOCONF_VERSION], []) # Call AM_AUTOMAKE_VERSION and AM_AUTOMAKE_VERSION so they can be traced. # This function is AC_REQUIREd by AM_INIT_AUTOMAKE. AC_DEFUN([AM_SET_CURRENT_AUTOMAKE_VERSION], -[AM_AUTOMAKE_VERSION([1.16.1])dnl +[AM_AUTOMAKE_VERSION([1.15.1])dnl m4_ifndef([AC_AUTOCONF_VERSION], [m4_copy([m4_PACKAGE_VERSION], [AC_AUTOCONF_VERSION])])dnl _AM_AUTOCONF_VERSION(m4_defn([AC_AUTOCONF_VERSION]))]) # AM_AUX_DIR_EXPAND -*- Autoconf -*- -# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# Copyright (C) 2001-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -110,7 +110,7 @@ am_aux_dir=`cd "$ac_aux_dir" && pwd` # AM_CONDITIONAL -*- Autoconf -*- -# Copyright (C) 1997-2018 Free Software Foundation, Inc. +# Copyright (C) 1997-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -141,7 +141,7 @@ AC_CONFIG_COMMANDS_PRE( Usually this means the macro was only invoked conditionally.]]) fi])]) -# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Copyright (C) 1999-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -332,12 +332,13 @@ _AM_SUBST_NOTMAKE([am__nodep])dnl # Generate code to set up dependency tracking. -*- Autoconf -*- -# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Copyright (C) 1999-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, # with or without modifications, as long as this notice is preserved. + # _AM_OUTPUT_DEPENDENCY_COMMANDS # ------------------------------ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], @@ -345,41 +346,49 @@ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], # Older Autoconf quotes --file arguments for eval, but not when files # are listed without --file. Let's play safe and only enable the eval # if we detect the quoting. - # TODO: see whether this extra hack can be removed once we start - # requiring Autoconf 2.70 or later. - AS_CASE([$CONFIG_FILES], - [*\'*], [eval set x "$CONFIG_FILES"], - [*], [set x $CONFIG_FILES]) + case $CONFIG_FILES in + *\'*) eval set x "$CONFIG_FILES" ;; + *) set x $CONFIG_FILES ;; + esac shift - # Used to flag and report bootstrapping failures. - am_rc=0 - for am_mf + for mf do # Strip MF so we end up with the name of the file. - am_mf=`AS_ECHO(["$am_mf"]) | sed -e 's/:.*$//'` - # Check whether this is an Automake generated Makefile which includes - # dependency-tracking related rules and includes. - # Grep'ing the whole file directly is not great: AIX grep has a line + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named 'Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line # limit of 2048, but all sed's we know have understand at least 4000. - sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ - || continue - am_dirpart=`AS_DIRNAME(["$am_mf"])` - am_filepart=`AS_BASENAME(["$am_mf"])` - AM_RUN_LOG([cd "$am_dirpart" \ - && sed -e '/# am--include-marker/d' "$am_filepart" \ - | $MAKE -f - am--depfiles]) || am_rc=$? + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`AS_DIRNAME("$mf")` + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running 'make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "$am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`AS_DIRNAME(["$file"])` + AS_MKDIR_P([$dirpart/$fdir]) + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done done - if test $am_rc -ne 0; then - AC_MSG_FAILURE([Something went wrong bootstrapping makefile fragments - for automatic dependency tracking. Try re-running configure with the - '--disable-dependency-tracking' option to at least be able to build - the package (albeit without support for automatic dependency tracking).]) - fi - AS_UNSET([am_dirpart]) - AS_UNSET([am_filepart]) - AS_UNSET([am_mf]) - AS_UNSET([am_rc]) - rm -f conftest-deps.mk } ])# _AM_OUTPUT_DEPENDENCY_COMMANDS @@ -388,17 +397,18 @@ AC_DEFUN([_AM_OUTPUT_DEPENDENCY_COMMANDS], # ----------------------------- # This macro should only be invoked once -- use via AC_REQUIRE. # -# This code is only required when automatic dependency tracking is enabled. -# This creates each '.Po' and '.Plo' makefile fragment that we'll need in -# order to bootstrap the dependency handling code. +# This code is only required when automatic dependency tracking +# is enabled. FIXME. This creates each '.P' file that we will +# need in order to bootstrap the dependency handling code. AC_DEFUN([AM_OUTPUT_DEPENDENCY_COMMANDS], [AC_CONFIG_COMMANDS([depfiles], [test x"$AMDEP_TRUE" != x"" || _AM_OUTPUT_DEPENDENCY_COMMANDS], - [AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}"])]) + [AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir"]) +]) # Do all the work for Automake. -*- Autoconf -*- -# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# Copyright (C) 1996-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -485,8 +495,8 @@ AC_REQUIRE([AM_PROG_INSTALL_STRIP])dnl AC_REQUIRE([AC_PROG_MKDIR_P])dnl # For better backward compatibility. To be removed once Automake 1.9.x # dies out for good. For more background, see: -# -# +# +# AC_SUBST([mkdir_p], ['$(MKDIR_P)']) # We need awk for the "check" target (and possibly the TAP driver). The # system "awk" is bad on some platforms. @@ -553,7 +563,7 @@ END Aborting the configuration process, to ensure you take notice of the issue. You can download and install GNU coreutils to get an 'rm' implementation -that behaves properly: . +that behaves properly: . If you want to complete the configuration process using your problematic 'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM @@ -595,7 +605,7 @@ for _am_header in $config_headers :; do done echo "timestamp for $_am_arg" >`AS_DIRNAME(["$_am_arg"])`/stamp-h[]$_am_stamp_count]) -# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# Copyright (C) 2001-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -616,7 +626,7 @@ if test x"${install_sh+set}" != xset; then fi AC_SUBST([install_sh])]) -# Copyright (C) 2003-2018 Free Software Foundation, Inc. +# Copyright (C) 2003-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -637,7 +647,7 @@ AC_SUBST([am__leading_dot])]) # Check to see how 'make' treats includes. -*- Autoconf -*- -# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# Copyright (C) 2001-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -645,42 +655,49 @@ AC_SUBST([am__leading_dot])]) # AM_MAKE_INCLUDE() # ----------------- -# Check whether make has an 'include' directive that can support all -# the idioms we need for our automatic dependency tracking code. +# Check to see how make treats includes. AC_DEFUN([AM_MAKE_INCLUDE], -[AC_MSG_CHECKING([whether ${MAKE-make} supports the include directive]) -cat > confinc.mk << 'END' +[am_make=${MAKE-make} +cat > confinc << 'END' am__doit: - @echo this is the am__doit target >confinc.out + @echo this is the am__doit target .PHONY: am__doit END +# If we don't find an include directive, just comment out the code. +AC_MSG_CHECKING([for style of include used by $am_make]) am__include="#" am__quote= -# BSD make does it like this. -echo '.include "confinc.mk" # ignored' > confmf.BSD -# Other make implementations (GNU, Solaris 10, AIX) do it like this. -echo 'include confinc.mk # ignored' > confmf.GNU -_am_result=no -for s in GNU BSD; do - AM_RUN_LOG([${MAKE-make} -f confmf.$s && cat confinc.out]) - AS_CASE([$?:`cat confinc.out 2>/dev/null`], - ['0:this is the am__doit target'], - [AS_CASE([$s], - [BSD], [am__include='.include' am__quote='"'], - [am__include='include' am__quote=''])]) - if test "$am__include" != "#"; then - _am_result="yes ($s style)" - break - fi -done -rm -f confinc.* confmf.* -AC_MSG_RESULT([${_am_result}]) -AC_SUBST([am__include])]) -AC_SUBST([am__quote])]) +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# Ignore all kinds of additional output from 'make'. +case `$am_make -s -f confmf 2> /dev/null` in #( +*the\ am__doit\ target*) + am__include=include + am__quote= + _am_result=GNU + ;; +esac +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + case `$am_make -s -f confmf 2> /dev/null` in #( + *the\ am__doit\ target*) + am__include=.include + am__quote="\"" + _am_result=BSD + ;; + esac +fi +AC_SUBST([am__include]) +AC_SUBST([am__quote]) +AC_MSG_RESULT([$_am_result]) +rm -f confinc confmf +]) # Fake the existence of programs that GNU maintainers use. -*- Autoconf -*- -# Copyright (C) 1997-2018 Free Software Foundation, Inc. +# Copyright (C) 1997-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -719,7 +736,7 @@ fi # Helper functions for option handling. -*- Autoconf -*- -# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# Copyright (C) 2001-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -748,7 +765,7 @@ AC_DEFUN([_AM_SET_OPTIONS], AC_DEFUN([_AM_IF_OPTION], [m4_ifset(_AM_MANGLE_OPTION([$1]), [$2], [$3])]) -# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Copyright (C) 1999-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -795,7 +812,7 @@ AC_LANG_POP([C])]) # For backward compatibility. AC_DEFUN_ONCE([AM_PROG_CC_C_O], [AC_REQUIRE([AC_PROG_CC])]) -# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# Copyright (C) 2001-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -814,7 +831,7 @@ AC_DEFUN([AM_RUN_LOG], # Check to make sure that the build environment is sane. -*- Autoconf -*- -# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# Copyright (C) 1996-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -895,7 +912,7 @@ AC_CONFIG_COMMANDS_PRE( rm -f conftest.file ]) -# Copyright (C) 2009-2018 Free Software Foundation, Inc. +# Copyright (C) 2009-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -955,7 +972,7 @@ AC_SUBST([AM_BACKSLASH])dnl _AM_SUBST_NOTMAKE([AM_BACKSLASH])dnl ]) -# Copyright (C) 2001-2018 Free Software Foundation, Inc. +# Copyright (C) 2001-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -983,7 +1000,7 @@ fi INSTALL_STRIP_PROGRAM="\$(install_sh) -c -s" AC_SUBST([INSTALL_STRIP_PROGRAM])]) -# Copyright (C) 2006-2018 Free Software Foundation, Inc. +# Copyright (C) 2006-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -1002,7 +1019,7 @@ AC_DEFUN([AM_SUBST_NOTMAKE], [_AM_SUBST_NOTMAKE($@)]) # Check how to create a tarball. -*- Autoconf -*- -# Copyright (C) 2004-2018 Free Software Foundation, Inc. +# Copyright (C) 2004-2017 Free Software Foundation, Inc. # # This file is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, diff --git a/build-aux/compile b/build-aux/compile index 99e5052..a85b723 100755 --- a/build-aux/compile +++ b/build-aux/compile @@ -1,9 +1,9 @@ #! /bin/sh # Wrapper for compilers which do not understand '-c -o'. -scriptversion=2018-03-07.03; # UTC +scriptversion=2012-10-14.11; # UTC -# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Copyright (C) 1999-2014 Free Software Foundation, Inc. # Written by Tom Tromey . # # This program is free software; you can redistribute it and/or modify @@ -17,7 +17,7 @@ scriptversion=2018-03-07.03; # UTC # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a @@ -255,8 +255,7 @@ EOF echo "compile $scriptversion" exit $? ;; - cl | *[/\\]cl | cl.exe | *[/\\]cl.exe | \ - icl | *[/\\]icl | icl.exe | *[/\\]icl.exe ) + cl | *[/\\]cl | cl.exe | *[/\\]cl.exe ) func_cl_wrapper "$@" # Doesn't return... ;; esac @@ -340,9 +339,9 @@ exit $ret # Local Variables: # mode: shell-script # sh-indentation: 2 -# eval: (add-hook 'before-save-hook 'time-stamp) +# eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC0" +# time-stamp-time-zone: "UTC" # time-stamp-end: "; # UTC" # End: diff --git a/build-aux/depcomp b/build-aux/depcomp index 65cbf70..b39f98f 100755 --- a/build-aux/depcomp +++ b/build-aux/depcomp @@ -1,9 +1,9 @@ #! /bin/sh # depcomp - compile a program generating dependencies as side-effects -scriptversion=2018-03-07.03; # UTC +scriptversion=2016-01-11.22; # UTC -# Copyright (C) 1999-2018 Free Software Foundation, Inc. +# Copyright (C) 1999-2017 Free Software Foundation, Inc. # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -16,7 +16,7 @@ scriptversion=2018-03-07.03; # UTC # GNU General Public License for more details. # You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a @@ -783,7 +783,7 @@ exit 0 # Local Variables: # mode: shell-script # sh-indentation: 2 -# eval: (add-hook 'before-save-hook 'time-stamp) +# eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" # time-stamp-time-zone: "UTC0" diff --git a/build-aux/install-sh b/build-aux/install-sh index 8175c64..59990a1 100755 --- a/build-aux/install-sh +++ b/build-aux/install-sh @@ -1,7 +1,7 @@ #!/bin/sh # install - install a program, script, or datafile -scriptversion=2018-03-11.20; # UTC +scriptversion=2014-09-12.12; # UTC # This originates from X11R5 (mit/util/scripts/install.sh), which was # later released in X11R6 (xc/config/util/install.sh) with the @@ -271,18 +271,15 @@ do fi dst=$dst_arg - # If destination is a directory, append the input filename. + # If destination is a directory, append the input filename; won't work + # if double slashes aren't ignored. if test -d "$dst"; then if test "$is_target_a_directory" = never; then echo "$0: $dst_arg: Is a directory" >&2 exit 1 fi dstdir=$dst - dstbase=`basename "$src"` - case $dst in - */) dst=$dst$dstbase;; - *) dst=$dst/$dstbase;; - esac + dst=$dstdir/`basename "$src"` dstdir_status=0 else dstdir=`dirname "$dst"` @@ -291,11 +288,6 @@ do fi fi - case $dstdir in - */) dstdirslash=$dstdir;; - *) dstdirslash=$dstdir/;; - esac - obsolete_mkdir_used=false if test $dstdir_status != 0; then @@ -332,16 +324,14 @@ do # is incompatible with FreeBSD 'install' when (umask & 300) != 0. ;; *) - # Note that $RANDOM variable is not portable (e.g. dash); Use it - # here however when possible just to lower collision chance. + # $RANDOM is not portable (e.g. dash); use it when possible to + # lower collision chance tmpdir=${TMPDIR-/tmp}/ins$RANDOM-$$ - trap 'ret=$?; rmdir "$tmpdir/a/b" "$tmpdir/a" "$tmpdir" 2>/dev/null; exit $ret' 0 - # Because "mkdir -p" follows existing symlinks and we likely work - # directly in world-writeable /tmp, make sure that the '$tmpdir' - # directory is successfully created first before we actually test - # 'mkdir -p' feature. + # As "mkdir -p" follows symlinks and we work in /tmp possibly; so + # create the $tmpdir first (and fail if unsuccessful) to make sure + # that nobody tries to guess the $tmpdir name. if (umask $mkdir_umask && $mkdirprog $mkdir_mode "$tmpdir" && exec $mkdirprog $mkdir_mode -p -- "$tmpdir/a/b") >/dev/null 2>&1 @@ -444,8 +434,8 @@ do else # Make a couple of temp file names in the proper directory. - dsttmp=${dstdirslash}_inst.$$_ - rmtmp=${dstdirslash}_rm.$$_ + dsttmp=$dstdir/_inst.$$_ + rmtmp=$dstdir/_rm.$$_ # Trap to clean up those temp files at exit. trap 'ret=$?; rm -f "$dsttmp" "$rmtmp" && exit $ret' 0 @@ -510,9 +500,9 @@ do done # Local variables: -# eval: (add-hook 'before-save-hook 'time-stamp) +# eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC0" +# time-stamp-time-zone: "UTC" # time-stamp-end: "; # UTC" # End: diff --git a/build-aux/ltmain.sh b/build-aux/ltmain.sh index 0cb7f90..a736cf9 100644 --- a/build-aux/ltmain.sh +++ b/build-aux/ltmain.sh @@ -31,7 +31,7 @@ PROGRAM=libtool PACKAGE=libtool -VERSION="2.4.6 Debian-2.4.6-14" +VERSION="2.4.6 Debian-2.4.6-2" package_revision=2.4.6 @@ -387,7 +387,7 @@ EXIT_SKIP=77 # $? = 77 is used to indicate a skipped test to automake. # putting '$debug_cmd' at the start of all your functions, you can get # bash to show function call trace with: # -# debug_cmd='echo "${FUNCNAME[0]} $*" >&2' bash your-script-name +# debug_cmd='eval echo "${FUNCNAME[0]} $*" >&2' bash your-script-name debug_cmd=${debug_cmd-":"} exit_cmd=: @@ -1370,7 +1370,7 @@ func_lt_ver () #! /bin/sh # Set a version string for this script. -scriptversion=2015-10-07.11; # UTC +scriptversion=2014-01-07.03; # UTC # A portable, pluggable option parser for Bourne shell. # Written by Gary V. Vaughan, 2010 @@ -1530,8 +1530,6 @@ func_run_hooks () { $debug_cmd - _G_rc_run_hooks=false - case " $hookable_fns " in *" $1 "*) ;; *) func_fatal_error "'$1' does not support hook funcions.n" ;; @@ -1540,16 +1538,16 @@ func_run_hooks () eval _G_hook_fns=\$$1_hooks; shift for _G_hook in $_G_hook_fns; do - if eval $_G_hook '"$@"'; then - # store returned options list back into positional - # parameters for next 'cmd' execution. - eval _G_hook_result=\$${_G_hook}_result - eval set dummy "$_G_hook_result"; shift - _G_rc_run_hooks=: - fi + eval $_G_hook '"$@"' + + # store returned options list back into positional + # parameters for next 'cmd' execution. + eval _G_hook_result=\$${_G_hook}_result + eval set dummy "$_G_hook_result"; shift done - $_G_rc_run_hooks && func_run_hooks_result=$_G_hook_result + func_quote_for_eval ${1+"$@"} + func_run_hooks_result=$func_quote_for_eval_result } @@ -1559,16 +1557,10 @@ func_run_hooks () ## --------------- ## # In order to add your own option parsing hooks, you must accept the -# full positional parameter list in your hook function, you may remove/edit -# any options that you action, and then pass back the remaining unprocessed +# full positional parameter list in your hook function, remove any +# options that you action, and then pass back the remaining unprocessed # options in '_result', escaped suitably for -# 'eval'. In this case you also must return $EXIT_SUCCESS to let the -# hook's caller know that it should pay attention to -# '_result'. Returning $EXIT_FAILURE signalizes that -# arguments are left untouched by the hook and therefore caller will ignore the -# result variable. -# -# Like this: +# 'eval'. Like this: # # my_options_prep () # { @@ -1578,11 +1570,9 @@ func_run_hooks () # usage_message=$usage_message' # -s, --silent don'\''t print informational messages # ' -# # No change in '$@' (ignored completely by this hook). There is -# # no need to do the equivalent (but slower) action: -# # func_quote_for_eval ${1+"$@"} -# # my_options_prep_result=$func_quote_for_eval_result -# false +# +# func_quote_for_eval ${1+"$@"} +# my_options_prep_result=$func_quote_for_eval_result # } # func_add_hook func_options_prep my_options_prep # @@ -1591,37 +1581,25 @@ func_run_hooks () # { # $debug_cmd # -# args_changed=false -# # # Note that for efficiency, we parse as many options as we can # # recognise in a loop before passing the remainder back to the # # caller on the first unrecognised argument we encounter. # while test $# -gt 0; do # opt=$1; shift # case $opt in -# --silent|-s) opt_silent=: -# args_changed=: -# ;; +# --silent|-s) opt_silent=: ;; # # Separate non-argument short options: # -s*) func_split_short_opt "$_G_opt" # set dummy "$func_split_short_opt_name" \ # "-$func_split_short_opt_arg" ${1+"$@"} # shift -# args_changed=: # ;; -# *) # Make sure the first unrecognised option "$_G_opt" -# # is added back to "$@", we could need that later -# # if $args_changed is true. -# set dummy "$_G_opt" ${1+"$@"}; shift; break ;; +# *) set dummy "$_G_opt" "$*"; shift; break ;; # esac # done # -# if $args_changed; then -# func_quote_for_eval ${1+"$@"} -# my_silent_option_result=$func_quote_for_eval_result -# fi -# -# $args_changed +# func_quote_for_eval ${1+"$@"} +# my_silent_option_result=$func_quote_for_eval_result # } # func_add_hook func_parse_options my_silent_option # @@ -1633,32 +1611,16 @@ func_run_hooks () # $opt_silent && $opt_verbose && func_fatal_help "\ # '--silent' and '--verbose' options are mutually exclusive." # -# false +# func_quote_for_eval ${1+"$@"} +# my_option_validation_result=$func_quote_for_eval_result # } # func_add_hook func_validate_options my_option_validation # -# You'll also need to manually amend $usage_message to reflect the extra +# You'll alse need to manually amend $usage_message to reflect the extra # options you parse. It's preferable to append if you can, so that # multiple option parsing hooks can be added safely. -# func_options_finish [ARG]... -# ---------------------------- -# Finishing the option parse loop (call 'func_options' hooks ATM). -func_options_finish () -{ - $debug_cmd - - _G_func_options_finish_exit=false - if func_run_hooks func_options ${1+"$@"}; then - func_options_finish_result=$func_run_hooks_result - _G_func_options_finish_exit=: - fi - - $_G_func_options_finish_exit -} - - # func_options [ARG]... # --------------------- # All the functions called inside func_options are hookable. See the @@ -1668,28 +1630,17 @@ func_options () { $debug_cmd - _G_rc_options=false + func_options_prep ${1+"$@"} + eval func_parse_options \ + ${func_options_prep_result+"$func_options_prep_result"} + eval func_validate_options \ + ${func_parse_options_result+"$func_parse_options_result"} - for my_func in options_prep parse_options validate_options options_finish - do - if eval func_$my_func '${1+"$@"}'; then - eval _G_res_var='$'"func_${my_func}_result" - eval set dummy "$_G_res_var" ; shift - _G_rc_options=: - fi - done - - # Save modified positional parameters for caller. As a top-level - # options-parser function we always need to set the 'func_options_result' - # variable (regardless the $_G_rc_options value). - if $_G_rc_options; then - func_options_result=$_G_res_var - else - func_quote_for_eval ${1+"$@"} - func_options_result=$func_quote_for_eval_result - fi + eval func_run_hooks func_options \ + ${func_validate_options_result+"$func_validate_options_result"} - $_G_rc_options + # save modified positional parameters for caller + func_options_result=$func_run_hooks_result } @@ -1698,9 +1649,9 @@ func_options () # All initialisations required before starting the option parse loop. # Note that when calling hook functions, we pass through the list of # positional parameters. If a hook function modifies that list, and -# needs to propagate that back to rest of this script, then the complete +# needs to propogate that back to rest of this script, then the complete # modified list must be put in 'func_run_hooks_result' before -# returning $EXIT_SUCCESS (otherwise $EXIT_FAILURE is returned). +# returning. func_hookable func_options_prep func_options_prep () { @@ -1710,14 +1661,10 @@ func_options_prep () opt_verbose=false opt_warning_types= - _G_rc_options_prep=false - if func_run_hooks func_options_prep ${1+"$@"}; then - _G_rc_options_prep=: - # save modified positional parameters for caller - func_options_prep_result=$func_run_hooks_result - fi + func_run_hooks func_options_prep ${1+"$@"} - $_G_rc_options_prep + # save modified positional parameters for caller + func_options_prep_result=$func_run_hooks_result } @@ -1731,20 +1678,18 @@ func_parse_options () func_parse_options_result= - _G_rc_parse_options=false # this just eases exit handling while test $# -gt 0; do # Defer to hook functions for initial option parsing, so they # get priority in the event of reusing an option name. - if func_run_hooks func_parse_options ${1+"$@"}; then - eval set dummy "$func_run_hooks_result"; shift - _G_rc_parse_options=: - fi + func_run_hooks func_parse_options ${1+"$@"} + + # Adjust func_parse_options positional parameters to match + eval set dummy "$func_run_hooks_result"; shift # Break out of the loop if we already parsed every option. test $# -gt 0 || break - _G_match_parse_options=: _G_opt=$1 shift case $_G_opt in @@ -1759,10 +1704,7 @@ func_parse_options () ;; --warnings|--warning|-W) - if test $# = 0 && func_missing_arg $_G_opt; then - _G_rc_parse_options=: - break - fi + test $# = 0 && func_missing_arg $_G_opt && break case " $warning_categories $1" in *" $1 "*) # trailing space prevents matching last $1 above @@ -1815,25 +1757,15 @@ func_parse_options () shift ;; - --) _G_rc_parse_options=: ; break ;; + --) break ;; -*) func_fatal_help "unrecognised option: '$_G_opt'" ;; - *) set dummy "$_G_opt" ${1+"$@"}; shift - _G_match_parse_options=false - break - ;; + *) set dummy "$_G_opt" ${1+"$@"}; shift; break ;; esac - - $_G_match_parse_options && _G_rc_parse_options=: done - - if $_G_rc_parse_options; then - # save modified positional parameters for caller - func_quote_for_eval ${1+"$@"} - func_parse_options_result=$func_quote_for_eval_result - fi - - $_G_rc_parse_options + # save modified positional parameters for caller + func_quote_for_eval ${1+"$@"} + func_parse_options_result=$func_quote_for_eval_result } @@ -1846,21 +1778,16 @@ func_validate_options () { $debug_cmd - _G_rc_validate_options=false - # Display all warnings if -W was not given. test -n "$opt_warning_types" || opt_warning_types=" $warning_categories" - if func_run_hooks func_validate_options ${1+"$@"}; then - # save modified positional parameters for caller - func_validate_options_result=$func_run_hooks_result - _G_rc_validate_options=: - fi + func_run_hooks func_validate_options ${1+"$@"} # Bail if the options were screwed! $exit_cmd $EXIT_FAILURE - $_G_rc_validate_options + # save modified positional parameters for caller + func_validate_options_result=$func_run_hooks_result } @@ -2141,7 +2068,7 @@ include the following information: compiler: $LTCC compiler flags: $LTCFLAGS linker: $LD (gnu? $with_gnu_ld) - version: $progname $scriptversion Debian-2.4.6-14 + version: $progname $scriptversion Debian-2.4.6-2 automake: `($AUTOMAKE --version) 2>/dev/null |$SED 1q` autoconf: `($AUTOCONF --version) 2>/dev/null |$SED 1q` @@ -2343,8 +2270,6 @@ libtool_options_prep () nonopt= preserve_args= - _G_rc_lt_options_prep=: - # Shorthand for --mode=foo, only valid as the first argument case $1 in clean|clea|cle|cl) @@ -2368,18 +2293,11 @@ libtool_options_prep () uninstall|uninstal|uninsta|uninst|unins|unin|uni|un|u) shift; set dummy --mode uninstall ${1+"$@"}; shift ;; - *) - _G_rc_lt_options_prep=false - ;; esac - if $_G_rc_lt_options_prep; then - # Pass back the list of options. - func_quote_for_eval ${1+"$@"} - libtool_options_prep_result=$func_quote_for_eval_result - fi - - $_G_rc_lt_options_prep + # Pass back the list of options. + func_quote_for_eval ${1+"$@"} + libtool_options_prep_result=$func_quote_for_eval_result } func_add_hook func_options_prep libtool_options_prep @@ -2391,12 +2309,9 @@ libtool_parse_options () { $debug_cmd - _G_rc_lt_parse_options=false - # Perform our own loop to consume as many options as possible in # each iteration. while test $# -gt 0; do - _G_match_lt_parse_options=: _G_opt=$1 shift case $_G_opt in @@ -2471,22 +2386,15 @@ libtool_parse_options () func_append preserve_args " $_G_opt" ;; - # An option not handled by this hook function: - *) set dummy "$_G_opt" ${1+"$@"} ; shift - _G_match_lt_parse_options=false - break - ;; + # An option not handled by this hook function: + *) set dummy "$_G_opt" ${1+"$@"}; shift; break ;; esac - $_G_match_lt_parse_options && _G_rc_lt_parse_options=: done - if $_G_rc_lt_parse_options; then - # save modified positional parameters for caller - func_quote_for_eval ${1+"$@"} - libtool_parse_options_result=$func_quote_for_eval_result - fi - $_G_rc_lt_parse_options + # save modified positional parameters for caller + func_quote_for_eval ${1+"$@"} + libtool_parse_options_result=$func_quote_for_eval_result } func_add_hook func_parse_options libtool_parse_options @@ -7367,13 +7275,10 @@ func_mode_link () # -specs=* GCC specs files # -stdlib=* select c++ std lib with clang # -fsanitize=* Clang/GCC memory and address sanitizer - # -fuse-ld=* Linker select flags for GCC - # -static-* direct GCC to link specific libraries statically - # -fcilkplus Cilk Plus language extension features for C/C++ -64|-mips[0-9]|-r[0-9][0-9]*|-xarch=*|-xtarget=*|+DA*|+DD*|-q*|-m*| \ -t[45]*|-txscale*|-p|-pg|--coverage|-fprofile-*|-F*|@*|-tp=*|--sysroot=*| \ -O*|-g*|-flto*|-fwhopr*|-fuse-linker-plugin|-fstack-protector*|-stdlib=*| \ - -specs=*|-fsanitize=*|-fuse-ld=*|-static-*|-fcilkplus) + -specs=*|-fsanitize=*) func_quote_for_eval "$arg" arg=$func_quote_for_eval_result func_append compile_command " $arg" diff --git a/build-aux/missing b/build-aux/missing index 625aeb1..f62bbae 100755 --- a/build-aux/missing +++ b/build-aux/missing @@ -1,9 +1,9 @@ #! /bin/sh # Common wrapper for a few potentially missing GNU programs. -scriptversion=2018-03-07.03; # UTC +scriptversion=2013-10-28.13; # UTC -# Copyright (C) 1996-2018 Free Software Foundation, Inc. +# Copyright (C) 1996-2014 Free Software Foundation, Inc. # Originally written by Fran,cois Pinard , 1996. # This program is free software; you can redistribute it and/or modify @@ -17,7 +17,7 @@ scriptversion=2018-03-07.03; # UTC # GNU General Public License for more details. # You should have received a copy of the GNU General Public License -# along with this program. If not, see . +# along with this program. If not, see . # As a special exception to the GNU General Public License, if you # distribute this file as part of a program that contains a @@ -101,9 +101,9 @@ else exit $st fi -perl_URL=https://www.perl.org/ -flex_URL=https://github.com/westes/flex -gnu_software_URL=https://www.gnu.org/software +perl_URL=http://www.perl.org/ +flex_URL=http://flex.sourceforge.net/ +gnu_software_URL=http://www.gnu.org/software program_details () { @@ -207,9 +207,9 @@ give_advice "$1" | sed -e '1s/^/WARNING: /' \ exit $st # Local variables: -# eval: (add-hook 'before-save-hook 'time-stamp) +# eval: (add-hook 'write-file-hooks 'time-stamp) # time-stamp-start: "scriptversion=" # time-stamp-format: "%:y-%02m-%02d.%02H" -# time-stamp-time-zone: "UTC0" +# time-stamp-time-zone: "UTC" # time-stamp-end: "; # UTC" # End: diff --git a/configure b/configure index c590114..662b6b8 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.1.2. +# Generated by GNU Autoconf 2.69 for exfatprogs 1.2.2. # # Report bugs to . # @@ -590,8 +590,8 @@ MAKEFLAGS= # Identity of this package. PACKAGE_NAME='exfatprogs' PACKAGE_TARNAME='exfatprogs' -PACKAGE_VERSION='1.1.2' -PACKAGE_STRING='exfatprogs 1.1.2' +PACKAGE_VERSION='1.2.2' +PACKAGE_STRING='exfatprogs 1.2.2' PACKAGE_BUGREPORT='linkinjeon@kernel.org' PACKAGE_URL='https://github.com/exfatprogs/exfatprogs' @@ -674,6 +674,7 @@ am__nodep AMDEPBACKSLASH AMDEP_FALSE AMDEP_TRUE +am__quote am__include DEPDIR OBJEXT @@ -748,8 +749,7 @@ PACKAGE_VERSION PACKAGE_TARNAME PACKAGE_NAME PATH_SEPARATOR -SHELL -am__quote' +SHELL' ac_subst_files='' ac_user_opts=' enable_option_checking @@ -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.1.2 to adapt to many kinds of systems. +\`configure' configures exfatprogs 1.2.2 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.1.2:";; + short | recursive ) echo "Configuration of exfatprogs 1.2.2:";; 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.1.2 +exfatprogs configure 1.2.2 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.1.2, which was +It was created by exfatprogs $as_me 1.2.2, which was generated by GNU Autoconf 2.69. Invocation command line was $ $0 $@ @@ -2168,7 +2168,7 @@ ac_config_sub="$SHELL $ac_aux_dir/config.sub" # Please don't use this var. ac_configure="$SHELL $ac_aux_dir/configure" # Please don't use this var. -am__api_version='1.16' +am__api_version='1.15' # Find a good install program. We prefer a C program (faster), # so one script is as good as another. But avoid the broken or @@ -2654,7 +2654,7 @@ fi # Define the identity of the package. PACKAGE='exfatprogs' - VERSION='1.1.2' + VERSION='1.2.2' cat >>confdefs.h <<_ACEOF @@ -2684,8 +2684,8 @@ MAKEINFO=${MAKEINFO-"${am_missing_run}makeinfo"} # For better backward compatibility. To be removed once Automake 1.9.x # dies out for good. For more background, see: -# -# +# +# mkdir_p='$(MKDIR_P)' # We need awk for the "check" target (and possibly the TAP driver). The @@ -2821,7 +2821,7 @@ END Aborting the configuration process, to ensure you take notice of the issue. You can download and install GNU coreutils to get an 'rm' implementation -that behaves properly: . +that behaves properly: . If you want to complete the configuration process using your problematic 'rm' anyway, export the environment variable ACCEPT_INFERIOR_RM_PROGRAM @@ -3690,45 +3690,45 @@ DEPDIR="${am__leading_dot}deps" ac_config_commands="$ac_config_commands depfiles" -{ $as_echo "$as_me:${as_lineno-$LINENO}: checking whether ${MAKE-make} supports the include directive" >&5 -$as_echo_n "checking whether ${MAKE-make} supports the include directive... " >&6; } -cat > confinc.mk << 'END' + +am_make=${MAKE-make} +cat > confinc << 'END' am__doit: - @echo this is the am__doit target >confinc.out + @echo this is the am__doit target .PHONY: am__doit END +# If we don't find an include directive, just comment out the code. +{ $as_echo "$as_me:${as_lineno-$LINENO}: checking for style of include used by $am_make" >&5 +$as_echo_n "checking for style of include used by $am_make... " >&6; } am__include="#" am__quote= -# BSD make does it like this. -echo '.include "confinc.mk" # ignored' > confmf.BSD -# Other make implementations (GNU, Solaris 10, AIX) do it like this. -echo 'include confinc.mk # ignored' > confmf.GNU -_am_result=no -for s in GNU BSD; do - { echo "$as_me:$LINENO: ${MAKE-make} -f confmf.$s && cat confinc.out" >&5 - (${MAKE-make} -f confmf.$s && cat confinc.out) >&5 2>&5 - ac_status=$? - echo "$as_me:$LINENO: \$? = $ac_status" >&5 - (exit $ac_status); } - case $?:`cat confinc.out 2>/dev/null` in #( - '0:this is the am__doit target') : - case $s in #( - BSD) : - am__include='.include' am__quote='"' ;; #( - *) : - am__include='include' am__quote='' ;; -esac ;; #( - *) : - ;; +_am_result=none +# First try GNU make style include. +echo "include confinc" > confmf +# Ignore all kinds of additional output from 'make'. +case `$am_make -s -f confmf 2> /dev/null` in #( +*the\ am__doit\ target*) + am__include=include + am__quote= + _am_result=GNU + ;; esac - if test "$am__include" != "#"; then - _am_result="yes ($s style)" - break - fi -done -rm -f confinc.* confmf.* -{ $as_echo "$as_me:${as_lineno-$LINENO}: result: ${_am_result}" >&5 -$as_echo "${_am_result}" >&6; } +# Now try BSD make style include. +if test "$am__include" = "#"; then + echo '.include "confinc"' > confmf + case `$am_make -s -f confmf 2> /dev/null` in #( + *the\ am__doit\ target*) + am__include=.include + am__quote="\"" + _am_result=BSD + ;; + esac +fi + + +{ $as_echo "$as_me:${as_lineno-$LINENO}: result: $_am_result" >&5 +$as_echo "$_am_result" >&6; } +rm -f confinc confmf # Check whether --enable-dependency-tracking was given. if test "${enable_dependency_tracking+set}" = set; then : @@ -5881,7 +5881,7 @@ esac fi : ${AR=ar} -: ${AR_FLAGS=cr} +: ${AR_FLAGS=cru} @@ -6382,8 +6382,11 @@ _LT_EOF test $ac_status = 0; }; then # Now try to grab the symbols. nlist=conftest.nm - $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&5 - if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&5 && test -s "$nlist"; then + if { { eval echo "\"\$as_me\":${as_lineno-$LINENO}: \"$NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist\""; } >&5 + (eval $NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) 2>&5 + ac_status=$? + $as_echo "$as_me:${as_lineno-$LINENO}: \$? = $ac_status" >&5 + test $ac_status = 0; } && test -s "$nlist"; then # Try sorting and uniquifying the output. if sort "$nlist" | uniq > "$nlist"T; then mv -f "$nlist"T "$nlist" @@ -7602,8 +7605,8 @@ int forced_loaded() { return 2;} _LT_EOF echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&5 $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&5 - echo "$AR cr libconftest.a conftest.o" >&5 - $AR cr libconftest.a conftest.o 2>&5 + echo "$AR cru libconftest.a conftest.o" >&5 + $AR cru libconftest.a conftest.o 2>&5 echo "$RANLIB libconftest.a" >&5 $RANLIB libconftest.a 2>&5 cat > conftest.c << _LT_EOF @@ -8732,12 +8735,6 @@ lt_prog_compiler_static= lt_prog_compiler_pic='-KPIC' lt_prog_compiler_static='-static' ;; - # flang / f18. f95 an alias for gfortran or flang on Debian - flang* | f18* | f95*) - lt_prog_compiler_wl='-Wl,' - lt_prog_compiler_pic='-fPIC' - lt_prog_compiler_static='-static' - ;; # icc used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. icc* | ifort*) @@ -12722,7 +12719,7 @@ $as_echo "#define AC_APPLE_UNIVERSAL_BUILD 1" >>confdefs.h esac -ac_config_files="$ac_config_files Makefile lib/Makefile mkfs/Makefile fsck/Makefile tune/Makefile label/Makefile dump/Makefile" +ac_config_files="$ac_config_files Makefile lib/Makefile mkfs/Makefile fsck/Makefile tune/Makefile label/Makefile dump/Makefile exfat2img/Makefile" cat >confcache <<\_ACEOF @@ -13256,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.1.2, which was +This file was extended by exfatprogs $as_me 1.2.2, which was generated by GNU Autoconf 2.69. Invocation command line was CONFIG_FILES = $CONFIG_FILES @@ -13323,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.1.2 +exfatprogs config.status 1.2.2 configured by $0, generated by GNU Autoconf 2.69, with options \\"\$ac_cs_config\\" @@ -13442,7 +13439,7 @@ cat >>$CONFIG_STATUS <<_ACEOF || ac_write_fail=1 # # INIT-COMMANDS # -AMDEP_TRUE="$AMDEP_TRUE" MAKE="${MAKE-make}" +AMDEP_TRUE="$AMDEP_TRUE" ac_aux_dir="$ac_aux_dir" # The HP-UX ksh and POSIX shell print the target directory to stdout @@ -13745,6 +13742,7 @@ do "tune/Makefile") CONFIG_FILES="$CONFIG_FILES tune/Makefile" ;; "label/Makefile") CONFIG_FILES="$CONFIG_FILES label/Makefile" ;; "dump/Makefile") CONFIG_FILES="$CONFIG_FILES dump/Makefile" ;; + "exfat2img/Makefile") CONFIG_FILES="$CONFIG_FILES exfat2img/Makefile" ;; *) as_fn_error $? "invalid argument: \`$ac_config_target'" "$LINENO" 5;; esac @@ -14344,35 +14342,29 @@ $as_echo "$as_me: executing $ac_file commands" >&6;} # Older Autoconf quotes --file arguments for eval, but not when files # are listed without --file. Let's play safe and only enable the eval # if we detect the quoting. - # TODO: see whether this extra hack can be removed once we start - # requiring Autoconf 2.70 or later. - case $CONFIG_FILES in #( - *\'*) : - eval set x "$CONFIG_FILES" ;; #( - *) : - set x $CONFIG_FILES ;; #( - *) : - ;; -esac + case $CONFIG_FILES in + *\'*) eval set x "$CONFIG_FILES" ;; + *) set x $CONFIG_FILES ;; + esac shift - # Used to flag and report bootstrapping failures. - am_rc=0 - for am_mf + for mf do # Strip MF so we end up with the name of the file. - am_mf=`$as_echo "$am_mf" | sed -e 's/:.*$//'` - # Check whether this is an Automake generated Makefile which includes - # dependency-tracking related rules and includes. - # Grep'ing the whole file directly is not great: AIX grep has a line + mf=`echo "$mf" | sed -e 's/:.*$//'` + # Check whether this is an Automake generated Makefile or not. + # We used to match only the files named 'Makefile.in', but + # some people rename them; so instead we look at the file content. + # Grep'ing the first line is not enough: some people post-process + # each Makefile.in and add a new line on top of each file to say so. + # Grep'ing the whole file is not good either: AIX grep has a line # limit of 2048, but all sed's we know have understand at least 4000. - sed -n 's,^am--depfiles:.*,X,p' "$am_mf" | grep X >/dev/null 2>&1 \ - || continue - am_dirpart=`$as_dirname -- "$am_mf" || -$as_expr X"$am_mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ - X"$am_mf" : 'X\(//\)[^/]' \| \ - X"$am_mf" : 'X\(//\)$' \| \ - X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X"$am_mf" | + if sed -n 's,^#.*generated by automake.*,X,p' "$mf" | grep X >/dev/null 2>&1; then + dirpart=`$as_dirname -- "$mf" || +$as_expr X"$mf" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$mf" : 'X\(//\)[^/]' \| \ + X"$mf" : 'X\(//\)$' \| \ + X"$mf" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$mf" | sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q @@ -14390,48 +14382,53 @@ $as_echo X"$am_mf" | q } s/.*/./; q'` - am_filepart=`$as_basename -- "$am_mf" || -$as_expr X/"$am_mf" : '.*/\([^/][^/]*\)/*$' \| \ - X"$am_mf" : 'X\(//\)$' \| \ - X"$am_mf" : 'X\(/\)' \| . 2>/dev/null || -$as_echo X/"$am_mf" | - sed '/^.*\/\([^/][^/]*\)\/*$/{ + else + continue + fi + # Extract the definition of DEPDIR, am__include, and am__quote + # from the Makefile without running 'make'. + DEPDIR=`sed -n 's/^DEPDIR = //p' < "$mf"` + test -z "$DEPDIR" && continue + am__include=`sed -n 's/^am__include = //p' < "$mf"` + test -z "$am__include" && continue + am__quote=`sed -n 's/^am__quote = //p' < "$mf"` + # Find all dependency output files, they are included files with + # $(DEPDIR) in their names. We invoke sed twice because it is the + # simplest approach to changing $(DEPDIR) to its actual value in the + # expansion. + for file in `sed -n " + s/^$am__include $am__quote\(.*(DEPDIR).*\)$am__quote"'$/\1/p' <"$mf" | \ + sed -e 's/\$(DEPDIR)/'"$DEPDIR"'/g'`; do + # Make sure the directory exists. + test -f "$dirpart/$file" && continue + fdir=`$as_dirname -- "$file" || +$as_expr X"$file" : 'X\(.*[^/]\)//*[^/][^/]*/*$' \| \ + X"$file" : 'X\(//\)[^/]' \| \ + X"$file" : 'X\(//\)$' \| \ + X"$file" : 'X\(/\)' \| . 2>/dev/null || +$as_echo X"$file" | + sed '/^X\(.*[^/]\)\/\/*[^/][^/]*\/*$/{ s//\1/ q } - /^X\/\(\/\/\)$/{ + /^X\(\/\/\)[^/].*/{ s//\1/ q } - /^X\/\(\/\).*/{ + /^X\(\/\/\)$/{ + s//\1/ + q + } + /^X\(\/\).*/{ s//\1/ q } s/.*/./; q'` - { echo "$as_me:$LINENO: cd "$am_dirpart" \ - && sed -e '/# am--include-marker/d' "$am_filepart" \ - | $MAKE -f - am--depfiles" >&5 - (cd "$am_dirpart" \ - && sed -e '/# am--include-marker/d' "$am_filepart" \ - | $MAKE -f - am--depfiles) >&5 2>&5 - ac_status=$? - echo "$as_me:$LINENO: \$? = $ac_status" >&5 - (exit $ac_status); } || am_rc=$? + as_dir=$dirpart/$fdir; as_fn_mkdir_p + # echo "creating $dirpart/$file" + echo '# dummy' > "$dirpart/$file" + done done - if test $am_rc -ne 0; then - { { $as_echo "$as_me:${as_lineno-$LINENO}: error: in \`$ac_pwd':" >&5 -$as_echo "$as_me: error: in \`$ac_pwd':" >&2;} -as_fn_error $? "Something went wrong bootstrapping makefile fragments - for automatic dependency tracking. Try re-running configure with the - '--disable-dependency-tracking' option to at least be able to build - the package (albeit without support for automatic dependency tracking). -See \`config.log' for more details" "$LINENO" 5; } - fi - { am_dirpart=; unset am_dirpart;} - { am_filepart=; unset am_filepart;} - { am_mf=; unset am_mf;} - { am_rc=; unset am_rc;} - rm -f conftest-deps.mk } ;; "libtool":C) diff --git a/configure.ac b/configure.ac index 0544309..bc20774 100644 --- a/configure.ac +++ b/configure.ac @@ -32,6 +32,7 @@ AC_CONFIG_FILES([ tune/Makefile label/Makefile dump/Makefile + exfat2img/Makefile ]) AC_OUTPUT diff --git a/debian/changelog b/debian/changelog index 5493369..f3a4431 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,57 @@ +exfatprogs (1.2.2-1) unstable; urgency=medium + + * New upstream release. + Includes fixes for CVE-2023-45897 out-of-bounds memory access. + * Update debian/copyright. Drop year from my own copyright line. + + -- Sven Hoexter Sun, 29 Oct 2023 11:53:47 +0100 + +exfatprogs (1.2.1-2) unstable; urgency=medium + + * Remove conflicts on exfat-utils, that is now only available in + oldstable. + + -- Sven Hoexter Wed, 14 Jun 2023 17:30:10 +0200 + +exfatprogs (1.2.1-1) experimental; urgency=medium + + * New upstream release. + + fsck.exfat improvements + * Update debian/watch to query the GitHub API for release information. + * Update Standards-Version to 4.6.2 - no changes required. + + -- Sven Hoexter Tue, 23 May 2023 14:08:51 +0200 + +exfatprogs (1.2.0-1) unstable; urgency=medium + + * New upstream release. + + New utilitiy exfat2img to dump exFAT metadata. + + fsck.exfat is now able to repair certain corruptions. + * Update Standards-Version to 4.6.1 - no changes required. + * Rewrite the package short and long description. + + -- Sven Hoexter Fri, 28 Oct 2022 14:48:05 +0200 + +exfatprogs (1.1.3-1) unstable; urgency=medium + + * New upstream release. + * Update Standards-Version to 4.6.0 - no changes required. + + -- Sven Hoexter Wed, 17 Nov 2021 20:10:43 +0100 + +exfatprogs (1.1.2-2) unstable; urgency=medium + + * Post stable release upload to unstable. + + -- Sven Hoexter Mon, 16 Aug 2021 19:55:59 +0200 + exfatprogs (1.1.2-1) experimental; urgency=medium * New upstream release, mainly bugfixes and mkfs.exfat set 0x80 to DriveSelect of the boot sector, to - help Windows 10 recognize exFAT formated partitions. + help Windows 10 recognize exFAT formatted partitions. - -- Sven Hoexter Thu, 20 May 2021 10:29:28 +0200 + -- Sven Hoexter Thu, 20 May 2021 10:41:20 +0200 exfatprogs (1.1.1-1) experimental; urgency=medium diff --git a/debian/control b/debian/control index 0fabda1..59624d7 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Section: otherosfs Priority: optional Maintainer: Sven Hoexter Build-Depends: debhelper-compat (= 13), pkg-config -Standards-Version: 4.5.1 +Standards-Version: 4.6.2 Rules-Requires-Root: no Homepage: https://github.com/exfatprogs/exfatprogs Vcs-Git: https://git.sven.stormbind.net/exfatprogs.git @@ -12,16 +12,15 @@ Vcs-Browser: https://git.sven.stormbind.net/?p=sven/exfatprogs.git Package: exfatprogs Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} -Conflicts: exfat-utils -Description: tools to create, check and label exFAT filesystems +Description: exFAT file system utilities Tools to manage extended file allocation table filesystem. - This package provides tools to create, check and label the - filesystem. It contains + This package provides tools to create, check, dump and label + the filesystem. It contains - mkfs.exfat to create an exFAT filesystem - fsck.exfat to check and repair an exFAT filesystem - - tune.exfat to print and edit the filesystem label + - tune.exfat to print and edit the volume label or serial + - dump.exfat to show on-disk information of an exFAT filesystem + - exfat2img to dump exFAT metadata The tools included in this package are the exfatprogs - maintained by Samsung engineers, who provided Linux exFAT - support. A similar but independent implementation of these - tools, written by the author of the exfat-fuse implementation, - are available in the exfat-utils package. + maintained by Samsung and LG engineers, who provided Linux exFAT + support. diff --git a/debian/copyright b/debian/copyright index 65cb319..1f9e1a9 100644 --- a/debian/copyright +++ b/debian/copyright @@ -34,7 +34,7 @@ License: The Debian packaging is: - Copyright (C) 2020-2021 Sven Hoexter + Copyright (C) Sven Hoexter and is licensed under the GPL version 2 or later, see "/usr/share/common-licenses/GPL-2". diff --git a/debian/watch b/debian/watch index 6076b3c..08fea01 100644 --- a/debian/watch +++ b/debian/watch @@ -1,2 +1,5 @@ version=4 -opts=pgpsigurlmangle=s/$/.asc/ https://github.com/exfatprogs/exfatprogs/releases .*/exfatprogs-(\d\S*)\.tar\.xz \ No newline at end of file +opts="searchmode=plain,\ +filenamemangle=s%v?@ANY_VERSION@%@PACKAGE@-$1.tar.xz%" \ +https://api.github.com/repos/exfatprogs/exfatprogs/releases?per_page=50 \ +https://api.github.com/repos/[^/]+/[^/]+/tarball/v?@ANY_VERSION@ \ No newline at end of file diff --git a/dump/Makefile.in b/dump/Makefile.in index 33cf63c..e79c123 100644 --- a/dump/Makefile.in +++ b/dump/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.16.1 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2018 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -125,8 +125,7 @@ am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp -am__maybe_remake_depfiles = depfiles -am__depfiles_remade = ./$(DEPDIR)/dump.Po +am__depfiles_maybe = depfiles am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -315,8 +314,8 @@ Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) @@ -387,13 +386,7 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dump.Po@am__quote@ # am--include-marker - -$(am__depfiles_remade): - @$(MKDIR_P) $(@D) - @echo '# dummy' >$@-t && $(am__mv) $@-t $@ - -am--depfiles: $(am__depfiles_remade) +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/dump.Po@am__quote@ .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @@ -477,10 +470,7 @@ cscopelist-am: $(am__tagged_files) distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags -distdir: $(BUILT_SOURCES) - $(MAKE) $(AM_MAKEFLAGS) distdir-am - -distdir-am: $(DISTFILES) +distdir: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ @@ -553,7 +543,7 @@ clean-am: clean-generic clean-libtool clean-sbinPROGRAMS \ mostlyclean-am distclean: distclean-am - -rm -f ./$(DEPDIR)/dump.Po + -rm -rf ./$(DEPDIR) -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags @@ -599,7 +589,7 @@ install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am - -rm -f ./$(DEPDIR)/dump.Po + -rm -rf ./$(DEPDIR) -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic @@ -620,9 +610,9 @@ uninstall-am: uninstall-sbinPROGRAMS .MAKE: install-am install-strip -.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ - clean-generic clean-libtool clean-sbinPROGRAMS cscopelist-am \ - ctags ctags-am distclean distclean-compile distclean-generic \ +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-sbinPROGRAMS cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-data \ install-data-am install-dvi install-dvi-am install-exec \ diff --git a/dump/dump.c b/dump/dump.c index 7ede550..8c96376 100644 --- a/dump/dump.c +++ b/dump/dump.c @@ -90,6 +90,12 @@ static int exfat_show_ondisk_all_info(struct exfat_blk_dev *bd) goto free_ppbr; } + if (memcmp(ppbr->bpb.oem_name, "EXFAT ", 8) != 0) { + exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n"); + ret = -EINVAL; + goto free_ppbr; + } + pbsx = &ppbr->bsx; if (pbsx->sect_size_bits < EXFAT_MIN_SECT_SIZE_BITS || @@ -217,6 +223,7 @@ int main(int argc, char *argv[]) bool version_only = false; init_user_input(&ui); + ui.writeable = false; if (!setlocale(LC_CTYPE, "")) exfat_err("failed to init locale/codeset\n"); diff --git a/exfat2img/Android.bp b/exfat2img/Android.bp new file mode 100644 index 0000000..3679276 --- /dev/null +++ b/exfat2img/Android.bp @@ -0,0 +1,11 @@ +// Copyright 2021 The Android Open Source Project + +cc_binary { + name: "exfat2img", + + srcs: [ + "exfat2img.c", + ], + defaults: ["exfatprogs-defaults"], + static_libs: ["libexfat"], +} diff --git a/exfat2img/Makefile.am b/exfat2img/Makefile.am new file mode 100644 index 0000000..8b5cee7 --- /dev/null +++ b/exfat2img/Makefile.am @@ -0,0 +1,6 @@ +AM_CFLAGS = -Wall -Wextra -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common +exfat2img_LDADD = $(top_builddir)/lib/libexfat.a + +sbin_PROGRAMS = exfat2img + +exfat2img_SOURCES = exfat2img.c diff --git a/exfat2img/Makefile.in b/exfat2img/Makefile.in new file mode 100644 index 0000000..04ba633 --- /dev/null +++ b/exfat2img/Makefile.in @@ -0,0 +1,632 @@ +# Makefile.in generated by automake 1.15.1 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2017 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +sbin_PROGRAMS = exfat2img$(EXEEXT) +subdir = exfat2img +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/m4/libtool.m4 \ + $(top_srcdir)/m4/ltoptions.m4 $(top_srcdir)/m4/ltsugar.m4 \ + $(top_srcdir)/m4/ltversion.m4 $(top_srcdir)/m4/lt~obsolete.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(sbindir)" +PROGRAMS = $(sbin_PROGRAMS) +am_exfat2img_OBJECTS = exfat2img.$(OBJEXT) +exfat2img_OBJECTS = $(am_exfat2img_OBJECTS) +exfat2img_DEPENDENCIES = $(top_builddir)/lib/libexfat.a +AM_V_lt = $(am__v_lt_@AM_V@) +am__v_lt_ = $(am__v_lt_@AM_DEFAULT_V@) +am__v_lt_0 = --silent +am__v_lt_1 = +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +LTCOMPILE = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=compile $(CC) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(LIBTOOL) $(AM_V_lt) --tag=CC $(AM_LIBTOOLFLAGS) \ + $(LIBTOOLFLAGS) --mode=link $(CCLD) $(AM_CFLAGS) $(CFLAGS) \ + $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = $(exfat2img_SOURCES) +DIST_SOURCES = $(exfat2img_SOURCES) +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in \ + $(top_srcdir)/build-aux/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +DLLTOOL = @DLLTOOL@ +DSYMUTIL = @DSYMUTIL@ +DUMPBIN = @DUMPBIN@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +FGREP = @FGREP@ +GREP = @GREP@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LD = @LD@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LIPO = @LIPO@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +LT_SYS_LIBRARY_PATH = @LT_SYS_LIBRARY_PATH@ +MAKEINFO = @MAKEINFO@ +MANIFEST_TOOL = @MANIFEST_TOOL@ +MKDIR_P = @MKDIR_P@ +NM = @NM@ +NMEDIT = @NMEDIT@ +OBJDUMP = @OBJDUMP@ +OBJEXT = @OBJEXT@ +OTOOL = @OTOOL@ +OTOOL64 = @OTOOL64@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +RANLIB = @RANLIB@ +SED = @SED@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_DUMPBIN = @ac_ct_DUMPBIN@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +runstatedir = @runstatedir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_CFLAGS = -Wall -Wextra -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common +exfat2img_LDADD = $(top_builddir)/lib/libexfat.a +exfat2img_SOURCES = exfat2img.c +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign exfat2img/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign exfat2img/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-sbinPROGRAMS: $(sbin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(sbindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(sbindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + || test -f $$p1 \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(sbindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(LIBTOOL) $(AM_LIBTOOLFLAGS) $(LIBTOOLFLAGS) --mode=install $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(sbindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-sbinPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(sbin_PROGRAMS)'; test -n "$(sbindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(sbindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(sbindir)" && rm -f $$files + +clean-sbinPROGRAMS: + @list='$(sbin_PROGRAMS)'; test -n "$$list" || exit 0; \ + echo " rm -f" $$list; \ + rm -f $$list || exit $$?; \ + test -n "$(EXEEXT)" || exit 0; \ + list=`for p in $$list; do echo "$$p"; done | sed 's/$(EXEEXT)$$//'`; \ + echo " rm -f" $$list; \ + rm -f $$list + +exfat2img$(EXEEXT): $(exfat2img_OBJECTS) $(exfat2img_DEPENDENCIES) $(EXTRA_exfat2img_DEPENDENCIES) + @rm -f exfat2img$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(exfat2img_OBJECTS) $(exfat2img_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exfat2img.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.c.lo: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.lo$$||'`;\ +@am__fastdepCC_TRUE@ $(LTCOMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Plo +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(LTCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(sbindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-sbinPROGRAMS \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-sbinPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-sbinPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-sbinPROGRAMS cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-sbinPROGRAMS install-strip \ + installcheck installcheck-am installdirs maintainer-clean \ + maintainer-clean-generic mostlyclean mostlyclean-compile \ + mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ + tags tags-am uninstall uninstall-am uninstall-sbinPROGRAMS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/exfat2img/exfat2img.c b/exfat2img/exfat2img.c new file mode 100644 index 0000000..3f83588 --- /dev/null +++ b/exfat2img/exfat2img.c @@ -0,0 +1,1017 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 LG Electronics. + * + * Author(s): Hyunchul Lee + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "exfat_ondisk.h" +#include "libexfat.h" +#include "exfat_fs.h" +#include "exfat_dir.h" + +#define EXFAT_MAX_UPCASE_CHARS 0x10000 + +struct exfat2img_hdr { + __le32 magic; + __le32 major_version; + __le32 minor_version; + __le32 data_offset; + __le32 heap_clus_offset; + __le32 cluster_size; + __le32 cluster_count; + __le32 reserved[20]; +} __packed; + +#define EI_MAGIC 0xB67598DB +#define EI_CC_PAYLOAD_LEN 4 + +enum { + EI_CC_INVALID, + EI_CC_COPY_1, + EI_CC_COPY_2, /* followed by cluster count(4-byte) */ + EI_CC_SKIP_1, + EI_CC_SKIP_2, /* followed by cluster count(4-byte) */ +}; + +struct exfat2img { + int out_fd; + bool is_stdout; + off_t stdout_offset; + bool save_cc; + struct exfat_blk_dev bdev; + struct exfat *exfat; + struct buffer_desc *dump_bdesc; + struct buffer_desc *scan_bdesc; + struct exfat_de_iter de_iter; +}; + +struct exfat_stat { + long dir_count; + long file_count; + long error_count; + uint64_t written_bytes; +}; + +static struct exfat2img_hdr ei_hdr; +static struct exfat2img ei; +static struct exfat_stat exfat_stat; +static struct path_resolve_ctx path_resolve_ctx; + +static struct option opts[] = { + {"output", required_argument, NULL, 'o' }, + {"version", no_argument, NULL, 'V' }, + {"help", no_argument, NULL, 'h' }, + {NULL, 0, NULL, 0 } +}; + +static void usage(const char *name) +{ + fprintf(stderr, "Usage: %s [image-file]\n", name); + fprintf(stderr, "\t-o | --output Specify destination file\n"); + fprintf(stderr, "\t-V | --version Show version\n"); + fprintf(stderr, "\t-h | --help Show help\n"); + exit(EXIT_FAILURE); +} + +#define ei_err(parent, inode, fmt, ...) \ +({ \ + exfat_resolve_path_parent(&path_resolve_ctx, \ + parent, inode); \ + exfat_err("ERROR: %s: " fmt, \ + path_resolve_ctx.local_path, \ + ##__VA_ARGS__); \ +}) + +static void free_exfat2img(struct exfat2img *ei) +{ + if (ei->exfat) + exfat_free_exfat(ei->exfat); + if (ei->dump_bdesc) + exfat_free_buffer(ei->dump_bdesc, 2); + if (ei->scan_bdesc) + exfat_free_buffer(ei->scan_bdesc, 2); + if (ei->out_fd) + close(ei->out_fd); + if (ei->bdev.dev_fd) + close(ei->bdev.dev_fd); +} + +static int create_exfat2img(struct exfat2img *ei, + struct pbr *bs, + const char *out_path) +{ + int err; + + ei->exfat = exfat_alloc_exfat(&ei->bdev, bs); + if (!ei->exfat) + return -ENOMEM; + + ei->dump_bdesc = exfat_alloc_buffer(2, + ei->exfat->clus_size, + ei->exfat->sect_size); + if (!ei->dump_bdesc) { + err = -ENOMEM; + goto err; + } + + ei->scan_bdesc = exfat_alloc_buffer(2, + ei->exfat->clus_size, + ei->exfat->sect_size); + if (!ei->scan_bdesc) { + err = -ENOMEM; + goto err; + } + + if (strcmp(out_path, "-")) { + ei->out_fd = open(out_path, O_CREAT | O_TRUNC | O_RDWR, 0664); + } else { + ei->is_stdout = true; + ei->out_fd = fileno(stdout); + ei->save_cc = true; + } + if (ei->out_fd < 0) { + exfat_err("failed to open %s: %s\n", out_path, + strerror(errno)); + err = -errno; + goto err; + } + + return 0; +err: + free_exfat2img(ei); + return err; +} + +/** + * @end: excluded. + */ +static ssize_t dump_range(struct exfat2img *ei, off_t start, off_t end) +{ + struct exfat *exfat = ei->exfat; + size_t len, total_len = 0; + ssize_t ret; + + if (ei->is_stdout) { + unsigned int sc, sc_offset; + unsigned int ec, ec_offset; + + if (exfat_o2c(ei->exfat, start, &sc, &sc_offset) < 0) + return -ERANGE; + if (exfat_o2c(ei->exfat, end - 1, &ec, &ec_offset) < 0) + return -ERANGE; + exfat_bitmap_set_range(ei->exfat, exfat->alloc_bitmap, + sc, ec - sc + 1); + return end - start; + } + + while (start < end) { + len = (size_t)MIN(end - start, exfat->clus_size); + + ret = exfat_read(exfat->blk_dev->dev_fd, + ei->dump_bdesc[0].buffer, + len, start); + if (ret != (ssize_t)len) { + exfat_err("failed to read %llu bytes at %llu\n", + (unsigned long long)len, + (unsigned long long)start); + return -EIO; + } + + ret = pwrite(ei->out_fd, ei->dump_bdesc[0].buffer, + len, start); + if (ret != (ssize_t)len) { + exfat_err("failed to write %llu bytes at %llu\n", + (unsigned long long)len, + (unsigned long long)start); + return -EIO; + } + + start += len; + total_len += len; + exfat_stat.written_bytes += len; + } + return total_len; +} + +static int dump_sectors(struct exfat2img *ei, + off_t start_sect, + off_t end_sect_excl) +{ + struct exfat *exfat = ei->exfat; + off_t s, e; + + s = exfat_s2o(exfat, start_sect); + e = exfat_s2o(exfat, end_sect_excl); + return dump_range(ei, s, e) <= 0 ? -EIO : 0; +} + +static int dump_clusters(struct exfat2img *ei, + clus_t start_clus, + clus_t end_clus_excl) +{ + struct exfat *exfat = ei->exfat; + off_t s, e; + + s = exfat_c2o(exfat, start_clus); + e = exfat_c2o(exfat, end_clus_excl); + return dump_range(ei, s, e) <= 0 ? -EIO : 0; +} + +static int dump_directory(struct exfat2img *ei, + struct exfat_inode *inode, size_t size, + clus_t *out_clus_count) +{ + struct exfat *exfat = ei->exfat; + clus_t clus, possible_count; + uint64_t max_count; + size_t dump_size; + off_t start_off, end_off; + + if (size == 0) + return -EINVAL; + + if (!(inode->attr & ATTR_SUBDIR)) + return -EINVAL; + + clus = inode->first_clus; + *out_clus_count = 0; + max_count = DIV_ROUND_UP(inode->size, exfat->clus_size); + + possible_count = (256 * MB) >> (exfat->bs->bsx.sect_per_clus_bits + + exfat->bs->bsx.sect_size_bits); + possible_count = MIN(possible_count, exfat->clus_count); + + while (exfat_heap_clus(exfat, clus) && *out_clus_count < possible_count) { + dump_size = MIN(size, exfat->clus_size); + start_off = exfat_c2o(exfat, clus); + end_off = start_off + DIV_ROUND_UP(dump_size, 512) * 512; + + if (dump_range(ei, start_off, end_off) < 0) + return -EIO; + + *out_clus_count += 1; + size -= dump_size; + if (size == 0) + break; + + if (inode->is_contiguous) { + if (*out_clus_count >= max_count) + break; + } + if (exfat_get_inode_next_clus(exfat, inode, clus, &clus)) + return -EINVAL; + } + return 0; +} + +static int dump_root(struct exfat2img *ei) +{ + struct exfat *exfat = ei->exfat; + struct exfat_inode *root; + clus_t clus_count = 0; + + root = exfat_alloc_inode(ATTR_SUBDIR); + if (!root) + return -ENOMEM; + + root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster); + dump_directory(ei, root, (size_t)-1, &clus_count); + root->size = clus_count * exfat->clus_size; + + ei->exfat->root = root; + return 0; +} + +static int read_file_dentry_set(struct exfat_de_iter *iter, + struct exfat_inode **new_node, int *skip_dentries) +{ + struct exfat_dentry *file_de, *stream_de, *dentry; + struct exfat_inode *node = NULL; + int i, ret; + + ret = exfat_de_iter_get(iter, 0, &file_de); + if (ret || file_de->type != EXFAT_FILE) { + exfat_debug("failed to get file dentry\n"); + return -EINVAL; + } + + ret = exfat_de_iter_get(iter, 1, &stream_de); + if (ret || stream_de->type != EXFAT_STREAM) { + exfat_debug("failed to get stream dentry\n"); + *skip_dentries = 2; + goto skip_dset; + } + + *new_node = NULL; + node = exfat_alloc_inode(le16_to_cpu(file_de->file_attr)); + if (!node) + return -ENOMEM; + + for (i = 2; i <= MIN(file_de->file_num_ext, 1 + MAX_NAME_DENTRIES); i++) { + ret = exfat_de_iter_get(iter, i, &dentry); + if (ret || dentry->type != EXFAT_NAME) + break; + memcpy(node->name + + (i - 2) * ENTRY_NAME_MAX, dentry->name_unicode, + sizeof(dentry->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); + + *skip_dentries = i; + *new_node = node; + return 0; +skip_dset: + *new_node = NULL; + exfat_free_inode(node); + return -EINVAL; +} + +static int read_file(struct exfat_de_iter *de_iter, + struct exfat_inode **new_node, int *dentry_count) +{ + struct exfat_inode *node; + int ret; + + *new_node = NULL; + + ret = read_file_dentry_set(de_iter, &node, dentry_count); + if (ret) + return ret; + + if (node->attr & ATTR_SUBDIR) + exfat_stat.dir_count++; + else + exfat_stat.file_count++; + *new_node = node; + return ret; +} + +static int read_bitmap(struct exfat2img *ei, struct exfat_de_iter *iter) +{ + struct exfat *exfat = ei->exfat; + struct exfat_dentry *dentry; + int ret; + + ret = exfat_de_iter_get(iter, 0, &dentry); + if (ret || dentry->type != EXFAT_BITMAP) { + exfat_debug("failed to get bimtap dentry\n"); + return -EINVAL; + } + + exfat_debug("start cluster %#x, size %#" PRIx64 "\n", + le32_to_cpu(dentry->bitmap_start_clu), + le64_to_cpu(dentry->bitmap_size)); + + if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) { + exfat_err("invalid start cluster of allocate bitmap. 0x%x\n", + le32_to_cpu(dentry->bitmap_start_clu)); + return -EINVAL; + } + + exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu); + exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8); + + return dump_clusters(ei, + exfat->disk_bitmap_clus, + exfat->disk_bitmap_clus + + DIV_ROUND_UP(exfat->disk_bitmap_size, + exfat->clus_size)); +} + +static int read_upcase_table(struct exfat2img *ei, + struct exfat_de_iter *iter) +{ + struct exfat *exfat = ei->exfat; + struct exfat_dentry *dentry = NULL; + int retval; + ssize_t size; + + retval = exfat_de_iter_get(iter, 0, &dentry); + if (retval || dentry->type != EXFAT_UPCASE) { + exfat_debug("failed to get upcase dentry\n"); + return -EINVAL; + } + + if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) { + exfat_err("invalid start cluster of upcase table. 0x%x\n", + le32_to_cpu(dentry->upcase_start_clu)); + return -EINVAL; + } + + size = EXFAT_MAX_UPCASE_CHARS * sizeof(__le16); + return dump_clusters(ei, le32_to_cpu(dentry->upcase_start_clu), + le32_to_cpu(dentry->upcase_start_clu) + + DIV_ROUND_UP(size, exfat->clus_size)); +} + +static int read_children(struct exfat2img *ei, struct exfat_inode *dir, + off_t *end_file_offset) +{ + struct exfat *exfat = ei->exfat; + struct exfat_inode *node = NULL; + struct exfat_dentry *dentry; + struct exfat_de_iter *de_iter; + int dentry_count; + int ret; + + *end_file_offset = 0; + de_iter = &ei->de_iter; + ret = exfat_de_iter_init(de_iter, exfat, dir, ei->scan_bdesc); + 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) { + ei_err(dir->parent, dir, + "failed to get a dentry. %d\n", ret); + goto err; + } + dentry_count = 1; + + switch (dentry->type) { + case EXFAT_FILE: + ret = read_file(de_iter, &node, &dentry_count); + if (ret < 0) { + exfat_stat.error_count++; + break; + } + + if (node) { + if ((node->attr & ATTR_SUBDIR) && node->size) { + node->parent = dir; + list_add_tail(&node->sibling, + &dir->children); + list_add_tail(&node->list, + &exfat->dir_list); + } else { + exfat_free_inode(node); + } + } + break; + case EXFAT_LAST: + goto out; + case EXFAT_BITMAP: + if (dir == exfat->root) { + ret = read_bitmap(ei, de_iter); + if (ret) + exfat_debug("failed to read bitmap\n"); + } + break; + case EXFAT_UPCASE: + if (dir == exfat->root) { + ret = read_upcase_table(ei, de_iter); + if (ret) + exfat_debug("failed to upcase table\n"); + } + break; + case EXFAT_VOLUME: + default: + break; + } + + ret = exfat_de_iter_advance(de_iter, dentry_count); + } +out: + *end_file_offset = exfat_de_iter_file_offset(de_iter); + exfat_de_iter_flush(de_iter); + return 0; +err: + exfat_free_children(dir, false); + INIT_LIST_HEAD(&dir->children); + exfat_de_iter_flush(de_iter); + return ret; +} + +static int dump_filesystem(struct exfat2img *ei) +{ + struct exfat *exfat = ei->exfat; + struct exfat_inode *dir; + int ret = 0, dir_errors; + clus_t clus_count; + off_t end_file_offset; + + 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); + clus_count = 0; + + if (!(dir->attr & ATTR_SUBDIR)) { + ei_err(dir->parent, dir, + "failed to travel directories. the node is not directory\n"); + ret = -EINVAL; + goto out; + } + + dir_errors = read_children(ei, dir, &end_file_offset); + if (!dir_errors) { + dump_directory(ei, dir, (size_t)end_file_offset, + &clus_count); + } else if (dir_errors) { + dump_directory(ei, dir, (size_t)-1, + &clus_count); + exfat_resolve_path(&path_resolve_ctx, dir); + exfat_debug("failed to check dentries: %s\n", + path_resolve_ctx.local_path); + ret = dir_errors; + } + + list_del(&dir->list); + exfat_free_ancestors(dir); + } +out: + exfat_free_dir_list(exfat); + return ret; +} + +static int dump_bytes_to_stdout(struct exfat2img *ei, + off_t start, off_t end_excl, bool fill_zero) +{ + struct exfat *exfat = ei->exfat; + size_t len; + ssize_t ret; + + if (start != ei->stdout_offset) { + exfat_err("try to skip for stdout at %llu, expected: %llu\n", + (unsigned long long)start, + (unsigned long long)ei->stdout_offset); + return -EINVAL; + } + + while (start < end_excl) { + len = (size_t)MIN(end_excl - start, exfat->clus_size); + if (!fill_zero) { + ret = exfat_read(exfat->blk_dev->dev_fd, + ei->dump_bdesc[0].buffer, + len, start); + if (ret != (ssize_t)len) { + exfat_err("failed to read %llu bytes at %llu\n", + (unsigned long long)len, + (unsigned long long)start); + return -EIO; + } + + ret = write(ei->out_fd, ei->dump_bdesc[0].buffer, len); + if (ret != (ssize_t)len) { + exfat_err("failed to write %llu bytes at %llu\n", + (unsigned long long)len, + (unsigned long long)start); + return -EIO; + } + } else { + ret = write(ei->out_fd, exfat->zero_cluster, len); + if (ret != (ssize_t)len) { + exfat_err("failed to write %llu bytes at %llu\n", + (unsigned long long)len, + (unsigned long long)start); + return -EIO; + } + } + + start += len; + ei->stdout_offset += len; + exfat_stat.written_bytes += len; + } + return 0; +} + +static int dump_clusters_to_stdout(struct exfat2img *ei, + unsigned int start_clu, unsigned int end_clu, + bool fill_zero) +{ + unsigned int clu, clu_count; + unsigned char cc; + unsigned int cc_clu_count, cc_len; + off_t start_off, end_off_excl; + char buf[1 + EI_CC_PAYLOAD_LEN]; + + clu = start_clu; + clu_count = end_clu - start_clu + 1; + + if (ei->save_cc) { + /* if the count of clusters is less than 5, use SKIP_1 or COPY_2 */ + cc_clu_count = clu_count < 5 ? 1 : clu_count; + cc_len = cc_clu_count == 1 ? 1 : 1 + EI_CC_PAYLOAD_LEN; + if (fill_zero) + cc = cc_clu_count == 1 ? EI_CC_SKIP_1 : EI_CC_SKIP_2; + else + cc = cc_clu_count == 1 ? EI_CC_COPY_1 : EI_CC_COPY_2; + } else { + cc = EI_CC_INVALID; + cc_clu_count = clu_count; + } + + while (clu <= end_clu) { + if (cc != EI_CC_INVALID) { + buf[0] = cc; + *((__le32 *)&buf[1]) = + cpu_to_le32(cc_clu_count); + if (write(ei->out_fd, buf, cc_len) != (ssize_t)cc_len) { + exfat_err("failed to write cc %d : %u\n for %u ~ %u clusters\n", + cc, cc_clu_count, + start_clu, start_clu + cc_clu_count - 1); + } + } + + if (cc == EI_CC_COPY_1 || cc == EI_CC_COPY_2) { + start_off = exfat_c2o(ei->exfat, clu); + end_off_excl = exfat_c2o(ei->exfat, clu + cc_clu_count); + + if (dump_bytes_to_stdout(ei, start_off, end_off_excl, + false) < 0) + return -EIO; + } else { + ei->stdout_offset += (off_t)cc_clu_count * ei->exfat->clus_size; + } + clu += cc_clu_count; + } + + return 0; +} + +static int dump_to_stdout(struct exfat2img *ei) +{ + struct exfat *exfat = ei->exfat; + off_t start_off, end_off; + unsigned int clu, last_clu, next_clu; + unsigned int start_clu, end_clu; + + start_off = 0; + end_off = exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset)); + if (dump_bytes_to_stdout(ei, start_off, end_off, false) < 0) { + exfat_err("failed to dump boot sectors and FAT tables\n"); + return -EIO; + } + + clu = EXFAT_FIRST_CLUSTER; + last_clu = clu + exfat->clus_count; + while (clu < last_clu) { + /* read and write clusters for allocated ones */ + start_clu = 0; + while (clu < last_clu && + exfat_bitmap_get(exfat->alloc_bitmap, clu)) { + if (!start_clu) + start_clu = clu; + end_clu = clu; + clu++; + } + + if (start_clu) { + if (dump_clusters_to_stdout(ei, start_clu, end_clu, false) < 0) { + start_off = exfat_c2o(exfat, start_clu); + end_off = exfat_c2o(exfat, end_clu); + exfat_err("failed to dump range from %llx to %llx\n", + (unsigned long long)start_off, + (unsigned long long)end_off); + return -EIO; + } + } + + /* exit if all of the remaining clusters are free */ + if (clu >= last_clu) + break; + if (exfat_bitmap_find_one(exfat, exfat->alloc_bitmap, + clu, &next_clu)) + next_clu = EXFAT_FIRST_CLUSTER + exfat->clus_count; + + /* write zeroes for free clusters */ + start_clu = clu; + end_clu = next_clu - 1; + if (dump_clusters_to_stdout(ei, start_clu, end_clu, true) < 0) { + start_off = exfat_c2o(exfat, start_clu); + end_off = exfat_c2o(exfat, end_clu); + exfat_err("failed to dump zero range from %llx to %llx\n", + (unsigned long long)start_off, + (unsigned long long)end_off); + return -EIO; + } + + clu = next_clu; + } + + return 0; +} + +static int dump_header(struct exfat2img *ei) +{ + struct exfat *exfat = ei->exfat; + + ei_hdr.magic = cpu_to_le32(EI_MAGIC); + ei_hdr.major_version = cpu_to_le32(1); + ei_hdr.minor_version = cpu_to_le32(0); + ei_hdr.data_offset = cpu_to_le32(sizeof(struct exfat2img_hdr)); + ei_hdr.heap_clus_offset = + cpu_to_le32(le32_to_cpu(exfat->bs->bsx.clu_offset) * + exfat->sect_size); + ei_hdr.cluster_size = cpu_to_le32(exfat->clus_size); + ei_hdr.cluster_count = cpu_to_le32(exfat->clus_count); + + if (write(ei->out_fd, &ei_hdr, sizeof(ei_hdr)) != (ssize_t)sizeof(ei_hdr)) { + exfat_err("failed to write exfat2img header\n"); + return -EIO; + } + return 0; +} + +static ssize_t read_stream(int fd, void *buf, size_t len) +{ + size_t read_len = 0; + ssize_t ret; + + while (read_len < len) { + ret = read(fd, buf, len - read_len); + if (ret < 0) { + if (errno != -EAGAIN && errno != -EINTR) + return -1; + ret = 0; + } else if (ret == 0) { + return 0; + } + buf = (char *)buf + (size_t)ret; + read_len += (size_t)ret; + } + return read_len; +} + +static int restore_from_stdin(struct exfat2img *ei) +{ + int in_fd, ret; + unsigned char cc; + unsigned int clu, end_clu; + unsigned int cc_clu_count; + unsigned int clus_size; + __le32 t_cc_clu_count; + off_t out_start_off, out_end_off_excl; + off_t in_start_off; + size_t len; + + in_fd = fileno(stdin); + if (in_fd < 0) { + exfat_err("failed to get fd from stdin\n"); + return in_fd; + } + + if (read_stream(in_fd, &ei_hdr, sizeof(ei_hdr)) != (ssize_t)sizeof(ei_hdr)) { + exfat_err("failed to read a header\n"); + return -EIO; + } + + if (le32_to_cpu(ei_hdr.magic) != EI_MAGIC) { + exfat_err("header has invalid magic %#x, expected %#x\n", + le32_to_cpu(ei_hdr.magic), EI_MAGIC); + return -EINVAL; + } + + clus_size = le32_to_cpu(ei_hdr.cluster_size); + + ei->out_fd = ei->bdev.dev_fd; + ei->dump_bdesc = exfat_alloc_buffer(2, clus_size, 512); + if (!ei->dump_bdesc) + return -ENOMEM; + + /* restore boot regions, and FAT tables */ + in_start_off = le32_to_cpu(ei_hdr.data_offset); + out_start_off = 0; + out_end_off_excl = le32_to_cpu(ei_hdr.heap_clus_offset); + while (out_start_off < out_end_off_excl) { + len = MIN(out_end_off_excl - out_start_off, clus_size); + if (read_stream(in_fd, ei->dump_bdesc[0].buffer, len) != (ssize_t)len) { + exfat_err("failed to read first meta region. %llu ~ %llu\n", + (unsigned long long)in_start_off, + (unsigned long long)in_start_off + len); + ret = -EIO; + goto out; + } + + if (pwrite(ei->out_fd, ei->dump_bdesc[0].buffer, len, out_start_off) + != (ssize_t)len) { + exfat_err("failed to write first meta region. %llu ~ %llu\n", + (unsigned long long)out_start_off, + (unsigned long long)out_start_off + len); + ret = -EIO; + goto out; + } + + out_start_off += len; + in_start_off += len; + } + + /* restore heap clusters */ + clu = 0; + while (clu < le32_to_cpu(ei_hdr.cluster_count)) { + if (read_stream(in_fd, &cc, sizeof(cc)) != (ssize_t)sizeof(cc)) { + exfat_err("failed to read cc at %llu\n", + (unsigned long long)in_start_off); + ret = -EIO; + goto out; + } + in_start_off += 1; + + if (cc == EI_CC_COPY_2 || cc == EI_CC_SKIP_2) { + if (read_stream(in_fd, &t_cc_clu_count, EI_CC_PAYLOAD_LEN) != + (ssize_t)EI_CC_PAYLOAD_LEN) { + exfat_err("failed to read cc cluster count at %llu\n", + (unsigned long long)in_start_off); + ret = -EIO; + goto out; + } + cc_clu_count = le32_to_cpu(t_cc_clu_count); + in_start_off += EI_CC_PAYLOAD_LEN; + } else if (cc == EI_CC_COPY_1 || cc == EI_CC_SKIP_1) { + cc_clu_count = 1; + } else { + exfat_err("unexpected cc %d at %llu\n", + cc, (unsigned long long)in_start_off); + ret = -EINVAL; + goto out; + } + + if (cc == EI_CC_COPY_1 || cc == EI_CC_COPY_2) { + end_clu = clu + cc_clu_count; + while (clu < end_clu) { + if (read_stream(in_fd, ei->dump_bdesc[0].buffer, + clus_size) != (ssize_t)clus_size) { + exfat_err("failed to read range %llu ~ %llu\n", + (unsigned long long)in_start_off, + (unsigned long long)in_start_off + clus_size); + ret = -EIO; + goto out; + } + if (pwrite(ei->out_fd, ei->dump_bdesc[0].buffer, + clus_size, out_start_off) != (ssize_t)clus_size) { + exfat_err("failed to write range %llu ~ %llu\n", + (unsigned long long)out_start_off, + (unsigned long long)out_start_off + clus_size); + ret = -EIO; + goto out; + } + + out_start_off += clus_size; + in_start_off += clus_size; + clu++; + } + } else { + out_start_off += (off_t)cc_clu_count * clus_size; + in_start_off += (off_t)cc_clu_count * clus_size; + if (lseek(ei->out_fd, out_start_off, SEEK_SET) == (off_t)-1) { + exfat_err("failed to seek to %llu\n", + (unsigned long long)out_start_off); + ret = -EIO; + goto out; + } + clu += cc_clu_count; + } + } +out: + fsync(ei->out_fd); + exfat_free_buffer(ei->dump_bdesc, 2); + return ret; +} + +int main(int argc, char * const argv[]) +{ + int err = 0, c; + const char *in_path, *out_path = NULL, *blkdev_path; + struct pbr *bs; + struct exfat_user_input ui; + off_t last_sect; + bool restore; + + print_level = EXFAT_ERROR; + + opterr = 0; + while ((c = getopt_long(argc, argv, "o:Vh", opts, NULL)) != EOF) { + switch (c) { + case 'o': + out_path = optarg; + break; + case 'V': + show_version(); + return 0; + case 'h': + /* Fall through */ + default: + usage(argv[0]); + break; + } + } + + show_version(); + if (!(optind == argc - 1 && out_path) && + !(optind == argc - 2 && !out_path)) + usage(argv[0]); + + in_path = argv[optind++]; + if (!out_path) + out_path = argv[optind++]; + + if (!strcmp(in_path, "-")) { + restore = true; + blkdev_path = out_path; + } else { + restore = false; + blkdev_path = in_path; + } + + memset(&ui, 0, sizeof(ui)); + snprintf(ui.dev_name, sizeof(ui.dev_name), "%s", blkdev_path); + if (restore) + ui.writeable = true; + else + ui.writeable = false; + + if (exfat_get_blk_dev_info(&ui, &ei.bdev)) { + exfat_err("failed to open %s\n", ui.dev_name); + return EXIT_FAILURE; + } + + if (restore) + return restore_from_stdin(&ei); + + err = read_boot_sect(&ei.bdev, &bs); + if (err) { + close(ei.bdev.dev_fd); + return EXIT_FAILURE; + } + + err = create_exfat2img(&ei, bs, out_path); + if (err) + return EXIT_FAILURE; + + if (!ei.is_stdout) { + err = dump_sectors(&ei, 0, le32_to_cpu(ei.exfat->bs->bsx.clu_offset)); + if (err) { + exfat_err("failed to dump boot sectors, fat\n"); + goto out; + } + + last_sect = (off_t)le32_to_cpu(ei.exfat->bs->bsx.clu_offset) + + (le32_to_cpu(ei.exfat->bs->bsx.clu_count) << + ei.exfat->bs->bsx.sect_per_clus_bits) - 1; + err = dump_sectors(&ei, last_sect, last_sect + 1); + if (err) { + exfat_err("failed to dump last sector\n"); + goto out; + } + } + + err = dump_root(&ei); + if (err) { + exfat_err("failed to dump root\n"); + goto out; + } + + dump_filesystem(&ei); + + if (ei.is_stdout) { + err = dump_header(&ei); + if (err) + goto out; + err = dump_to_stdout(&ei); + if (err) + goto out; + } else { + err = fsync(ei.out_fd); + if (err) { + exfat_err("failed to fsync %s. %d\n", out_path, errno); + goto out; + } + close(ei.out_fd); + } + + printf("%ld files found, %ld directories dumped, %llu kbytes written\n", + exfat_stat.file_count, + exfat_stat.dir_count, + (unsigned long long)DIV_ROUND_UP(exfat_stat.written_bytes, 1024)); + +out: + free_exfat2img(&ei); + return err == 0 ? EXIT_SUCCESS : EXIT_FAILURE; +} diff --git a/fsck/Android.bp b/fsck/Android.bp index f267ad4..7473fea 100644 --- a/fsck/Android.bp +++ b/fsck/Android.bp @@ -4,7 +4,6 @@ cc_binary { name: "fsck.exfat", srcs: [ - "de_iter.c", "fsck.c", "repair.c", ], diff --git a/fsck/Makefile.am b/fsck/Makefile.am index 57a0ede..519b13a 100644 --- a/fsck/Makefile.am +++ b/fsck/Makefile.am @@ -1,6 +1,6 @@ -AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common +AM_CFLAGS = -Wall -Wextra -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common fsck_exfat_LDADD = $(top_builddir)/lib/libexfat.a sbin_PROGRAMS = fsck.exfat -fsck_exfat_SOURCES = fsck.c repair.c fsck.h de_iter.c repair.h +fsck_exfat_SOURCES = fsck.c repair.c fsck.h repair.h diff --git a/fsck/Makefile.in b/fsck/Makefile.in index 93ba140..56fa169 100644 --- a/fsck/Makefile.in +++ b/fsck/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.16.1 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2018 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -104,8 +104,7 @@ CONFIG_CLEAN_FILES = CONFIG_CLEAN_VPATH_FILES = am__installdirs = "$(DESTDIR)$(sbindir)" PROGRAMS = $(sbin_PROGRAMS) -am_fsck_exfat_OBJECTS = fsck.$(OBJEXT) repair.$(OBJEXT) \ - de_iter.$(OBJEXT) +am_fsck_exfat_OBJECTS = fsck.$(OBJEXT) repair.$(OBJEXT) fsck_exfat_OBJECTS = $(am_fsck_exfat_OBJECTS) fsck_exfat_DEPENDENCIES = $(top_builddir)/lib/libexfat.a AM_V_lt = $(am__v_lt_@AM_V@) @@ -126,9 +125,7 @@ am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp -am__maybe_remake_depfiles = depfiles -am__depfiles_remade = ./$(DEPDIR)/de_iter.Po ./$(DEPDIR)/fsck.Po \ - ./$(DEPDIR)/repair.Po +am__depfiles_maybe = depfiles am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -293,9 +290,9 @@ target_alias = @target_alias@ top_build_prefix = @top_build_prefix@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ -AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common +AM_CFLAGS = -Wall -Wextra -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 de_iter.c repair.h +fsck_exfat_SOURCES = fsck.c repair.c fsck.h repair.h all: all-am .SUFFIXES: @@ -317,8 +314,8 @@ Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) @@ -389,15 +386,8 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/de_iter.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fsck.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/repair.Po@am__quote@ # am--include-marker - -$(am__depfiles_remade): - @$(MKDIR_P) $(@D) - @echo '# dummy' >$@-t && $(am__mv) $@-t $@ - -am--depfiles: $(am__depfiles_remade) +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fsck.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/repair.Po@am__quote@ .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @@ -481,10 +471,7 @@ cscopelist-am: $(am__tagged_files) distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags -distdir: $(BUILT_SOURCES) - $(MAKE) $(AM_MAKEFLAGS) distdir-am - -distdir-am: $(DISTFILES) +distdir: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ @@ -557,9 +544,7 @@ clean-am: clean-generic clean-libtool clean-sbinPROGRAMS \ mostlyclean-am distclean: distclean-am - -rm -f ./$(DEPDIR)/de_iter.Po - -rm -f ./$(DEPDIR)/fsck.Po - -rm -f ./$(DEPDIR)/repair.Po + -rm -rf ./$(DEPDIR) -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags @@ -605,9 +590,7 @@ install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am - -rm -f ./$(DEPDIR)/de_iter.Po - -rm -f ./$(DEPDIR)/fsck.Po - -rm -f ./$(DEPDIR)/repair.Po + -rm -rf ./$(DEPDIR) -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic @@ -628,9 +611,9 @@ uninstall-am: uninstall-sbinPROGRAMS .MAKE: install-am install-strip -.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ - clean-generic clean-libtool clean-sbinPROGRAMS cscopelist-am \ - ctags ctags-am distclean distclean-compile distclean-generic \ +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-sbinPROGRAMS cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-data \ install-data-am install-dvi install-dvi-am install-exec \ diff --git a/fsck/de_iter.c b/fsck/de_iter.c deleted file mode 100644 index bc95c49..0000000 --- a/fsck/de_iter.c +++ /dev/null @@ -1,313 +0,0 @@ -// 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 00d1ca7..77272aa 100644 --- a/fsck/fsck.c +++ b/fsck/fsck.c @@ -15,8 +15,10 @@ #include "exfat_ondisk.h" #include "libexfat.h" -#include "fsck.h" #include "repair.h" +#include "exfat_fs.h" +#include "exfat_dir.h" +#include "fsck.h" struct fsck_user_input { struct exfat_user_input ei; @@ -25,24 +27,6 @@ struct fsck_user_input { #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 @@ -59,12 +43,7 @@ struct exfat_stat { long fixed_count; }; -struct path_resolve_ctx { - struct exfat_inode *ancestors[255]; - __le16 utf16_path[PATH_MAX + 2]; - char local_path[PATH_MAX * MB_LEN_MAX + 1]; -}; - +struct exfat_fsck exfat_fsck; struct exfat_stat exfat_stat; struct path_resolve_ctx path_resolve_ctx; @@ -73,10 +52,12 @@ static struct option opts[] = { {"repair-yes", no_argument, NULL, 'y' }, {"repair-no", no_argument, NULL, 'n' }, {"repair-auto", no_argument, NULL, 'p' }, + {"rescue", no_argument, NULL, 's' }, {"version", no_argument, NULL, 'V' }, {"verbose", no_argument, NULL, 'v' }, {"help", no_argument, NULL, 'h' }, {"?", no_argument, NULL, '?' }, + {"ignore-bad-fs", no_argument, NULL, 'b' }, {NULL, 0, NULL, 0 } }; @@ -88,6 +69,8 @@ static void usage(char *name) fprintf(stderr, "\t-n | --repair-no No repair\n"); fprintf(stderr, "\t-p | --repair-auto Repair automatically\n"); fprintf(stderr, "\t-a Repair automatically\n"); + fprintf(stderr, "\t-b | --ignore-bad-fs Try to recover even if exfat is not found\n"); + fprintf(stderr, "\t-s | --rescue Assign orphaned clusters to files\n"); fprintf(stderr, "\t-V | --version Show version\n"); fprintf(stderr, "\t-v | --verbose Print debug\n"); fprintf(stderr, "\t-h | --help Show help\n"); @@ -97,319 +80,32 @@ static void usage(char *name) #define fsck_err(parent, inode, fmt, ...) \ ({ \ - resolve_path_parent(&path_resolve_ctx, \ + exfat_resolve_path_parent(&path_resolve_ctx, \ parent, inode); \ exfat_err("ERROR: %s: " fmt, \ path_resolve_ctx.local_path, \ ##__VA_ARGS__); \ }) -static struct exfat_inode *alloc_exfat_inode(__u16 attr) -{ - struct exfat_inode *node; - int size; - - size = offsetof(struct exfat_inode, name) + NAME_BUFFER_SIZE; - node = (struct exfat_inode *)calloc(1, size); - if (!node) { - exfat_err("failed to allocate exfat_node\n"); - return NULL; - } - - node->parent = NULL; - INIT_LIST_HEAD(&node->children); - INIT_LIST_HEAD(&node->sibling); - INIT_LIST_HEAD(&node->list); - - node->last_pclus = EXFAT_EOF_CLUSTER; - node->attr = attr; - if (attr & ATTR_SUBDIR) - exfat_stat.dir_count++; - else - exfat_stat.file_count++; - return node; -} - -static void free_exfat_inode(struct exfat_inode *node) -{ - free(node); -} - -static void inode_free_children(struct exfat_inode *dir, bool file_only) -{ - struct exfat_inode *node, *i; - - list_for_each_entry_safe(node, i, &dir->children, sibling) { - if (file_only) { - if (!(node->attr & ATTR_SUBDIR)) { - list_del(&node->sibling); - free_exfat_inode(node); - } - } else { - list_del(&node->sibling); - list_del(&node->list); - free_exfat_inode(node); - } - } -} - -static void inode_free_file_children(struct exfat_inode *dir) -{ - inode_free_children(dir, true); -} - -/* delete @child and all ancestors that does not have - * children - */ -static void inode_free_ancestors(struct exfat_inode *child) -{ - struct exfat_inode *parent; - - if (!list_empty(&child->children)) - return; - - do { - if (!(child->attr & ATTR_SUBDIR)) { - exfat_err("not directory.\n"); - return; - } - - parent = child->parent; - list_del(&child->sibling); - free_exfat_inode(child); - - child = parent; - } while (child && list_empty(&child->children)); - - return; -} - -static void free_exfat(struct exfat *exfat) -{ - int i; - - if (exfat) { - if (exfat->bs) - free(exfat->bs); - if (exfat->alloc_bitmap) - free(exfat->alloc_bitmap); - if (exfat->disk_bitmap) - free(exfat->disk_bitmap); - for (i = 0; i < 2; i++) { - if (exfat->buffer_desc[i].buffer) - free(exfat->buffer_desc[i].buffer); - if (exfat->buffer_desc[i].dirty) - free(exfat->buffer_desc[i].dirty); - } - free(exfat); - } -} - -static int init_exfat(struct exfat *exfat, struct pbr *bs) -{ - int i; - - INIT_LIST_HEAD(&exfat->dir_list); - exfat->bs = bs; - exfat->clus_count = le32_to_cpu(bs->bsx.clu_count); - exfat->clus_size = EXFAT_CLUSTER_SIZE(bs); - exfat->sect_size = EXFAT_SECTOR_SIZE(bs); - - /* TODO: bitmap could be very large. */ - exfat->alloc_bitmap = (char *)calloc(1, - EXFAT_BITMAP_SIZE(exfat->clus_count)); - if (!exfat->alloc_bitmap) { - exfat_err("failed to allocate bitmap\n"); - goto err; - } - - exfat->disk_bitmap = (char *)malloc( - EXFAT_BITMAP_SIZE(exfat->clus_count)); - if (!exfat->disk_bitmap) { - exfat_err("failed to allocate bitmap\n"); - goto err; - } - - /* allocate cluster buffers */ - for (i = 0; i < 2; i++) { - exfat->buffer_desc[i].buffer = - (char *)malloc(exfat->clus_size); - if (!exfat->buffer_desc[i].buffer) - goto err; - exfat->buffer_desc[i].dirty = - (char *)calloc( - (exfat->clus_size / exfat->sect_size), 1); - if (!exfat->buffer_desc[i].dirty) - goto err; - } - return 0; -err: - free_exfat(exfat); - return -ENOMEM; -} - -static void exfat_free_dir_list(struct exfat *exfat) -{ - struct exfat_inode *dir, *i; - - list_for_each_entry_safe(dir, i, &exfat->dir_list, list) { - inode_free_file_children(dir); - list_del(&dir->list); - free_exfat_inode(dir); - } -} - -/* - * get references of ancestors that include @child until the count of - * ancesters is not larger than @count and the count of characters of - * their names is not larger than @max_char_len. - * return true if root is reached. - */ -bool get_ancestors(struct exfat_inode *child, - struct exfat_inode **ancestors, int count, - int max_char_len, - int *ancestor_count) -{ - struct exfat_inode *dir; - int name_len, char_len; - int root_depth, depth, i; - - root_depth = 0; - char_len = 0; - max_char_len += 1; - - dir = child; - while (dir) { - name_len = exfat_utf16_len(dir->name, NAME_BUFFER_SIZE); - if (char_len + name_len > max_char_len) - break; - - /* include '/' */ - char_len += name_len + 1; - root_depth++; - - dir = dir->parent; - } - - depth = MIN(root_depth, count); - - for (dir = child, i = depth - 1; i >= 0; dir = dir->parent, i--) - ancestors[i] = dir; - - *ancestor_count = depth; - return dir == NULL; -} - -static int resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child) -{ - int depth, i; - int name_len; - __le16 *utf16_path; - static const __le16 utf16_slash = cpu_to_le16(0x002F); - static const __le16 utf16_null = cpu_to_le16(0x0000); - size_t in_size; - - ctx->local_path[0] = '\0'; - - get_ancestors(child, - ctx->ancestors, - sizeof(ctx->ancestors) / sizeof(ctx->ancestors[0]), - PATH_MAX, - &depth); - - utf16_path = ctx->utf16_path; - for (i = 0; i < depth; i++) { - name_len = exfat_utf16_len(ctx->ancestors[i]->name, - NAME_BUFFER_SIZE); - memcpy((char *)utf16_path, (char *)ctx->ancestors[i]->name, - name_len * 2); - utf16_path += name_len; - memcpy((char *)utf16_path, &utf16_slash, sizeof(utf16_slash)); - utf16_path++; - } - - if (depth > 0) - utf16_path--; - memcpy((char *)utf16_path, &utf16_null, sizeof(utf16_null)); - utf16_path++; - - in_size = (utf16_path - ctx->utf16_path) * sizeof(__le16); - return exfat_utf16_dec(ctx->utf16_path, in_size, - ctx->local_path, sizeof(ctx->local_path)); -} - -static int resolve_path_parent(struct path_resolve_ctx *ctx, - struct exfat_inode *parent, struct exfat_inode *child) -{ - int ret; - struct exfat_inode *old; - - old = child->parent; - child->parent = parent; - - ret = resolve_path(ctx, child); - child->parent = old; - return ret; -} - #define repair_file_ask(iter, inode, code, fmt, ...) \ ({ \ - resolve_path_parent(&path_resolve_ctx, \ - (iter)->parent, inode); \ - exfat_repair_ask((iter)->exfat, code, \ - "ERROR: %s: " fmt, \ - path_resolve_ctx.local_path, \ - ##__VA_ARGS__); \ + if (inode) \ + exfat_resolve_path_parent(&path_resolve_ctx, \ + (iter)->parent, inode); \ + else \ + exfat_resolve_path(&path_resolve_ctx, \ + (iter)->parent); \ + exfat_repair_ask(&exfat_fsck, code, \ + "ERROR: %s: " fmt " at %#" PRIx64, \ + path_resolve_ctx.local_path, \ + ##__VA_ARGS__, \ + exfat_de_iter_device_offset(iter)); \ }) -static inline bool heap_clus(struct exfat *exfat, clus_t clus) -{ - return clus >= EXFAT_FIRST_CLUSTER && - (clus - EXFAT_FIRST_CLUSTER) < exfat->clus_count; -} - -int get_next_clus(struct exfat *exfat, struct exfat_inode *node, - clus_t clus, clus_t *next) -{ - off_t offset; - - *next = EXFAT_EOF_CLUSTER; - - if (!heap_clus(exfat, clus)) - return -EINVAL; - - if (node->is_contiguous) { - *next = clus + 1; - return 0; - } - - offset = (off_t)le32_to_cpu(exfat->bs->bsx.fat_offset) << - exfat->bs->bsx.sect_size_bits; - offset += sizeof(clus_t) * clus; - - if (exfat_read(exfat->blk_dev->dev_fd, next, sizeof(*next), offset) - != sizeof(*next)) - return -EIO; - *next = le32_to_cpu(*next); - return 0; -} - -static int set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus) -{ - off_t offset; - - offset = le32_to_cpu(exfat->bs->bsx.fat_offset) << - exfat->bs->bsx.sect_size_bits; - offset += sizeof(clus_t) * clus; - - if (exfat_write(exfat->blk_dev->dev_fd, &next_clus, sizeof(next_clus), - offset) != sizeof(next_clus)) - return -EIO; - return 0; -} - -static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) +static int check_clus_chain(struct exfat_de_iter *de_iter, + struct exfat_inode *node) { + struct exfat *exfat = de_iter->exfat; struct exfat_dentry *stream_de; clus_t clus, prev, next; uint64_t count, max_count; @@ -424,9 +120,11 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) /* the first cluster is wrong */ if ((node->size == 0 && node->first_clus != EXFAT_FREE_CLUSTER) || - (node->size > 0 && !heap_clus(exfat, node->first_clus))) { - if (repair_file_ask(&exfat->de_iter, node, - ER_FILE_FIRST_CLUS, "first cluster is wrong")) + (node->size > 0 && !exfat_heap_clus(exfat, node->first_clus))) { + if (repair_file_ask(de_iter, node, + ER_FILE_FIRST_CLUS, + "size %#" PRIx64 ", but the first cluster %#x", + node->size, node->first_clus)) goto truncate_file; else return -EINVAL; @@ -436,11 +134,11 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) if (count >= max_count) { if (node->is_contiguous) break; - if (repair_file_ask(&exfat->de_iter, node, - ER_FILE_SMALLER_SIZE, - "more clusters are allocated. " - "truncate to %" PRIu64 " bytes", - count * exfat->clus_size)) + if (repair_file_ask(de_iter, node, + ER_FILE_SMALLER_SIZE, + "more clusters are allocated. truncate to %" + PRIu64 " bytes", + count * exfat->clus_size)) goto truncate_file; else return -EINVAL; @@ -450,62 +148,67 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) * This cluster is already allocated. it may be shared with * the other file, or there is a loop in cluster chain. */ - if (EXFAT_BITMAP_GET(exfat->alloc_bitmap, - clus - EXFAT_FIRST_CLUSTER)) { - if (repair_file_ask(&exfat->de_iter, node, - ER_FILE_DUPLICATED_CLUS, - "cluster is already allocated for " - "the other file. truncated to %" - PRIu64 " bytes", - count * exfat->clus_size)) + if (exfat_bitmap_get(exfat->alloc_bitmap, clus)) { + if (repair_file_ask(de_iter, node, + ER_FILE_DUPLICATED_CLUS, + "cluster is already allocated for the other file. truncated to %" + PRIu64 " bytes", + count * exfat->clus_size)) goto truncate_file; else return -EINVAL; } - if (!EXFAT_BITMAP_GET(exfat->disk_bitmap, - clus - EXFAT_FIRST_CLUSTER)) { - if (repair_file_ask(&exfat->de_iter, node, - ER_FILE_INVALID_CLUS, - "cluster is marked as free. truncate to %" PRIu64 " bytes", - count * exfat->clus_size)) - goto truncate_file; - - else + if (!exfat_bitmap_get(exfat->disk_bitmap, clus)) { + if (!repair_file_ask(de_iter, node, + ER_FILE_INVALID_CLUS, + "cluster %#x is marked as free", + clus)) return -EINVAL; } /* This cluster is allocated or not */ - if (get_next_clus(exfat, node, clus, &next)) + if (exfat_get_inode_next_clus(exfat, node, clus, &next)) goto truncate_file; - if (!node->is_contiguous) { - if (!heap_clus(exfat, next) && - next != EXFAT_EOF_CLUSTER) { - if (repair_file_ask(&exfat->de_iter, node, - ER_FILE_INVALID_CLUS, - "broken cluster chain. " - "truncate to %" - PRIu64 " bytes", - count * exfat->clus_size)) + if (next == EXFAT_BAD_CLUSTER) { + if (repair_file_ask(de_iter, node, + ER_FILE_INVALID_CLUS, + "BAD cluster. truncate to %" + PRIu64 " bytes", + count * exfat->clus_size)) + goto truncate_file; + else + return -EINVAL; + } else if (!node->is_contiguous) { + if (next != EXFAT_EOF_CLUSTER && + !exfat_heap_clus(exfat, next)) { + if (repair_file_ask(de_iter, node, + ER_FILE_INVALID_CLUS, + "broken cluster chain. truncate to %" + PRIu64 " bytes", + (count + 1) * exfat->clus_size)) { + count++; + prev = clus; + exfat_bitmap_set(exfat->alloc_bitmap, + clus); goto truncate_file; - - else + } else { return -EINVAL; + } } } count++; - EXFAT_BITMAP_SET(exfat->alloc_bitmap, - clus - EXFAT_FIRST_CLUSTER); + exfat_bitmap_set(exfat->alloc_bitmap, clus); prev = clus; clus = next; } if (count < max_count) { - if (repair_file_ask(&exfat->de_iter, node, - ER_FILE_LARGER_SIZE, "less clusters are allocated. " - "truncates to %" PRIu64 " bytes", - count * exfat->clus_size)) + if (repair_file_ask(de_iter, node, ER_FILE_LARGER_SIZE, + "less clusters are allocated. truncates to %" + PRIu64 " bytes", + count * exfat->clus_size)) goto truncate_file; else return -EINVAL; @@ -514,74 +217,83 @@ static int check_clus_chain(struct exfat *exfat, struct exfat_inode *node) return 0; truncate_file: node->size = count * exfat->clus_size; - if (!heap_clus(exfat, prev)) + if (!exfat_heap_clus(exfat, prev)) node->first_clus = EXFAT_FREE_CLUSTER; - exfat_de_iter_get_dirty(&exfat->de_iter, 1, &stream_de); + exfat_de_iter_get_dirty(de_iter, 1, &stream_de); if (count * exfat->clus_size < - le64_to_cpu(stream_de->stream_valid_size)) + le64_to_cpu(stream_de->stream_valid_size)) stream_de->stream_valid_size = cpu_to_le64( - count * exfat->clus_size); - if (!heap_clus(exfat, prev)) + count * exfat->clus_size); + if (!exfat_heap_clus(exfat, prev)) stream_de->stream_start_clu = EXFAT_FREE_CLUSTER; stream_de->stream_size = cpu_to_le64( - count * exfat->clus_size); + count * exfat->clus_size); /* remaining clusters will be freed while FAT is compared with * alloc_bitmap. */ - if (!node->is_contiguous && heap_clus(exfat, prev)) - return set_fat(exfat, prev, EXFAT_EOF_CLUSTER); + if (!node->is_contiguous && exfat_heap_clus(exfat, prev)) { + if (exfat_set_fat(exfat, prev, EXFAT_EOF_CLUSTER)) + return -EIO; + } return 1; } -static bool root_get_clus_count(struct exfat *exfat, struct exfat_inode *node, - clus_t *clus_count) +static int root_check_clus_chain(struct exfat *exfat, + struct exfat_inode *node, + clus_t *clus_count) { - clus_t clus; + clus_t clus, next, prev = EXFAT_EOF_CLUSTER; + + if (!exfat_heap_clus(exfat, node->first_clus)) + goto out_trunc; clus = node->first_clus; *clus_count = 0; do { - if (!heap_clus(exfat, clus)) { - exfat_err("/: bad cluster. 0x%x\n", clus); - return false; + if (exfat_bitmap_get(exfat->alloc_bitmap, clus)) { + if (exfat_repair_ask(&exfat_fsck, + ER_FILE_DUPLICATED_CLUS, + "ERROR: the cluster chain of root is cyclic")) + goto out_trunc; + return -EINVAL; } - if (EXFAT_BITMAP_GET(exfat->alloc_bitmap, - clus - EXFAT_FIRST_CLUSTER)) { - exfat_err("/: cluster is already allocated, or " - "there is a loop in cluster chain\n"); - return false; - } + exfat_bitmap_set(exfat->alloc_bitmap, clus); - EXFAT_BITMAP_SET(exfat->alloc_bitmap, - clus - EXFAT_FIRST_CLUSTER); + if (exfat_get_inode_next_clus(exfat, node, clus, &next)) { + exfat_err("ERROR: failed to read the fat entry of root"); + goto out_trunc; + } - if (get_next_clus(exfat, node, clus, &clus) != 0) { - exfat_err("/: broken cluster chain\n"); - return false; + if (next != EXFAT_EOF_CLUSTER && !exfat_heap_clus(exfat, next)) { + if (exfat_repair_ask(&exfat_fsck, + ER_FILE_INVALID_CLUS, + "ERROR: the cluster chain of root is broken")) { + if (next != EXFAT_BAD_CLUSTER) { + prev = clus; + (*clus_count)++; + } + goto out_trunc; + } + return -EINVAL; } + prev = clus; + clus = next; (*clus_count)++; } while (clus != EXFAT_EOF_CLUSTER); - return true; -} - -static off_t exfat_s2o(struct exfat *exfat, off_t sect) -{ - return sect << exfat->bs->bsx.sect_size_bits; -} - -off_t exfat_c2o(struct exfat *exfat, unsigned int clus) -{ - if (clus < EXFAT_FIRST_CLUSTER) - return ~0L; - return exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset) + - ((off_t)(clus - EXFAT_FIRST_CLUSTER) << - exfat->bs->bsx.sect_per_clus_bits)); + return 0; +out_trunc: + if (!exfat_heap_clus(exfat, prev)) { + exfat_err("ERROR: the start cluster of root is wrong\n"); + return -EINVAL; + } + node->size = *clus_count * exfat->clus_size; + return exfat_set_fat(exfat, prev, EXFAT_EOF_CLUSTER); } static int boot_region_checksum(int dev_fd, @@ -633,9 +345,6 @@ 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; @@ -782,7 +491,9 @@ free_sector: return ret; } -static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) +static int exfat_boot_region_check(struct exfat_blk_dev *blkdev, + struct pbr **bs, + bool ignore_bad_fs_name) { struct pbr *boot_sect; unsigned int sect_size; @@ -793,26 +504,35 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) if (boot_sect == NULL) return -ENOMEM; - if (exfat_read(exfat->blk_dev->dev_fd, boot_sect, + if (exfat_read(blkdev->dev_fd, boot_sect, sizeof(*boot_sect), 0) != (ssize_t)sizeof(*boot_sect)) { exfat_err("failed to read Main boot sector\n"); + free(boot_sect); return -EIO; } + if (memcmp(boot_sect->bpb.oem_name, "EXFAT ", 8) != 0 && + !ignore_bad_fs_name) { + exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n"); + free(boot_sect); + return -ENOTSUP; + } + sect_size = 1 << boot_sect->bsx.sect_size_bits; free(boot_sect); /* check boot regions */ - ret = read_boot_region(exfat->blk_dev, bs, + ret = read_boot_region(blkdev, bs, BOOT_SEC_IDX, sect_size, true); - if (ret == -EINVAL && exfat_repair_ask(exfat, ER_BS_BOOT_REGION, - "boot region is corrupted. try to restore the region from backup" + if (ret == -EINVAL && + exfat_repair_ask(&exfat_fsck, ER_BS_BOOT_REGION, + "boot region is corrupted. try to restore the region from backup" )) { const unsigned int sector_sizes[] = {512, 4096, 1024, 2048}; unsigned int i; if (sect_size >= 512 && sect_size <= EXFAT_MAX_SECTOR_SIZE) { - ret = read_boot_region(exfat->blk_dev, bs, + ret = read_boot_region(blkdev, bs, BACKUP_BOOT_SEC_IDX, sect_size, false); if (!ret) @@ -823,7 +543,7 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) if (sector_sizes[i] == sect_size) continue; - ret = read_boot_region(exfat->blk_dev, bs, + ret = read_boot_region(blkdev, bs, BACKUP_BOOT_SEC_IDX, sector_sizes[i], false); if (!ret) { @@ -836,7 +556,7 @@ static int exfat_boot_region_check(struct exfat *exfat, struct pbr **bs) return ret; restore: - ret = restore_boot_region(exfat->blk_dev, sect_size); + ret = restore_boot_region(blkdev, sect_size); if (ret) { exfat_err("failed to restore boot region from backup\n"); free(*bs); @@ -845,38 +565,20 @@ restore: 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) +static uint16_t file_calc_checksum(struct exfat_de_iter *iter) { - __le16 checksum; + uint16_t checksum; struct exfat_dentry *file_de, *de; int i; checksum = 0; exfat_de_iter_get(iter, 0, &file_de); - dentry_calc_checksum(file_de, &checksum, true); + exfat_calc_dentry_checksum(file_de, &checksum, true); for (i = 1; i <= file_de->file_num_ext; i++) { exfat_de_iter_get(iter, i, &de); - dentry_calc_checksum(de, &checksum, false); + exfat_calc_dentry_checksum(de, &checksum, false); } - return checksum; } @@ -892,7 +594,7 @@ static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node) uint16_t checksum; bool valid = true; - ret = check_clus_chain(exfat, node); + ret = check_clus_chain(iter, node); if (ret < 0) return ret; @@ -906,7 +608,7 @@ static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node) if (node->size == 0 && node->is_contiguous) { if (repair_file_ask(iter, node, ER_FILE_ZERO_NOFAT, - "empty, but has no Fat chain\n")) { + "empty, but has no Fat chain")) { exfat_de_iter_get_dirty(iter, 1, &dentry); dentry->stream_flags &= ~EXFAT_SF_CONTIGUOUS; ret = 1; @@ -925,61 +627,184 @@ static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node) checksum = file_calc_checksum(iter); exfat_de_iter_get(iter, 0, &dentry); if (checksum != le16_to_cpu(dentry->file_checksum)) { - if (repair_file_ask(iter, node, ER_DE_CHECKSUM, - "the checksum of a file is wrong")) { - exfat_de_iter_get_dirty(iter, 0, &dentry); - dentry->file_checksum = cpu_to_le16(checksum); - ret = 1; - } else - valid = false; + exfat_de_iter_get_dirty(iter, 0, &dentry); + dentry->file_checksum = cpu_to_le16(checksum); + ret = 1; } return valid ? ret : -EINVAL; } -static int read_file_dentries(struct exfat_de_iter *iter, - struct exfat_inode **new_node, int *skip_dentries) +static int handle_duplicated_filename(struct exfat_de_iter *iter, + struct exfat_inode *inode) { - struct exfat_dentry *file_de, *stream_de, *name_de; - struct exfat_inode *node; - int i, ret; + int ret; + struct exfat_lookup_filter filter; + char filename[PATH_MAX + 1] = {0}; + + ret = exfat_lookup_file_by_utf16name(iter->exfat, iter->parent, + inode->name, &filter); + if (ret) + return ret; + + free(filter.out.dentry_set); + + /* Hash is same, but filename is not same */ + if (exfat_de_iter_device_offset(iter) == filter.out.dev_offset) + return 0; + + ret = exfat_utf16_dec(inode->name, NAME_BUFFER_SIZE, filename, + PATH_MAX); + if (ret < 0) { + exfat_err("failed to decode filename\n"); + return ret; + } + + return exfat_repair_rename_ask(&exfat_fsck, iter, filename, + ER_DE_DUPLICATED_NAME, "filename is duplicated"); +} + +static int check_name_dentry_set(struct exfat_de_iter *iter, + struct exfat_inode *inode) +{ + struct exfat_dentry *stream_de; + size_t name_len; + __u16 hash; + + exfat_de_iter_get(iter, 1, &stream_de); + + name_len = exfat_utf16_len(inode->name, NAME_BUFFER_SIZE); + if (stream_de->stream_name_len != name_len) { + if (repair_file_ask(iter, NULL, ER_DE_NAME_LEN, + "the name length of a file is wrong")) { + exfat_de_iter_get_dirty(iter, 1, &stream_de); + stream_de->stream_name_len = (__u8)name_len; + } else { + return -EINVAL; + } + } + + hash = exfat_calc_name_hash(iter->exfat, inode->name, (int)name_len); + if (cpu_to_le16(hash) != stream_de->stream_name_hash) { + if (repair_file_ask(iter, NULL, ER_DE_NAME_HASH, + "the name hash of a file is wrong")) { + exfat_de_iter_get_dirty(iter, 1, &stream_de); + stream_de->stream_name_hash = cpu_to_le16(hash); + } else { + return -EINVAL; + } + } + + if (BITMAP_GET(iter->name_hash_bitmap, hash)) { + int ret = handle_duplicated_filename(iter, inode); + + if (ret) + return ret; + } else + BITMAP_SET(iter->name_hash_bitmap, hash); + + return 0; +} + +const __le16 MSDOS_DOT[ENTRY_NAME_MAX] = {cpu_to_le16(46), 0, }; +const __le16 MSDOS_DOTDOT[ENTRY_NAME_MAX] = {cpu_to_le16(46), cpu_to_le16(46), 0, }; + +static int handle_dot_dotdot_filename(struct exfat_de_iter *iter, + struct exfat_dentry *dentry, + int strm_name_len) +{ + char *filename; + + if (!memcmp(dentry->name_unicode, MSDOS_DOT, strm_name_len * 2)) + filename = "."; + else if (!memcmp(dentry->name_unicode, MSDOS_DOTDOT, + strm_name_len * 2)) + filename = ".."; + else + return 0; - /* TODO: mtime, atime, ... */ + return exfat_repair_rename_ask(&exfat_fsck, iter, filename, + ER_DE_DOT_NAME, "filename is not allowed"); +} + +static int read_file_dentry_set(struct exfat_de_iter *iter, + struct exfat_inode **new_node, int *skip_dentries) +{ + struct exfat_dentry *file_de, *stream_de, *dentry; + struct exfat_inode *node = NULL; + int i, ret; + bool need_delete = false; + uint16_t checksum; ret = exfat_de_iter_get(iter, 0, &file_de); if (ret || file_de->type != EXFAT_FILE) { - exfat_err("failed to get file dentry. %d\n", ret); + exfat_err("failed to get file dentry\n"); return -EINVAL; } + + checksum = file_calc_checksum(iter); + if (checksum != le16_to_cpu(file_de->file_checksum)) { + if (repair_file_ask(iter, NULL, ER_DE_CHECKSUM, + "the checksum of a file is wrong")) + need_delete = true; + *skip_dentries = 1; + goto skip_dset; + } + + if (file_de->file_num_ext < 2) { + if (repair_file_ask(iter, NULL, ER_DE_SECONDARY_COUNT, + "a file has too few secondary count. %d", + file_de->file_num_ext)) + need_delete = true; + *skip_dentries = 1; + goto skip_dset; + } + ret = exfat_de_iter_get(iter, 1, &stream_de); if (ret || stream_de->type != EXFAT_STREAM) { - exfat_err("failed to get stream dentry. %d\n", ret); - return -EINVAL; + if (repair_file_ask(iter, NULL, ER_DE_STREAM, + "failed to get stream dentry")) + need_delete = true; + *skip_dentries = 2; + goto skip_dset; } *new_node = NULL; - node = alloc_exfat_inode(le16_to_cpu(file_de->file_attr)); + node = exfat_alloc_inode(le16_to_cpu(file_de->file_attr)); if (!node) return -ENOMEM; - if (file_de->file_num_ext < 2) { - exfat_err("too few secondary count. %d\n", - file_de->file_num_ext); - free_exfat_inode(node); - return -EINVAL; - } - - for (i = 2; i <= file_de->file_num_ext; i++) { - ret = exfat_de_iter_get(iter, i, &name_de); - if (ret || name_de->type != EXFAT_NAME) { - exfat_err("failed to get name dentry. %d\n", ret); - ret = -EINVAL; - goto err; + for (i = 2; i <= MIN(file_de->file_num_ext, 1 + MAX_NAME_DENTRIES); i++) { + ret = exfat_de_iter_get(iter, i, &dentry); + if (ret || dentry->type != EXFAT_NAME) { + if (i > 2 && repair_file_ask(iter, NULL, ER_DE_NAME, + "failed to get name dentry")) { + exfat_de_iter_get_dirty(iter, 0, &file_de); + file_de->file_num_ext = i - 1; + break; + } + *skip_dentries = i + 1; + goto skip_dset; } memcpy(node->name + - (i-2) * ENTRY_NAME_MAX, name_de->name_unicode, - sizeof(name_de->name_unicode)); + (i - 2) * ENTRY_NAME_MAX, dentry->name_unicode, + sizeof(dentry->name_unicode)); + } + + ret = check_name_dentry_set(iter, node); + if (ret) { + *skip_dentries = file_de->file_num_ext + 1; + goto skip_dset; + } + + if (file_de->file_num_ext == 2 && stream_de->stream_name_len <= 2) { + ret = handle_dot_dotdot_filename(iter, dentry, + stream_de->stream_name_len); + if (ret < 0) { + *skip_dentries = file_de->file_num_ext + 1; + goto skip_dset; + } } node->first_clus = le32_to_cpu(stream_de->stream_start_clu); @@ -988,27 +813,41 @@ static int read_file_dentries(struct exfat_de_iter *iter, node->size = le64_to_cpu(stream_de->stream_size); if (node->size < le64_to_cpu(stream_de->stream_valid_size)) { + *skip_dentries = file_de->file_num_ext + 1; if (repair_file_ask(iter, node, ER_FILE_VALID_SIZE, - "valid size %" PRIu64 " greater than size %" PRIu64, - le64_to_cpu(stream_de->stream_valid_size), - node->size)) { + "valid size %" PRIu64 " greater than size %" PRIu64, + le64_to_cpu(stream_de->stream_valid_size), + node->size)) { exfat_de_iter_get_dirty(iter, 1, &stream_de); stream_de->stream_valid_size = stream_de->stream_size; } else { - ret = -EINVAL; - goto err; + *skip_dentries = file_de->file_num_ext + 1; + goto skip_dset; } } *skip_dentries = (file_de->file_num_ext + 1); *new_node = node; return 0; -err: - *skip_dentries = 0; +skip_dset: + if (need_delete) { + exfat_de_iter_get_dirty(iter, 0, &dentry); + dentry->type &= EXFAT_DELETE; + } + for (i = 1; i < *skip_dentries; i++) { + exfat_de_iter_get(iter, i, &dentry); + if (dentry->type == EXFAT_FILE) + break; + if (need_delete) { + exfat_de_iter_get_dirty(iter, i, &dentry); + dentry->type &= EXFAT_DELETE; + } + } + *skip_dentries = i; *new_node = NULL; - free_exfat_inode(node); - return ret; + exfat_free_inode(node); + return need_delete ? 1 : -EINVAL; } static int read_file(struct exfat_de_iter *de_iter, @@ -1019,75 +858,40 @@ static int read_file(struct exfat_de_iter *de_iter, *new_node = NULL; - ret = read_file_dentries(de_iter, &node, dentry_count); + ret = read_file_dentry_set(de_iter, &node, dentry_count); if (ret) return ret; ret = check_inode(de_iter, node); if (ret < 0) { - free_exfat_inode(node); + exfat_free_inode(node); return -EINVAL; } + if (node->attr & ATTR_SUBDIR) + exfat_stat.dir_count++; + else + exfat_stat.file_count++; *new_node = node; return ret; } -static bool read_volume_label(struct exfat_de_iter *iter) +static int read_bitmap(struct exfat *exfat) { - struct exfat *exfat; + struct exfat_lookup_filter filter = { + .in.type = EXFAT_BITMAP, + .in.dentry_count = 0, + .in.filter = NULL, + .in.param = NULL, + }; 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; + int retval; - 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; + retval = exfat_lookup_dentry_set(exfat, exfat->root, &filter); + if (retval) + return retval; + dentry = filter.out.dentry_set; exfat_debug("start cluster %#x, size %#" PRIx64 "\n", le32_to_cpu(dentry->bitmap_start_clu), le64_to_cpu(dentry->bitmap_size)); @@ -1096,47 +900,82 @@ static bool read_bitmap(struct exfat_de_iter *iter) DIV_ROUND_UP(exfat->clus_count, 8)) { exfat_err("invalid size of allocation bitmap. 0x%" PRIx64 "\n", le64_to_cpu(dentry->bitmap_size)); - return false; + return -EINVAL; } - if (!heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) { + if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) { exfat_err("invalid start cluster of allocate bitmap. 0x%x\n", le32_to_cpu(dentry->bitmap_start_clu)); - return false; + return -EINVAL; } exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu); exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8); - exfat_bitmap_set_range(exfat, le64_to_cpu(dentry->bitmap_start_clu), - DIV_ROUND_UP(exfat->disk_bitmap_size, - exfat->clus_size)); + exfat_bitmap_set_range(exfat, exfat->alloc_bitmap, + le64_to_cpu(dentry->bitmap_start_clu), + DIV_ROUND_UP(exfat->disk_bitmap_size, + exfat->clus_size)); + free(filter.out.dentry_set); if (exfat_read(exfat->blk_dev->dev_fd, exfat->disk_bitmap, exfat->disk_bitmap_size, exfat_c2o(exfat, exfat->disk_bitmap_clus)) != (ssize_t)exfat->disk_bitmap_size) - return false; + return -EIO; + return 0; +} + +static int decompress_upcase_table(const __le16 *in_table, size_t in_len, + __u16 *out_table, size_t out_len) +{ + size_t i, k; + uint16_t ch; + + if (in_len > out_len) + return -E2BIG; + + for (k = 0; k < out_len; k++) + out_table[k] = k; + + for (i = 0, k = 0; i < in_len && k < out_len; i++) { + ch = le16_to_cpu(in_table[i]); + + if (ch == 0xFFFF && i + 1 < in_len) { + uint16_t len = le16_to_cpu(in_table[++i]); - return true; + k += len; + } else { + out_table[k++] = ch; + } + } + return 0; } -static bool read_upcase_table(struct exfat_de_iter *iter) +static int read_upcase_table(struct exfat *exfat) { - struct exfat_dentry *dentry; - struct exfat *exfat; + struct exfat_lookup_filter filter = { + .in.type = EXFAT_UPCASE, + .in.dentry_count = 0, + .in.filter = NULL, + .in.param = NULL, + }; + struct exfat_dentry *dentry = NULL; + __le16 *upcase = NULL; + int retval; ssize_t size; - __le16 *upcase; __le32 checksum; - exfat = iter->exfat; + retval = exfat_lookup_dentry_set(exfat, exfat->root, &filter); + if (retval) + return retval; - if (exfat_de_iter_get(iter, 0, &dentry)) - return false; + dentry = filter.out.dentry_set; - if (!heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) { + if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) { exfat_err("invalid start cluster of upcase table. 0x%x\n", le32_to_cpu(dentry->upcase_start_clu)); - return false; + retval = -EINVAL; + goto out; } size = (ssize_t)le64_to_cpu(dentry->upcase_size); @@ -1144,21 +983,23 @@ static bool read_upcase_table(struct exfat_de_iter *iter) size == 0 || size % sizeof(__le16)) { exfat_err("invalid size of upcase table. 0x%" PRIx64 "\n", le64_to_cpu(dentry->upcase_size)); - return false; + retval = -EINVAL; + goto out; } upcase = (__le16 *)malloc(size); if (!upcase) { exfat_err("failed to allocate upcase table\n"); - return false; + retval = -ENOMEM; + goto out; } if (exfat_read(exfat->blk_dev->dev_fd, upcase, size, exfat_c2o(exfat, le32_to_cpu(dentry->upcase_start_clu))) != size) { exfat_err("failed to read upcase table\n"); - free(upcase); - return false; + retval = -EIO; + goto out; } checksum = 0; @@ -1166,33 +1007,52 @@ static bool read_upcase_table(struct exfat_de_iter *iter) if (le32_to_cpu(dentry->upcase_checksum) != checksum) { exfat_err("corrupted upcase table %#x (expected: %#x)\n", checksum, le32_to_cpu(dentry->upcase_checksum)); - free(upcase); - return false; + retval = -EINVAL; + goto out; } - exfat_bitmap_set_range(exfat, le32_to_cpu(dentry->upcase_start_clu), - DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size), - exfat->clus_size)); + exfat_bitmap_set_range(exfat, exfat->alloc_bitmap, + le32_to_cpu(dentry->upcase_start_clu), + DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size), + exfat->clus_size)); + + exfat->upcase_table = calloc(1, + sizeof(uint16_t) * EXFAT_UPCASE_TABLE_CHARS); + if (!exfat->upcase_table) { + retval = -EIO; + goto out; + } - free(upcase); - return true; + decompress_upcase_table(upcase, size / 2, + exfat->upcase_table, EXFAT_UPCASE_TABLE_CHARS); +out: + if (dentry) + free(dentry); + if (upcase) + free(upcase); + return retval; } -static int read_children(struct exfat *exfat, struct exfat_inode *dir) +static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir) { - int ret; + struct exfat *exfat = fsck->exfat; struct exfat_inode *node = NULL; struct exfat_dentry *dentry; - int dentry_count; struct exfat_de_iter *de_iter; + int dentry_count; + int ret; - de_iter = &exfat->de_iter; - ret = exfat_de_iter_init(de_iter, exfat, dir); + de_iter = &fsck->de_iter; + ret = exfat_de_iter_init(de_iter, exfat, dir, fsck->buffer_desc); if (ret == EOF) return 0; else if (ret) return ret; + de_iter->name_hash_bitmap = fsck->name_hash_bitmap; + memset(fsck->name_hash_bitmap, 0, + EXFAT_BITMAP_SIZE(EXFAT_MAX_HASH_COUNT)); + while (1) { ret = exfat_de_iter_get(de_iter, 0, &dentry); if (ret == EOF) { @@ -1210,50 +1070,46 @@ static int read_children(struct exfat *exfat, struct exfat_inode *dir) ret = read_file(de_iter, &node, &dentry_count); if (ret < 0) { exfat_stat.error_count++; - goto err; + break; } else if (ret) { exfat_stat.error_count++; exfat_stat.fixed_count++; } - if ((node->attr & ATTR_SUBDIR) && node->size) { - node->parent = dir; - list_add_tail(&node->sibling, &dir->children); - list_add_tail(&node->list, &exfat->dir_list); - } else - free_exfat_inode(node); - break; - case EXFAT_VOLUME: - if (!read_volume_label(de_iter)) { - exfat_err("failed to verify volume label\n"); - ret = -EINVAL; - goto err; - } - break; - case EXFAT_BITMAP: - if (!read_bitmap(de_iter)) { - exfat_err( - "failed to verify allocation bitmap\n"); - ret = -EINVAL; - goto err; - } - break; - case EXFAT_UPCASE: - if (!read_upcase_table(de_iter)) { - exfat_err( - "failed to verify upcase table\n"); - ret = -EINVAL; - goto err; + if (node) { + if ((node->attr & ATTR_SUBDIR) && node->size) { + node->parent = dir; + list_add_tail(&node->sibling, + &dir->children); + list_add_tail(&node->list, + &exfat->dir_list); + } else { + exfat_free_inode(node); + } } break; case EXFAT_LAST: goto out; + case EXFAT_VOLUME: + case EXFAT_BITMAP: + case EXFAT_UPCASE: + case EXFAT_GUID: + if (dir == exfat->root) + break; + /* fallthrough */ default: if (IS_EXFAT_DELETED(dentry->type)) break; - exfat_err("unknown entry type. 0x%x\n", dentry->type); - ret = -EINVAL; - goto err; + if (repair_file_ask(de_iter, NULL, ER_DE_UNKNOWN, + "unknown entry type %#x at %07" PRIx64, + dentry->type, + exfat_de_iter_file_offset(de_iter))) { + struct exfat_dentry *dentry; + + exfat_de_iter_get_dirty(de_iter, 0, &dentry); + dentry->type &= EXFAT_DELETE; + } + break; } exfat_de_iter_advance(de_iter, dentry_count); @@ -1262,131 +1118,51 @@ out: exfat_de_iter_flush(de_iter); return 0; err: - inode_free_children(dir, false); + exfat_free_children(dir, false); INIT_LIST_HEAD(&dir->children); exfat_de_iter_flush(de_iter); return ret; } -static int write_dirty_fat(struct exfat *exfat) +/* write bitmap segments for clusters which are marked + * as free, but allocated to files. + */ +static int write_bitmap(struct exfat_fsck *fsck) { - struct buffer_desc *bd; - off_t offset; - ssize_t len; - size_t read_size, write_size; - clus_t clus, last_clus, clus_count, i; - unsigned int idx; - - clus = 0; - last_clus = le32_to_cpu(exfat->bs->bsx.clu_count) + 2; - bd = exfat->buffer_desc; - idx = 0; - offset = le32_to_cpu(exfat->bs->bsx.fat_offset) * - exfat->sect_size; - read_size = exfat->clus_size; - write_size = exfat->sect_size; - - while (clus < last_clus) { - clus_count = MIN(read_size / sizeof(clus_t), last_clus - clus); - len = exfat_read(exfat->blk_dev->dev_fd, bd[idx].buffer, - clus_count * sizeof(clus_t), offset); - if (len != (ssize_t)(sizeof(clus_t) * clus_count)) { - exfat_err("failed to read fat entries, %zd\n", len); - return -EIO; + struct exfat *exfat = fsck->exfat; + bitmap_t *disk_b, *alloc_b, *ohead_b; + off_t dev_offset; + unsigned int i, bitmap_bytes, byte_offset, write_bytes; + + dev_offset = exfat_c2o(exfat, exfat->disk_bitmap_clus); + bitmap_bytes = EXFAT_BITMAP_SIZE(le32_to_cpu(exfat->bs->bsx.clu_count)); + + disk_b = (bitmap_t *)exfat->disk_bitmap; + alloc_b = (bitmap_t *)exfat->alloc_bitmap; + ohead_b = (bitmap_t *)exfat->ohead_bitmap; + + for (i = 0; i < bitmap_bytes / sizeof(bitmap_t); i++) + ohead_b[i] = alloc_b[i] | disk_b[i]; + + i = 0; + while (i < bitmap_bytes / sizeof(bitmap_t)) { + if (ohead_b[i] == disk_b[i]) { + i++; + continue; } - /* 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; -} + byte_offset = ((i * sizeof(bitmap_t)) / 512) * 512; + write_bytes = MIN(512, bitmap_bytes - byte_offset); -static int write_dirty_bitmap(struct exfat *exfat) -{ - struct buffer_desc *bd; - off_t offset, last_offset, bitmap_offset; - ssize_t len; - ssize_t read_size, write_size, i, size; - int idx; - - offset = exfat_c2o(exfat, exfat->disk_bitmap_clus); - last_offset = offset + exfat->disk_bitmap_size; - bitmap_offset = 0; - read_size = exfat->clus_size; - write_size = exfat->sect_size; - - bd = exfat->buffer_desc; - idx = 0; - - while (offset < last_offset) { - len = MIN(read_size, last_offset - offset); - if (exfat_read(exfat->blk_dev->dev_fd, bd[idx].buffer, - len, offset) != (ssize_t)len) + if (exfat_write(exfat->blk_dev->dev_fd, + (char *)ohead_b + byte_offset, write_bytes, + dev_offset + byte_offset) != (ssize_t)write_bytes) return -EIO; - /* TODO: read-ahead */ - - for (i = 0; i < len; i += write_size) { - size = MIN(write_size, len - i); - if (memcmp(&bd[idx].buffer[i], - exfat->alloc_bitmap + bitmap_offset + i, - size)) { - if (exfat_write(exfat->blk_dev->dev_fd, - exfat->alloc_bitmap + bitmap_offset + i, - size, offset + i) != size) - return -EIO; - } - } - - idx ^= 0x01; - offset += len; - bitmap_offset += len; + i = (byte_offset + write_bytes) / sizeof(bitmap_t); } return 0; -} -static int reclaim_free_clusters(struct exfat *exfat) -{ - if (write_dirty_fat(exfat)) { - exfat_err("failed to write fat entries\n"); - return -EIO; - } - if (write_dirty_bitmap(exfat)) { - exfat_err("failed to write bitmap\n"); - return -EIO; - } - return 0; } /* @@ -1396,8 +1172,9 @@ static int reclaim_free_clusters(struct exfat *exfat) * 2. free all of file exfat_nodes. * 3. if the directory does not have children, free its exfat_node. */ -static int exfat_filesystem_check(struct exfat *exfat) +static int exfat_filesystem_check(struct exfat_fsck *fsck) { + struct exfat *exfat = fsck->exfat; struct exfat_inode *dir; int ret = 0, dir_errors; @@ -1406,10 +1183,17 @@ static int exfat_filesystem_check(struct exfat *exfat) return -ENOENT; } + fsck->name_hash_bitmap = malloc(EXFAT_BITMAP_SIZE(EXFAT_MAX_HASH_COUNT)); + if (!fsck->name_hash_bitmap) { + exfat_err("failed to allocate name hash bitmap\n"); + return -ENOMEM; + } + list_add(&exfat->root->list, &exfat->dir_list); while (!list_empty(&exfat->dir_list)) { - dir = list_entry(exfat->dir_list.next, struct exfat_inode, list); + dir = list_entry(exfat->dir_list.next, + struct exfat_inode, list); if (!(dir->attr & ATTR_SUBDIR)) { fsck_err(dir->parent, dir, @@ -1419,64 +1203,217 @@ static int exfat_filesystem_check(struct exfat *exfat) goto out; } - dir_errors = read_children(exfat, dir); + dir_errors = read_children(fsck, dir); if (dir_errors) { - resolve_path(&path_resolve_ctx, dir); + exfat_resolve_path(&path_resolve_ctx, dir); exfat_debug("failed to check dentries: %s\n", path_resolve_ctx.local_path); ret = dir_errors; } list_del(&dir->list); - inode_free_file_children(dir); - inode_free_ancestors(dir); + exfat_free_file_children(dir); + exfat_free_ancestors(dir); } out: exfat_free_dir_list(exfat); - exfat->root = NULL; - if (exfat->dirty_fat && reclaim_free_clusters(exfat)) - return -EIO; + free(fsck->name_hash_bitmap); return ret; } static int exfat_root_dir_check(struct exfat *exfat) { struct exfat_inode *root; - clus_t clus_count; + clus_t clus_count = 0; + int err; - root = alloc_exfat_inode(ATTR_SUBDIR); - if (!root) { - exfat_err("failed to allocate memory\n"); + root = exfat_alloc_inode(ATTR_SUBDIR); + if (!root) return -ENOMEM; - } + exfat->root = root; root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster); - if (!root_get_clus_count(exfat, root, &clus_count)) { + if (root_check_clus_chain(exfat, root, &clus_count)) { exfat_err("failed to follow the cluster chain of root\n"); - free_exfat_inode(root); + exfat_free_inode(root); + exfat->root = NULL; return -EINVAL; } root->size = clus_count * exfat->clus_size; - exfat->root = root; + exfat_stat.dir_count++; exfat_debug("root directory: start cluster[0x%x] size[0x%" PRIx64 "]\n", root->first_clus, root->size); + + err = exfat_read_volume_label(exfat); + if (err && err != EOF) + exfat_err("failed to read volume label\n"); + err = 0; + + err = read_bitmap(exfat); + if (err) { + exfat_err("failed to read bitmap\n"); + return -EINVAL; + } + + err = read_upcase_table(exfat); + if (err) { + exfat_err("failed to read upcase table\n"); + return -EINVAL; + } + + root->dev_offset = 0; + err = exfat_build_file_dentry_set(exfat, " ", ATTR_SUBDIR, + &root->dentry_set, &root->dentry_count); + if (err) { + exfat_free_inode(root); + return -ENOMEM; + } + return 0; +} + +static int read_lostfound(struct exfat *exfat, struct exfat_inode **lostfound) +{ + struct exfat_lookup_filter filter; + struct exfat_inode *inode; + int err; + + err = exfat_lookup_file(exfat, exfat->root, "LOST+FOUND", &filter); + if (err) + return err; + + inode = exfat_alloc_inode(ATTR_SUBDIR); + if (!inode) { + free(filter.out.dentry_set); + return -ENOMEM; + } + + inode->dentry_set = filter.out.dentry_set; + inode->dentry_count = filter.out.dentry_count; + inode->dev_offset = filter.out.dev_offset; + + inode->first_clus = + le32_to_cpu(filter.out.dentry_set[1].dentry.stream.start_clu); + inode->size = + le64_to_cpu(filter.out.dentry_set[1].dentry.stream.size); + + *lostfound = inode; return 0; } +/* Create temporary files under LOST+FOUND and assign orphan + * chains of clusters to these files. + */ +static int rescue_orphan_clusters(struct exfat_fsck *fsck) +{ + struct exfat *exfat = fsck->exfat; + struct exfat_inode *lostfound; + bitmap_t *disk_b, *alloc_b, *ohead_b; + struct exfat_dentry *dset; + clus_t clu_count, clu, s_clu, e_clu; + int err, dcount; + unsigned int i; + char name[] = "FILE0000000.CHK"; + struct exfat_dentry_loc loc; + struct exfat_lookup_filter lf = { + .in.type = EXFAT_INVAL, + .in.dentry_count = 0, + .in.filter = NULL, + }; + + clu_count = le32_to_cpu(exfat->bs->bsx.clu_count); + + /* find clusters which are not marked as free, but not allocated to + * any files. + */ + disk_b = (bitmap_t *)exfat->disk_bitmap; + alloc_b = (bitmap_t *)exfat->alloc_bitmap; + ohead_b = (bitmap_t *)exfat->ohead_bitmap; + for (i = 0; i < EXFAT_BITMAP_SIZE(clu_count) / sizeof(bitmap_t); i++) + ohead_b[i] = disk_b[i] & ~alloc_b[i]; + + /* no orphan clusters */ + if (exfat_bitmap_find_one(exfat, exfat->ohead_bitmap, + EXFAT_FIRST_CLUSTER, &s_clu)) + return 0; + + err = exfat_create_file(exfat_fsck.exfat, + exfat_fsck.exfat->root, + "LOST+FOUND", + ATTR_SUBDIR); + if (err) { + exfat_err("failed to create LOST+FOUND directory\n"); + return err; + } + + if (fsync(exfat_fsck.exfat->blk_dev->dev_fd) != 0) { + exfat_err("failed to sync()\n"); + return -EIO; + } + + err = read_lostfound(exfat, &lostfound); + if (err) { + exfat_err("failed to find LOST+FOUND\n"); + return err; + } + + /* get the last empty region of LOST+FOUND */ + err = exfat_lookup_dentry_set(exfat, lostfound, &lf); + if (err && err != EOF) { + exfat_err("failed to find the last empty slot in LOST+FOUND\n"); + goto out; + } + + loc.parent = lostfound; + loc.file_offset = lf.out.file_offset; + loc.dev_offset = lf.out.dev_offset; + + /* build a template dentry set */ + err = exfat_build_file_dentry_set(exfat, name, 0, &dset, &dcount); + if (err) { + exfat_err("failed to create a temporary file in LOST+FOUNDn"); + goto out; + } + dset[1].dentry.stream.flags |= EXFAT_SF_CONTIGUOUS; + + /* create temporary files and allocate contiguous orphan clusters + * to each file. + */ + for (clu = EXFAT_FIRST_CLUSTER; clu < clu_count + EXFAT_FIRST_CLUSTER && + exfat_bitmap_find_one(exfat, exfat->ohead_bitmap, clu, &s_clu) == 0;) { + if (exfat_bitmap_find_zero(exfat, exfat->ohead_bitmap, s_clu, &e_clu)) + e_clu = clu_count + EXFAT_FIRST_CLUSTER; + clu = e_clu; + + snprintf(name, sizeof(name), "FILE%07d.CHK", + (unsigned int)(loc.file_offset >> 5)); + err = exfat_update_file_dentry_set(exfat, dset, dcount, + name, s_clu, e_clu - s_clu); + if (err) + continue; + err = exfat_add_dentry_set(exfat, &loc, dset, dcount, true); + if (err) + continue; + } + + free(dset); + err = 0; +out: + exfat_free_inode(lostfound); + return err; +} + static char *bytes_to_human_readable(size_t bytes) { static const char * const units[] = {"B", "KB", "MB", "GB", "TB", "PB"}; static char buf[15*4]; unsigned int i, shift, quoti, remain; + i = sizeof(units) / sizeof(units[0]) - 1; - shift = 0; - for (i = 0; i < sizeof(units)/sizeof(units[0]); i++) { - if (bytes / (1ULL << (shift + 10)) == 0) - break; - shift += 10; - } + while (i && (bytes >> i * 10) == 0) + i--; + shift = i * 10; quoti = (unsigned int)(bytes / (1ULL << shift)); remain = 0; if (shift > 0) { @@ -1489,9 +1426,11 @@ static char *bytes_to_human_readable(size_t bytes) return buf; } -static void exfat_show_info(struct exfat *exfat, const char *dev_name, - int errors) +static void exfat_show_info(struct exfat_fsck *fsck, const char *dev_name) { + struct exfat *exfat = fsck->exfat; + bool clean; + exfat_info("sector size: %s\n", bytes_to_human_readable(1 << exfat->bs->bsx.sect_size_bits)); exfat_info("cluster size: %s\n", @@ -1499,19 +1438,21 @@ static void exfat_show_info(struct exfat *exfat, const char *dev_name, exfat_info("volume size: %s\n", bytes_to_human_readable(exfat->blk_dev->size)); + clean = exfat_stat.error_count == 0 || + exfat_stat.error_count == exfat_stat.fixed_count; printf("%s: %s. directories %ld, files %ld\n", dev_name, - errors ? "checking stopped" : "clean", + clean ? "clean" : "corrupted", exfat_stat.dir_count, exfat_stat.file_count); - if (errors || exfat->dirty) + if (exfat_stat.error_count) printf("%s: files corrupted %ld, files fixed %ld\n", dev_name, - exfat_stat.error_count, exfat_stat.fixed_count); + exfat_stat.error_count - exfat_stat.fixed_count, + exfat_stat.fixed_count); } int main(int argc, char * const argv[]) { struct fsck_user_input ui; struct exfat_blk_dev bd; - struct exfat *exfat = NULL; struct pbr *bs = NULL; int c, ret, exit_code; bool version_only = false; @@ -1525,7 +1466,7 @@ int main(int argc, char * const argv[]) exfat_err("failed to init locale/codeset\n"); opterr = 0; - while ((c = getopt_long(argc, argv, "arynpVvh", opts, NULL)) != EOF) { + while ((c = getopt_long(argc, argv, "arynpbsVvh", opts, NULL)) != EOF) { switch (c) { case 'n': if (ui.options & FSCK_OPTS_REPAIR_ALL) @@ -1548,6 +1489,12 @@ int main(int argc, char * const argv[]) usage(argv[0]); ui.options |= FSCK_OPTS_REPAIR_AUTO; break; + case 'b': + ui.options |= FSCK_OPTS_IGNORE_BAD_FS_NAME; + break; + case 's': + ui.options |= FSCK_OPTS_RESCUE_CLUS; + break; case 'V': version_only = true; break; @@ -1571,10 +1518,15 @@ int main(int argc, char * const argv[]) if (ui.options & FSCK_OPTS_REPAIR_WRITE) ui.ei.writeable = true; else { + if (ui.options & (FSCK_OPTS_IGNORE_BAD_FS_NAME | + FSCK_OPTS_RESCUE_CLUS)) + usage(argv[0]); ui.options |= FSCK_OPTS_REPAIR_NO; ui.ei.writeable = false; } + exfat_fsck.options = ui.options; + snprintf(ui.ei.dev_name, sizeof(ui.ei.dev_name), "%s", argv[optind]); ret = exfat_get_blk_dev_info(&ui.ei, &bd); if (ret < 0) { @@ -1582,60 +1534,83 @@ int main(int argc, char * const argv[]) return FSCK_EXIT_OPERATION_ERROR; } - exfat = (struct exfat *)calloc(1, sizeof(*exfat)); - if (!exfat) { - exfat_err("failed to allocate exfat\n"); - ret = -ENOMEM; + ret = exfat_boot_region_check(&bd, &bs, + ui.options & FSCK_OPTS_IGNORE_BAD_FS_NAME ? + true : false); + if (ret) goto err; - } - exfat->blk_dev = &bd; - exfat->options = ui.options; - ret = exfat_boot_region_check(exfat, &bs); - if (ret) + exfat_fsck.exfat = exfat_alloc_exfat(&bd, bs); + if (!exfat_fsck.exfat) { + ret = -ENOMEM; goto err; + } - ret = init_exfat(exfat, bs); - if (ret) + exfat_fsck.buffer_desc = exfat_alloc_buffer(2, + exfat_fsck.exfat->clus_size, + exfat_fsck.exfat->sect_size); + if (!exfat_fsck.buffer_desc) { + ret = -ENOMEM; goto err; + } - if (exfat_mark_volume_dirty(exfat, true)) { + if ((exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) && + exfat_mark_volume_dirty(exfat_fsck.exfat, true)) { ret = -EIO; goto err; } exfat_debug("verifying root directory...\n"); - ret = exfat_root_dir_check(exfat); + ret = exfat_root_dir_check(exfat_fsck.exfat); if (ret) { exfat_err("failed to verify root directory.\n"); goto out; } exfat_debug("verifying directory entries...\n"); - ret = exfat_filesystem_check(exfat); + ret = exfat_filesystem_check(&exfat_fsck); if (ret) goto out; + if (exfat_fsck.options & FSCK_OPTS_RESCUE_CLUS) { + rescue_orphan_clusters(&exfat_fsck); + exfat_fsck.dirty = true; + exfat_fsck.dirty_fat = true; + } + + if (exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) { + ret = write_bitmap(&exfat_fsck); + if (ret) { + exfat_err("failed to write bitmap\n"); + goto out; + } + } + if (ui.ei.writeable && fsync(bd.dev_fd)) { exfat_err("failed to sync\n"); ret = -EIO; goto out; } - exfat_mark_volume_dirty(exfat, false); + if (exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) + exfat_mark_volume_dirty(exfat_fsck.exfat, false); out: - exfat_show_info(exfat, ui.ei.dev_name, ret); + exfat_show_info(&exfat_fsck, ui.ei.dev_name); err: - if (ret == -EINVAL) - exit_code = FSCK_EXIT_ERRORS_LEFT; - else if (ret) + if (ret && ret != -EINVAL) exit_code = FSCK_EXIT_OPERATION_ERROR; - else if (exfat->dirty) + else if (ret == -EINVAL || + exfat_stat.error_count != exfat_stat.fixed_count) + exit_code = FSCK_EXIT_ERRORS_LEFT; + else if (exfat_fsck.dirty) exit_code = FSCK_EXIT_CORRECTED; else exit_code = FSCK_EXIT_NO_ERRORS; - free_exfat(exfat); + if (exfat_fsck.buffer_desc) + exfat_free_buffer(exfat_fsck.buffer_desc, 2); + if (exfat_fsck.exfat) + exfat_free_exfat(exfat_fsck.exfat); close(bd.dev_fd); return exit_code; } diff --git a/fsck/fsck.h b/fsck/fsck.h index 6c91fac..ee0cb30 100644 --- a/fsck/fsck.h +++ b/fsck/fsck.h @@ -7,46 +7,6 @@ #include "list.h" -typedef __u32 clus_t; - -struct exfat_inode { - struct exfat_inode *parent; - struct list_head children; - struct list_head sibling; - struct list_head list; - clus_t first_clus; - clus_t last_lclus; - clus_t last_pclus; - __u16 attr; - uint64_t size; - bool is_contiguous; - __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; - 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; -}; - enum fsck_ui_options { FSCK_OPTS_REPAIR_ASK = 0x01, FSCK_OPTS_REPAIR_YES = 0x02, @@ -54,46 +14,24 @@ enum fsck_ui_options { FSCK_OPTS_REPAIR_AUTO = 0x08, FSCK_OPTS_REPAIR_WRITE = 0x0b, FSCK_OPTS_REPAIR_ALL = 0x0f, + FSCK_OPTS_IGNORE_BAD_FS_NAME = 0x10, + FSCK_OPTS_RESCUE_CLUS = 0x20, }; -struct exfat { +struct exfat; +struct exfat_inode; + +struct exfat_fsck { + struct exfat *exfat; + struct exfat_de_iter de_iter; + struct buffer_desc *buffer_desc; /* cluster * 2 */ 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; - 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) + char *name_hash_bitmap; +}; -/* 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 c79d379..f983ee1 100644 --- a/fsck/repair.c +++ b/fsck/repair.c @@ -5,16 +5,22 @@ #include #include #include +#include +#include #include "exfat_ondisk.h" #include "libexfat.h" -#include "fsck.h" #include "repair.h" +#include "exfat_fs.h" +#include "fsck.h" struct exfat_repair_problem { er_problem_code_t prcode; unsigned int flags; unsigned int prompt_type; + unsigned int default_number; + unsigned int bypass_number; + unsigned int max_number; }; /* Problem flags */ @@ -25,24 +31,37 @@ struct exfat_repair_problem { /* Prompt types */ #define ERP_FIX 0x00000001 #define ERP_TRUNCATE 0x00000002 +#define ERP_DELETE 0x00000003 +#define ERP_RENAME 0x00000004 static const char *prompts[] = { "Repair", "Fix", "Truncate", + "Delete", + "Select", }; static struct exfat_repair_problem problems[] = { - {ER_BS_CHECKSUM, ERF_PREEN_YES, ERP_FIX}, - {ER_BS_BOOT_REGION, 0, ERP_FIX}, - {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_FIX}, - {ER_FILE_VALID_SIZE, 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_PREEN_YES, ERP_FIX}, + {ER_BS_CHECKSUM, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, + {ER_BS_BOOT_REGION, 0, ERP_FIX, 0, 0, 0}, + {ER_DE_CHECKSUM, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0}, + {ER_DE_UNKNOWN, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0}, + {ER_DE_FILE, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0}, + {ER_DE_SECONDARY_COUNT, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0}, + {ER_DE_STREAM, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0}, + {ER_DE_NAME, ERF_PREEN_YES, ERP_DELETE, 0, 0, 0}, + {ER_DE_NAME_HASH, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, + {ER_DE_NAME_LEN, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, + {ER_DE_DOT_NAME, ERF_PREEN_YES, ERP_RENAME, 2, 3, 4}, + {ER_DE_DUPLICATED_NAME, ERF_PREEN_YES, ERP_RENAME, 2, 3, 4}, + {ER_FILE_VALID_SIZE, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, + {ER_FILE_INVALID_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, + {ER_FILE_FIRST_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, + {ER_FILE_SMALLER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, + {ER_FILE_LARGER_SIZE, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, + {ER_FILE_DUPLICATED_CLUS, ERF_PREEN_YES, ERP_TRUNCATE, 0, 0, 0}, + {ER_FILE_ZERO_NOFAT, ERF_PREEN_YES, ERP_FIX, 0, 0, 0}, }; static struct exfat_repair_problem *find_problem(er_problem_code_t prcode) @@ -57,63 +76,220 @@ static struct exfat_repair_problem *find_problem(er_problem_code_t prcode) return NULL; } -static bool ask_repair(struct exfat *exfat, struct exfat_repair_problem *pr) +static int ask_repair(struct exfat_fsck *fsck, struct exfat_repair_problem *pr) { - bool repair = false; + int repair = 0; char answer[8]; - 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; + if (fsck->options & FSCK_OPTS_REPAIR_NO || + pr->flags & ERF_DEFAULT_NO) + repair = 0; + else if (fsck->options & FSCK_OPTS_REPAIR_YES || + pr->flags & ERF_DEFAULT_YES) + repair = 1; else { - if (exfat->options & FSCK_OPTS_REPAIR_ASK) { + if (fsck->options & FSCK_OPTS_REPAIR_ASK) { do { - printf(". %s (y/N)? ", - prompts[pr->prompt_type]); + if (pr->prompt_type & ERP_RENAME) { + printf("%s (Number: ?) ", + prompts[pr->prompt_type]); + } else { + printf(". %s (y/N)? ", + prompts[pr->prompt_type]); + } fflush(stdout); - if (fgets(answer, sizeof(answer), stdin)) { + if (!fgets(answer, sizeof(answer), stdin)) + continue; + + if (pr->prompt_type & ERP_RENAME) { + unsigned int number = atoi(answer); + + if (number > 0 && number < pr->max_number) + return number; + } else { if (strcasecmp(answer, "Y\n") == 0) - return true; - else if (strcasecmp(answer, "\n") == 0 - || strcasecmp(answer, "N\n") == 0) - return false; + return 1; + else if (strcasecmp(answer, "\n") == 0 || + strcasecmp(answer, "N\n") == 0) + return 0; } } while (1); - } else if (exfat->options & FSCK_OPTS_REPAIR_AUTO && - pr->flags & ERF_PREEN_YES) - repair = true; + } else if (fsck->options & FSCK_OPTS_REPAIR_AUTO && + pr->flags & ERF_PREEN_YES) + repair = 1; } - printf(". %s (y/N)? %c\n", prompts[pr->prompt_type], - repair ? 'y' : 'n'); + if (pr->prompt_type & ERP_RENAME) { + int print_num = repair ? pr->default_number : pr->bypass_number; + + printf("%s (Number : %d)\n", prompts[pr->prompt_type], + print_num); + repair = print_num; + } else { + printf(". %s (y/N)? %c\n", prompts[pr->prompt_type], + repair ? 'y' : 'n'); + } return repair; } -bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode, - const char *desc, ...) +int exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode, + const char *desc, ...) { struct exfat_repair_problem *pr = NULL; va_list ap; + int repair; pr = find_problem(prcode); if (!pr) { exfat_err("unknown problem code. %#x\n", prcode); - return false; + return 0; } va_start(ap, desc); vprintf(desc, ap); va_end(ap); - if (ask_repair(exfat, pr)) { + repair = ask_repair(fsck, pr); + if (repair) { if (pr->prompt_type & ERP_TRUNCATE) - exfat->dirty_fat = true; - exfat->dirty = true; - return true; - } else - return false; + fsck->dirty_fat = true; + fsck->dirty = true; + } + return repair; +} + +static int check_bad_char(char w) +{ + return (w < 0x0020) || (w == '*') || (w == '?') || (w == '<') || + (w == '>') || (w == '|') || (w == '"') || (w == ':') || + (w == '/') || (w == '\\'); +} + +static char *get_rename_from_user(struct exfat_de_iter *iter) +{ + char *rename = malloc(ENTRY_NAME_MAX + 2); + + if (!rename) + return NULL; + +retry: + /* +2 means LF(Line Feed) and NULL terminator */ + memset(rename, 0x1, ENTRY_NAME_MAX + 2); + printf("New name: "); + if (fgets(rename, ENTRY_NAME_MAX + 2, stdin)) { + int i, len, err; + struct exfat_lookup_filter filter; + + len = strlen(rename); + /* Remove LF in filename */ + rename[len - 1] = '\0'; + for (i = 0; i < len - 1; i++) { + if (check_bad_char(rename[i])) { + printf("filename contain invalid character(%c)\n", rename[i]); + goto retry; + } + } + + exfat_de_iter_flush(iter); + err = exfat_lookup_file(iter->exfat, iter->parent, rename, &filter); + if (!err) { + printf("file(%s) already exists, retry to insert name\n", rename); + goto retry; + } + } + + return rename; +} + +static char *generate_rename(struct exfat_de_iter *iter) +{ + char *rename; + + if (iter->invalid_name_num > INVALID_NAME_NUM_MAX) + return NULL; + + rename = malloc(ENTRY_NAME_MAX + 1); + if (!rename) + return NULL; + + while (1) { + struct exfat_lookup_filter filter; + int err; + + snprintf(rename, ENTRY_NAME_MAX + 1, "FILE%07d.CHK", + iter->invalid_name_num++); + err = exfat_lookup_file(iter->exfat, iter->parent, rename, + &filter); + if (!err) + continue; + break; + } + + return rename; +} + +int exfat_repair_rename_ask(struct exfat_fsck *fsck, struct exfat_de_iter *iter, + char *old_name, er_problem_code_t prcode, char *error_msg) +{ + int num; + +ask_again: + num = exfat_repair_ask(fsck, prcode, "ERROR: '%s' %s.\n%s", + old_name, error_msg, + " [1] Insert the name you want to rename.\n" + " [2] Automatically renames filename.\n" + " [3] Bypass this check(No repair)\n"); + if (num) { + __le16 utf16_name[ENTRY_NAME_MAX]; + char *rename = NULL; + __u16 hash; + struct exfat_dentry *dentry; + int ret, i; + + switch (num) { + case 1: + rename = get_rename_from_user(iter); + break; + case 2: + rename = generate_rename(iter); + break; + case 3: + break; + default: + exfat_info("select 1 or 2 number instead of %d\n", num); + goto ask_again; + } + + if (!rename) + return -EINVAL; + + exfat_info("%s filename is renamed to %s\n", old_name, rename); + + exfat_de_iter_get_dirty(iter, 2, &dentry); + + memset(utf16_name, 0, sizeof(utf16_name)); + ret = exfat_utf16_enc(rename, utf16_name, sizeof(utf16_name)); + free(rename); + if (ret < 0) + return ret; + + ret >>= 1; + memcpy(dentry->name_unicode, utf16_name, ENTRY_NAME_MAX * 2); + hash = exfat_calc_name_hash(iter->exfat, utf16_name, ret); + exfat_de_iter_get_dirty(iter, 1, &dentry); + dentry->stream_name_len = (__u8)ret; + dentry->stream_name_hash = cpu_to_le16(hash); + + exfat_de_iter_get_dirty(iter, 0, &dentry); + i = dentry->file_num_ext; + dentry->file_num_ext = 2; + + for (; i > 2; i--) { + exfat_de_iter_get_dirty(iter, i, &dentry); + dentry->type &= EXFAT_DELETE; + } + } + + return 0; } diff --git a/fsck/repair.h b/fsck/repair.h index f7286b9..634cc49 100644 --- a/fsck/repair.h +++ b/fsck/repair.h @@ -5,9 +5,20 @@ #ifndef _REPAIR_H #define _REPAIR_H +#include "exfat_dir.h" + #define ER_BS_CHECKSUM 0x00000001 #define ER_BS_BOOT_REGION 0x00000002 #define ER_DE_CHECKSUM 0x00001001 +#define ER_DE_UNKNOWN 0x00001002 +#define ER_DE_FILE 0x00001010 +#define ER_DE_SECONDARY_COUNT 0x00001011 +#define ER_DE_STREAM 0x00001020 +#define ER_DE_NAME 0x00001030 +#define ER_DE_NAME_HASH 0x00001031 +#define ER_DE_NAME_LEN 0x00001032 +#define ER_DE_DOT_NAME 0x00001033 +#define ER_DE_DUPLICATED_NAME 0x00001034 #define ER_FILE_VALID_SIZE 0x00002001 #define ER_FILE_INVALID_CLUS 0x00002002 #define ER_FILE_FIRST_CLUS 0x00002003 @@ -17,8 +28,11 @@ #define ER_FILE_ZERO_NOFAT 0x00002007 typedef unsigned int er_problem_code_t; +struct exfat_fsck; -bool exfat_repair_ask(struct exfat *exfat, er_problem_code_t prcode, - const char *fmt, ...); +int exfat_repair_ask(struct exfat_fsck *fsck, er_problem_code_t prcode, + const char *fmt, ...); +int exfat_repair_rename_ask(struct exfat_fsck *fsck, struct exfat_de_iter *iter, + char *old_name, er_problem_code_t prcode, char *error_msg); #endif diff --git a/include/exfat_dir.h b/include/exfat_dir.h new file mode 100644 index 0000000..d450c61 --- /dev/null +++ b/include/exfat_dir.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 LG Electronics. + * + * Author(s): Hyunchul Lee + */ + +#ifndef _DIR_H_ +#define _DIR_H_ + +struct exfat; +struct exfat_inode; +struct exfat_dentry_loc; +struct buffer_desc; + +struct exfat_de_iter { + struct exfat *exfat; + struct exfat_inode *parent; + struct buffer_desc *buffer_desc; /* cluster * 2 */ + __u32 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; +#define INVALID_NAME_NUM_MAX 9999999 + unsigned int invalid_name_num; + + char *name_hash_bitmap; /* bitmap of children's name hashes */ +}; + +struct exfat_lookup_filter { + struct { + uint8_t type; + int dentry_count; + /* return 0 if matched, return 1 if not matched, + * otherwise return errno + */ + int (*filter)(struct exfat_de_iter *iter, + void *param, int *dentry_count); + void *param; + } in; + struct { + struct exfat_dentry *dentry_set; + int dentry_count; + off_t file_offset; + /* + * If the dentry_set found: + * - device offset where the dentry_set locates. + * If the dentry_set not found: + * - device offset where the first empty dentry_set locates + * if in.dentry_count > 0 and there are enough empty dentry. + * - device offset where the last empty dentry_set locates + * if in.dentry_count = 0 or no enough empty dentry. + * - EOF if no empty dentry_set. + */ + off_t dev_offset; + } out; +}; + +int exfat_de_iter_init(struct exfat_de_iter *iter, struct exfat *exfat, + struct exfat_inode *dir, struct buffer_desc *bd); +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_device_offset(struct exfat_de_iter *iter); +off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter); + +int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent, + struct exfat_lookup_filter *filter); +int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent, + const char *name, struct exfat_lookup_filter *filter_out); +int exfat_lookup_file_by_utf16name(struct exfat *exfat, + struct exfat_inode *parent, + __le16 *utf16_name, + struct exfat_lookup_filter *filter_out); + +int exfat_create_file(struct exfat *exfat, struct exfat_inode *parent, + const char *name, unsigned short attr); +int exfat_update_file_dentry_set(struct exfat *exfat, + struct exfat_dentry *dset, int dcount, + const char *name, + clus_t start_clu, clus_t ccount); +int exfat_build_file_dentry_set(struct exfat *exfat, const char *name, + unsigned short attr, struct exfat_dentry **dentry_set, + int *dentry_count); +int exfat_add_dentry_set(struct exfat *exfat, struct exfat_dentry_loc *loc, + struct exfat_dentry *dset, int dcount, + bool need_next_loc); +void exfat_calc_dentry_checksum(struct exfat_dentry *dentry, + uint16_t *checksum, bool primary); +uint16_t exfat_calc_name_hash(struct exfat *exfat, + __le16 *name, int len); + +#endif diff --git a/include/exfat_fs.h b/include/exfat_fs.h new file mode 100644 index 0000000..d35b12c --- /dev/null +++ b/include/exfat_fs.h @@ -0,0 +1,88 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (C) 2021 LG Electronics. + * + * Author(s): Hyunchul Lee + */ +#ifndef _EXFAT_FS_H_ +#define _EXFAT_FS_H_ + +#include "list.h" + +struct exfat_dentry; + +struct exfat_inode { + struct exfat_inode *parent; + struct list_head children; + struct list_head sibling; + struct list_head list; + clus_t first_clus; + __u16 attr; + uint64_t size; + bool is_contiguous; + struct exfat_dentry *dentry_set; + int dentry_count; + off_t dev_offset; + __le16 name[0]; /* only for directory */ +}; + +#define EXFAT_NAME_MAX 255 +#define NAME_BUFFER_SIZE ((EXFAT_NAME_MAX + 1) * 2) + +struct exfat { + 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; + char *disk_bitmap; + char *alloc_bitmap; + char *ohead_bitmap; + clus_t disk_bitmap_clus; + unsigned int disk_bitmap_size; + __u16 *upcase_table; + clus_t start_clu; + char *zero_cluster; +}; + +struct exfat_dentry_loc { + struct exfat_inode *parent; + off_t file_offset; + off_t dev_offset; +}; + +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 buffer_desc { + __u32 p_clus; + unsigned int offset; + char *buffer; + char *dirty; +}; + +struct exfat *exfat_alloc_exfat(struct exfat_blk_dev *blk_dev, struct pbr *bs); +void exfat_free_exfat(struct exfat *exfat); + +struct exfat_inode *exfat_alloc_inode(__u16 attr); +void exfat_free_inode(struct exfat_inode *node); + +void exfat_free_children(struct exfat_inode *dir, bool file_only); +void exfat_free_file_children(struct exfat_inode *dir); +void exfat_free_ancestors(struct exfat_inode *child); +void exfat_free_dir_list(struct exfat *exfat); + +int exfat_resolve_path(struct path_resolve_ctx *ctx, struct exfat_inode *child); +int exfat_resolve_path_parent(struct path_resolve_ctx *ctx, + struct exfat_inode *parent, struct exfat_inode *child); + +struct buffer_desc *exfat_alloc_buffer(int count, + unsigned int clu_size, unsigned int sect_size); +void exfat_free_buffer(struct buffer_desc *bd, int count); +#endif diff --git a/include/exfat_ondisk.h b/include/exfat_ondisk.h index b3fc1fe..42cdadf 100644 --- a/include/exfat_ondisk.h +++ b/include/exfat_ondisk.h @@ -39,6 +39,8 @@ #define DENTRY_SIZE_BITS 5 /* exFAT allows 8388608(256MB) directory entries */ #define MAX_EXFAT_DENTRIES 8388608 +#define MIN_FILE_DENTRIES 3 +#define MAX_NAME_DENTRIES 17 /* dentry types */ #define MSDOS_DELETED 0xE5 /* deleted mark */ @@ -132,6 +134,7 @@ struct pbr { }; #define VOLUME_LABEL_MAX_LEN 11 +#define VOLUME_GUID_LEN 16 #define ENTRY_NAME_MAX 15 struct exfat_dentry { @@ -156,8 +159,10 @@ struct exfat_dentry { __le16 access_date; __u8 create_time_ms; __u8 modify_time_ms; - __u8 access_time_ms; - __u8 reserved2[9]; + __u8 create_tz; + __u8 modify_tz; + __u8 access_tz; + __u8 reserved2[7]; } __attribute__((packed)) file; /* file directory entry */ struct { __u8 flags; @@ -187,6 +192,13 @@ struct exfat_dentry { __le32 start_clu; __le64 size; } __attribute__((packed)) upcase; /* up-case table directory entry */ + struct { + __u8 num_ext; + __le16 checksum; + __u16 flags; + __u8 guid[VOLUME_GUID_LEN]; + __u8 reserved[10]; + } __attribute__((packed)) guid; /* volume GUID directory entry */ } __attribute__((packed)) dentry; } __attribute__((packed)); diff --git a/include/libexfat.h b/include/libexfat.h index fecd7f2..9e853a8 100644 --- a/include/libexfat.h +++ b/include/libexfat.h @@ -10,6 +10,8 @@ #include #include +typedef __u32 clus_t; + #define KB (1024) #define MB (1024*1024) #define GB (1024UL*1024UL*1024UL) @@ -34,7 +36,8 @@ #define VOLUME_LABEL_BUFFER_SIZE (VOLUME_LABEL_MAX_LEN*MB_LEN_MAX+1) -/* Upcase tabel macro */ +/* Upcase table macro */ +#define EXFAT_UPCASE_TABLE_CHARS (0x10000) #define EXFAT_UPCASE_TABLE_SIZE (5836) /* Flags for tune.exfat and exfatlabel */ @@ -42,9 +45,17 @@ #define EXFAT_SET_VOLUME_LABEL 0x02 #define EXFAT_GET_VOLUME_SERIAL 0x03 #define EXFAT_SET_VOLUME_SERIAL 0x04 +#define EXFAT_GET_VOLUME_GUID 0x05 +#define EXFAT_SET_VOLUME_GUID 0x06 #define EXFAT_MAX_SECTOR_SIZE 4096 +#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) + +#define EXFAT_MAX_HASH_COUNT (UINT16_MAX + 1) + enum { BOOT_SEC_IDX = 0, EXBOOT_SEC_IDX, @@ -77,14 +88,60 @@ struct exfat_user_input { __u16 volume_label[VOLUME_LABEL_MAX_LEN]; int volume_label_len; unsigned int volume_serial; + const char *guid; }; +struct exfat; +struct exfat_inode; + +#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 BITMAP_GET(bmap, bit) \ + (((bitmap_t *)(bmap))[BIT_ENTRY(bit)] & BIT_MASK(bit)) + +#define BITMAP_SET(bmap, bit) \ + (((bitmap_t *)(bmap))[BIT_ENTRY(bit)] |= BIT_MASK(bit)) + +static inline bool exfat_bitmap_get(char *bmap, clus_t c) +{ + clus_t cc = c - EXFAT_FIRST_CLUSTER; + + return BITMAP_GET(bmap, cc); +} + +static inline void exfat_bitmap_set(char *bmap, clus_t c) +{ + clus_t cc = c - EXFAT_FIRST_CLUSTER; + + BITMAP_SET(bmap, cc); +} + +static inline void exfat_bitmap_clear(char *bmap, clus_t c) +{ + clus_t cc = c - EXFAT_FIRST_CLUSTER; + (((bitmap_t *)(bmap))[BIT_ENTRY(cc)] &= ~BIT_MASK(cc)); +} + +void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, + clus_t start_clus, clus_t count); +int exfat_bitmap_find_zero(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next); +int exfat_bitmap_find_one(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next); + void show_version(void); -void exfat_set_bit(struct exfat_blk_dev *bd, char *bitmap, - unsigned int clu); -void exfat_clear_bit(struct exfat_blk_dev *bd, char *bitmap, - unsigned int clu); wchar_t exfat_bad_char(wchar_t w); void boot_calc_checksum(unsigned char *sector, unsigned short size, bool is_boot_sec, __le32 *checksum); @@ -99,9 +156,11 @@ 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); off_t exfat_get_root_entry_offset(struct exfat_blk_dev *bd); -int exfat_show_volume_label(struct exfat_blk_dev *bd, off_t root_clu_off); -int exfat_set_volume_label(struct exfat_blk_dev *bd, - char *label_input, off_t root_clu_off); +int exfat_read_volume_label(struct exfat *exfat); +int exfat_set_volume_label(struct exfat *exfat, char *label_input); +int __exfat_set_volume_guid(struct exfat_dentry *dentry, const char *guid); +int exfat_read_volume_guid(struct exfat *exfat); +int exfat_set_volume_guid(struct exfat *exfat, const char *guid); int exfat_read_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off); int exfat_write_sector(struct exfat_blk_dev *bd, void *buf, @@ -114,7 +173,17 @@ int exfat_set_volume_serial(struct exfat_blk_dev *bd, struct exfat_user_input *ui); unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd, unsigned int clu_off, unsigned int clu); - +int exfat_get_next_clus(struct exfat *exfat, clus_t clus, clus_t *next); +int exfat_get_inode_next_clus(struct exfat *exfat, struct exfat_inode *node, + clus_t clus, clus_t *next); +int exfat_set_fat(struct exfat *exfat, clus_t clus, clus_t next_clus); +off_t exfat_s2o(struct exfat *exfat, off_t sect); +off_t exfat_c2o(struct exfat *exfat, unsigned int clus); +int exfat_o2c(struct exfat *exfat, off_t device_offset, + unsigned int *clu, unsigned int *offset); +bool exfat_heap_clus(struct exfat *exfat, clus_t clus); +int exfat_root_clus_count(struct exfat *exfat); +int read_boot_sect(struct exfat_blk_dev *bdev, struct pbr **bs); /* * Exfat Print diff --git a/include/list.h b/include/list.h index 30a32de..cc93668 100644 --- a/include/list.h +++ b/include/list.h @@ -84,7 +84,6 @@ static inline void list_add_tail(struct list_head *new, struct list_head *head) __list_add(new, head->prev, head); } - /* * Delete a list entry by making the prev/next entries * point to each other. @@ -111,8 +110,6 @@ static inline void list_del(struct list_head *entry) entry->prev = LIST_POISON2; } - - /** * list_del_init - deletes entry from list and reinitialize it. * @entry: the element to delete from the list. diff --git a/include/version.h b/include/version.h index 0f8a463..f3cf9e2 100644 --- a/include/version.h +++ b/include/version.h @@ -5,6 +5,6 @@ #ifndef _VERSION_H -#define EXFAT_PROGS_VERSION "1.1.2" +#define EXFAT_PROGS_VERSION "1.2.2" #endif /* !_VERSION_H */ diff --git a/label/Makefile.in b/label/Makefile.in index fcf1417..b79d9d5 100644 --- a/label/Makefile.in +++ b/label/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.16.1 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2018 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -125,8 +125,7 @@ am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp -am__maybe_remake_depfiles = depfiles -am__depfiles_remade = ./$(DEPDIR)/label.Po +am__depfiles_maybe = depfiles am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -315,8 +314,8 @@ Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) @@ -387,13 +386,7 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/label.Po@am__quote@ # am--include-marker - -$(am__depfiles_remade): - @$(MKDIR_P) $(@D) - @echo '# dummy' >$@-t && $(am__mv) $@-t $@ - -am--depfiles: $(am__depfiles_remade) +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/label.Po@am__quote@ .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @@ -477,10 +470,7 @@ cscopelist-am: $(am__tagged_files) distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags -distdir: $(BUILT_SOURCES) - $(MAKE) $(AM_MAKEFLAGS) distdir-am - -distdir-am: $(DISTFILES) +distdir: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ @@ -553,7 +543,7 @@ clean-am: clean-generic clean-libtool clean-sbinPROGRAMS \ mostlyclean-am distclean: distclean-am - -rm -f ./$(DEPDIR)/label.Po + -rm -rf ./$(DEPDIR) -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags @@ -599,7 +589,7 @@ install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am - -rm -f ./$(DEPDIR)/label.Po + -rm -rf ./$(DEPDIR) -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic @@ -620,9 +610,9 @@ uninstall-am: uninstall-sbinPROGRAMS .MAKE: install-am install-strip -.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ - clean-generic clean-libtool clean-sbinPROGRAMS cscopelist-am \ - ctags ctags-am distclean distclean-compile distclean-generic \ +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-sbinPROGRAMS cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-data \ install-data-am install-dvi install-dvi-am install-exec \ diff --git a/label/label.c b/label/label.c index b41e827..8cd5748 100644 --- a/label/label.c +++ b/label/label.c @@ -13,6 +13,7 @@ #include "exfat_ondisk.h" #include "libexfat.h" +#include "exfat_fs.h" static void usage(void) { @@ -39,7 +40,6 @@ int main(int argc, char *argv[]) struct exfat_blk_dev bd; struct exfat_user_input ui; bool version_only = false; - off_t root_clu_off; int serial_mode = 0; int flags = 0; @@ -96,15 +96,43 @@ int main(int argc, char *argv[]) ret = exfat_set_volume_serial(&bd, &ui); } } else { - /* Mode to change or display volume label */ - root_clu_off = exfat_get_root_entry_offset(&bd); - if (root_clu_off < 0) + struct exfat *exfat; + struct pbr *bs; + + ret = read_boot_sect(&bd, &bs); + if (ret) + goto close_fd_out; + + exfat = exfat_alloc_exfat(&bd, bs); + if (!exfat) { + free(bs); + ret = -ENOMEM; goto close_fd_out; + } + exfat->root = exfat_alloc_inode(ATTR_SUBDIR); + if (!exfat->root) { + ret = -ENOMEM; + goto free_exfat; + } + + exfat->root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster); + if (exfat_root_clus_count(exfat)) { + exfat_err("failed to follow the cluster chain of root\n"); + exfat_free_inode(exfat->root); + ret = -EINVAL; + goto free_exfat; + } + + /* Mode to change or display volume label */ if (flags == EXFAT_GET_VOLUME_LABEL) - ret = exfat_show_volume_label(&bd, root_clu_off); + ret = exfat_read_volume_label(exfat); else if (flags == EXFAT_SET_VOLUME_LABEL) - ret = exfat_set_volume_label(&bd, argv[2], root_clu_off); + ret = exfat_set_volume_label(exfat, argv[2]); + +free_exfat: + if (exfat) + exfat_free_exfat(exfat); } close_fd_out: diff --git a/lib/Android.bp b/lib/Android.bp index 0ccf6aa..f97aff5 100644 --- a/lib/Android.bp +++ b/lib/Android.bp @@ -5,6 +5,8 @@ cc_library_static { srcs: [ "libexfat.c", + "exfat_fs.c", + "exfat_dir.c", ], defaults: ["exfatprogs-defaults"], } diff --git a/lib/Makefile.am b/lib/Makefile.am index 5ea12db..d732cfd 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,4 +1,4 @@ AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common noinst_LIBRARIES = libexfat.a -libexfat_a_SOURCES = libexfat.c +libexfat_a_SOURCES = libexfat.c exfat_fs.c exfat_dir.c diff --git a/lib/Makefile.in b/lib/Makefile.in index d65352e..50c6658 100644 --- a/lib/Makefile.in +++ b/lib/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.16.1 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2018 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -109,7 +109,8 @@ am__v_AR_0 = @echo " AR " $@; am__v_AR_1 = libexfat_a_AR = $(AR) $(ARFLAGS) libexfat_a_LIBADD = -am_libexfat_a_OBJECTS = libexfat.$(OBJEXT) +am_libexfat_a_OBJECTS = libexfat.$(OBJEXT) exfat_fs.$(OBJEXT) \ + exfat_dir.$(OBJEXT) libexfat_a_OBJECTS = $(am_libexfat_a_OBJECTS) AM_V_P = $(am__v_P_@AM_V@) am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) @@ -125,8 +126,7 @@ am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp -am__maybe_remake_depfiles = depfiles -am__depfiles_remade = ./$(DEPDIR)/libexfat.Po +am__depfiles_maybe = depfiles am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -297,7 +297,7 @@ top_builddir = @top_builddir@ top_srcdir = @top_srcdir@ AM_CFLAGS = -Wall -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common noinst_LIBRARIES = libexfat.a -libexfat_a_SOURCES = libexfat.c +libexfat_a_SOURCES = libexfat.c exfat_fs.c exfat_dir.c all: all-am .SUFFIXES: @@ -319,8 +319,8 @@ Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) @@ -346,13 +346,9 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexfat.Po@am__quote@ # am--include-marker - -$(am__depfiles_remade): - @$(MKDIR_P) $(@D) - @echo '# dummy' >$@-t && $(am__mv) $@-t $@ - -am--depfiles: $(am__depfiles_remade) +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exfat_dir.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exfat_fs.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/libexfat.Po@am__quote@ .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @@ -436,10 +432,7 @@ cscopelist-am: $(am__tagged_files) distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags -distdir: $(BUILT_SOURCES) - $(MAKE) $(AM_MAKEFLAGS) distdir-am - -distdir-am: $(DISTFILES) +distdir: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ @@ -509,7 +502,7 @@ clean-am: clean-generic clean-libtool clean-noinstLIBRARIES \ mostlyclean-am distclean: distclean-am - -rm -f ./$(DEPDIR)/libexfat.Po + -rm -rf ./$(DEPDIR) -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags @@ -555,7 +548,7 @@ install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am - -rm -f ./$(DEPDIR)/libexfat.Po + -rm -rf ./$(DEPDIR) -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic @@ -576,16 +569,16 @@ uninstall-am: .MAKE: install-am install-strip -.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ - clean-generic clean-libtool clean-noinstLIBRARIES \ - cscopelist-am ctags ctags-am distclean distclean-compile \ - distclean-generic distclean-libtool distclean-tags distdir dvi \ - dvi-am html html-am info info-am install install-am \ - install-data install-data-am install-dvi install-dvi-am \ - install-exec install-exec-am install-html install-html-am \ - install-info install-info-am install-man install-pdf \ - install-pdf-am install-ps install-ps-am install-strip \ - installcheck installcheck-am installdirs maintainer-clean \ +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-noinstLIBRARIES cscopelist-am ctags \ + ctags-am distclean distclean-compile distclean-generic \ + distclean-libtool distclean-tags distdir dvi dvi-am html \ + html-am info info-am install install-am install-data \ + install-data-am install-dvi install-dvi-am install-exec \ + install-exec-am install-html install-html-am install-info \ + install-info-am install-man install-pdf install-pdf-am \ + install-ps install-ps-am install-strip installcheck \ + installcheck-am installdirs maintainer-clean \ maintainer-clean-generic mostlyclean mostlyclean-compile \ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \ tags tags-am uninstall uninstall-am diff --git a/lib/exfat_dir.c b/lib/exfat_dir.c new file mode 100644 index 0000000..98e820f --- /dev/null +++ b/lib/exfat_dir.c @@ -0,0 +1,955 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 LG Electronics. + * + * Author(s): Hyunchul Lee + */ +#include +#include +#include +#include +#include +#include + +#include "exfat_ondisk.h" +#include "libexfat.h" +#include "exfat_fs.h" +#include "exfat_dir.h" + +static struct path_resolve_ctx path_resolve_ctx; + +#define fsck_err(parent, inode, fmt, ...) \ +({ \ + exfat_resolve_path_parent(&path_resolve_ctx, \ + parent, inode); \ + exfat_err("ERROR: %s: " fmt, \ + path_resolve_ctx.local_path, \ + ##__VA_ARGS__); \ +}) + +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]; + + for (i = 0; i < iter->read_size / iter->write_size; i++) { + if (desc->dirty[i]) { + device_offset = exfat_c2o(exfat, desc->p_clus) + + desc->offset; + 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 = exfat_get_inode_next_clus(exfat, iter->parent, + p_clus, &ra_p_clus); + if (ret) + return ret; + + 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 = exfat_get_inode_next_clus(exfat, iter->parent, + prev_desc->p_clus, &desc->p_clus); + desc->offset = 0; + if (ret) + return ret; + else if (desc->p_clus == EXFAT_EOF_CLUSTER) + return EOF; + } + } + + 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, struct buffer_desc *bd) +{ + 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); + + iter->buffer_desc = bd; + + iter->de_file_offset = 0; + iter->next_read_offset = iter->read_size; + iter->max_skip_dentries = 0; + iter->invalid_name_num = 0; + + 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; + } + + 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; +} + +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_device_offset(struct exfat_de_iter *iter) +{ + struct buffer_desc *bd; + unsigned int block; + + if ((uint64_t)iter->de_file_offset >= iter->parent->size) + return EOF; + + block = iter->de_file_offset / iter->read_size; + bd = &iter->buffer_desc[block & 0x01]; + return exfat_c2o(iter->exfat, bd->p_clus) + bd->offset + + iter->de_file_offset % iter->read_size; +} + +off_t exfat_de_iter_file_offset(struct exfat_de_iter *iter) +{ + return iter->de_file_offset; +} + +/* + * try to find the dentry set matched with @filter. this function + * doesn't verify the dentry set. + * + * if found, return 0. if not found, return EOF. otherwise return errno. + */ +int exfat_lookup_dentry_set(struct exfat *exfat, struct exfat_inode *parent, + struct exfat_lookup_filter *filter) +{ + struct buffer_desc *bd = NULL; + struct exfat_dentry *dentry = NULL; + off_t free_file_offset = 0, free_dev_offset = 0; + struct exfat_de_iter de_iter; + int dentry_count, empty_dentry_count = 0; + int retval; + + bd = exfat_alloc_buffer(2, exfat->clus_size, exfat->sect_size); + if (!bd) + return -ENOMEM; + + retval = exfat_de_iter_init(&de_iter, exfat, parent, bd); + if (retval == EOF || retval) + goto out; + + filter->out.dentry_set = NULL; + while (1) { + retval = exfat_de_iter_get(&de_iter, 0, &dentry); + if (retval == EOF) { + break; + } else if (retval) { + fsck_err(parent->parent, parent, + "failed to get a dentry. %d\n", retval); + goto out; + } + + if (!IS_EXFAT_DELETED(dentry->type)) { + if (filter->in.dentry_count == 0 || + empty_dentry_count < filter->in.dentry_count) + empty_dentry_count = 0; + } + + dentry_count = 1; + if (dentry->type == filter->in.type) { + retval = 0; + if (filter->in.filter) + retval = filter->in.filter(&de_iter, + filter->in.param, + &dentry_count); + + if (retval == 0) { + struct exfat_dentry *d; + int i; + + filter->out.dentry_set = calloc(dentry_count, + sizeof(struct exfat_dentry)); + if (!filter->out.dentry_set) { + retval = -ENOMEM; + goto out; + } + for (i = 0; i < dentry_count; i++) { + exfat_de_iter_get(&de_iter, i, &d); + memcpy(filter->out.dentry_set + i, d, + sizeof(struct exfat_dentry)); + } + filter->out.dentry_count = dentry_count; + goto out; + } else if (retval < 0) { + goto out; + } + } else if (IS_EXFAT_DELETED(dentry->type)) { + if (empty_dentry_count == 0) { + free_file_offset = + exfat_de_iter_file_offset(&de_iter); + free_dev_offset = + exfat_de_iter_device_offset(&de_iter); + } + + if (filter->in.dentry_count == 0 || + empty_dentry_count < filter->in.dentry_count) + empty_dentry_count++; + } + + exfat_de_iter_advance(&de_iter, dentry_count); + } + +out: + if (retval == 0) { + filter->out.file_offset = + exfat_de_iter_file_offset(&de_iter); + filter->out.dev_offset = + exfat_de_iter_device_offset(&de_iter); + } else if (retval == EOF && empty_dentry_count) { + filter->out.file_offset = free_file_offset; + filter->out.dev_offset = free_dev_offset; + } else { + filter->out.file_offset = exfat_de_iter_file_offset(&de_iter); + filter->out.dev_offset = EOF; + } + if (bd) + exfat_free_buffer(bd, 2); + return retval; +} + +static int filter_lookup_file(struct exfat_de_iter *de_iter, + void *param, int *dentry_count) +{ + struct exfat_dentry *file_de, *stream_de, *name_de; + __le16 *name; + int retval, name_len; + int i; + + retval = exfat_de_iter_get(de_iter, 0, &file_de); + if (retval || file_de->type != EXFAT_FILE) + return 1; + + retval = exfat_de_iter_get(de_iter, 1, &stream_de); + if (retval || stream_de->type != EXFAT_STREAM) + return 1; + + name = (__le16 *)param; + name_len = (int)exfat_utf16_len(name, PATH_MAX); + + if (file_de->dentry.file.num_ext < + 1 + (name_len + ENTRY_NAME_MAX - 1) / ENTRY_NAME_MAX) + return 1; + + for (i = 2; i <= file_de->dentry.file.num_ext && name_len > 0; i++) { + int len; + + retval = exfat_de_iter_get(de_iter, i, &name_de); + if (retval || name_de->type != EXFAT_NAME) + return 1; + + len = MIN(name_len + 1, ENTRY_NAME_MAX); + if (memcmp(name_de->dentry.name.unicode_0_14, + name, len * 2) != 0) + return 1; + + name += len; + name_len -= len; + } + + *dentry_count = i; + return 0; +} + +int exfat_lookup_file_by_utf16name(struct exfat *exfat, + struct exfat_inode *parent, + __le16 *utf16_name, + struct exfat_lookup_filter *filter_out) +{ + int retval; + + filter_out->in.type = EXFAT_FILE; + filter_out->in.filter = filter_lookup_file; + filter_out->in.param = utf16_name; + filter_out->in.dentry_count = 0; + + retval = exfat_lookup_dentry_set(exfat, parent, filter_out); + if (retval < 0) + return retval; + + return 0; +} + +int exfat_lookup_file(struct exfat *exfat, struct exfat_inode *parent, + const char *name, struct exfat_lookup_filter *filter_out) +{ + int retval; + __le16 utf16_name[PATH_MAX + 2] = {0, }; + + retval = (int)exfat_utf16_enc(name, utf16_name, sizeof(utf16_name)); + if (retval < 0) + return retval; + + return exfat_lookup_file_by_utf16name(exfat, parent, utf16_name, + filter_out); +} + +void exfat_calc_dentry_checksum(struct exfat_dentry *dentry, + uint16_t *checksum, bool primary) +{ + unsigned int i; + uint8_t *bytes; + + bytes = (uint8_t *)dentry; + + /* use += to avoid promotion to int; UBSan complaints about signed overflow */ + *checksum = (*checksum << 15) | (*checksum >> 1); + *checksum += bytes[0]; + *checksum = (*checksum << 15) | (*checksum >> 1); + *checksum += bytes[1]; + + i = primary ? 4 : 2; + for (; i < sizeof(*dentry); i++) { + *checksum = (*checksum << 15) | (*checksum >> 1); + *checksum += bytes[i]; + } +} + +static uint16_t calc_dentry_set_checksum(struct exfat_dentry *dset, int dcount) +{ + uint16_t checksum; + int i; + + if (dcount < MIN_FILE_DENTRIES) + return 0; + + checksum = 0; + exfat_calc_dentry_checksum(&dset[0], &checksum, true); + for (i = 1; i < dcount; i++) + exfat_calc_dentry_checksum(&dset[i], &checksum, false); + return checksum; +} + +uint16_t exfat_calc_name_hash(struct exfat *exfat, + __le16 *name, int len) +{ + int i; + __le16 ch; + uint16_t chksum = 0; + + for (i = 0; i < len; i++) { + ch = exfat->upcase_table[le16_to_cpu(name[i])]; + ch = cpu_to_le16(ch); + + /* use += to avoid promotion to int; UBSan complaints about signed overflow */ + chksum = (chksum << 15) | (chksum >> 1); + chksum += ch & 0xFF; + chksum = (chksum << 15) | (chksum >> 1); + chksum += ch >> 8; + } + return chksum; +} + +static void unix_time_to_exfat_time(time_t unix_time, __u8 *tz, __le16 *date, + __le16 *time, __u8 *time_ms) +{ + struct tm tm; + __u16 t, d; + + gmtime_r(&unix_time, &tm); + d = ((tm.tm_year - 80) << 9) | ((tm.tm_mon + 1) << 5) | tm.tm_mday; + t = (tm.tm_hour << 11) | (tm.tm_min << 5) | (tm.tm_sec >> 1); + + *tz = 0x80; + *date = cpu_to_le16(d); + *time = cpu_to_le16(t); + if (time_ms) + *time_ms = (tm.tm_sec & 1) * 100; +} + +int exfat_build_file_dentry_set(struct exfat *exfat, const char *name, + unsigned short attr, struct exfat_dentry **dentry_set, + int *dentry_count) +{ + struct exfat_dentry *dset; + __le16 utf16_name[PATH_MAX + 2]; + int retval; + int dcount, name_len, i; + __le16 e_date, e_time; + __u8 tz, e_time_ms; + + memset(utf16_name, 0, sizeof(utf16_name)); + retval = exfat_utf16_enc(name, utf16_name, sizeof(utf16_name)); + if (retval < 0) + return retval; + + name_len = retval / 2; + dcount = 2 + DIV_ROUND_UP(name_len, ENTRY_NAME_MAX); + dset = calloc(1, dcount * DENTRY_SIZE); + if (!dset) + return -ENOMEM; + + dset[0].type = EXFAT_FILE; + dset[0].dentry.file.num_ext = dcount - 1; + dset[0].dentry.file.attr = cpu_to_le16(attr); + + unix_time_to_exfat_time(time(NULL), &tz, + &e_date, &e_time, &e_time_ms); + + dset[0].dentry.file.create_date = e_date; + dset[0].dentry.file.create_time = e_time; + dset[0].dentry.file.create_time_ms = e_time_ms; + dset[0].dentry.file.create_tz = tz; + + dset[0].dentry.file.modify_date = e_date; + dset[0].dentry.file.modify_time = e_time; + dset[0].dentry.file.modify_time_ms = e_time_ms; + dset[0].dentry.file.modify_tz = tz; + + dset[0].dentry.file.access_date = e_date; + dset[0].dentry.file.access_time = e_time; + dset[0].dentry.file.access_tz = tz; + + dset[1].type = EXFAT_STREAM; + dset[1].dentry.stream.flags = 0x01; + dset[1].dentry.stream.name_len = (__u8)name_len; + dset[1].dentry.stream.name_hash = + cpu_to_le16(exfat_calc_name_hash(exfat, utf16_name, name_len)); + + for (i = 2; i < dcount; i++) { + dset[i].type = EXFAT_NAME; + memcpy(dset[i].dentry.name.unicode_0_14, + utf16_name + (i - 2) * ENTRY_NAME_MAX, + ENTRY_NAME_MAX * 2); + } + + dset[0].dentry.file.checksum = + cpu_to_le16(calc_dentry_set_checksum(dset, dcount)); + + *dentry_set = dset; + *dentry_count = dcount; + return 0; +} + +int exfat_update_file_dentry_set(struct exfat *exfat, + struct exfat_dentry *dset, int dcount, + const char *name, + clus_t start_clu, clus_t ccount) +{ + int i, name_len; + __le16 utf16_name[PATH_MAX + 2]; + + if (dset[0].type != EXFAT_FILE || dcount < MIN_FILE_DENTRIES) + return -EINVAL; + + if (name) { + name_len = (int)exfat_utf16_enc(name, + utf16_name, sizeof(utf16_name)); + if (name_len < 0) + return name_len; + + name_len /= 2; + if (dcount != 2 + DIV_ROUND_UP(name_len, ENTRY_NAME_MAX)) + return -EINVAL; + + dset[1].dentry.stream.name_len = (__u8)name_len; + dset[1].dentry.stream.name_hash = + exfat_calc_name_hash(exfat, utf16_name, name_len); + + for (i = 2; i < dcount; i++) { + dset[i].type = EXFAT_NAME; + memcpy(dset[i].dentry.name.unicode_0_14, + utf16_name + (i - 2) * ENTRY_NAME_MAX, + ENTRY_NAME_MAX * 2); + } + } + + dset[1].dentry.stream.valid_size = cpu_to_le64(ccount * exfat->clus_size); + dset[1].dentry.stream.size = cpu_to_le64(ccount * exfat->clus_size); + if (start_clu) + dset[1].dentry.stream.start_clu = cpu_to_le32(start_clu); + + dset[0].dentry.file.checksum = + cpu_to_le16(calc_dentry_set_checksum(dset, dcount)); + return 0; +} + +static int find_free_cluster(struct exfat *exfat, + clus_t start, clus_t *new_clu) +{ + clus_t end = le32_to_cpu(exfat->bs->bsx.clu_count) + + EXFAT_FIRST_CLUSTER; + + if (!exfat_heap_clus(exfat, start)) + return -EINVAL; + + while (start < end) { + if (exfat_bitmap_find_zero(exfat, exfat->alloc_bitmap, + start, new_clu)) + break; + if (!exfat_bitmap_get(exfat->disk_bitmap, *new_clu)) + return 0; + start = *new_clu + 1; + } + + end = start; + start = EXFAT_FIRST_CLUSTER; + while (start < end) { + if (exfat_bitmap_find_zero(exfat, exfat->alloc_bitmap, + start, new_clu)) + goto out_nospc; + if (!exfat_bitmap_get(exfat->disk_bitmap, *new_clu)) + return 0; + start = *new_clu + 1; + } + +out_nospc: + *new_clu = EXFAT_EOF_CLUSTER; + return -ENOSPC; +} + +static int exfat_map_cluster(struct exfat *exfat, struct exfat_inode *inode, + off_t file_off, clus_t *mapped_clu) +{ + clus_t clu, next, count, last_count; + + if (!exfat_heap_clus(exfat, inode->first_clus)) + return -EINVAL; + + clu = inode->first_clus; + next = EXFAT_EOF_CLUSTER; + count = 1; + if (file_off == EOF) + last_count = DIV_ROUND_UP(inode->size, exfat->clus_size); + else + last_count = file_off / exfat->clus_size + 1; + + while (true) { + if (count * exfat->clus_size > inode->size) + return -EINVAL; + + if (count == last_count) { + *mapped_clu = clu; + return 0; + } + + if (exfat_get_inode_next_clus(exfat, inode, clu, &next)) + return -EINVAL; + + if (!exfat_heap_clus(exfat, clu)) + return -EINVAL; + + clu = next; + count++; + } + return -EINVAL; +} + +static int exfat_write_dentry_set(struct exfat *exfat, + struct exfat_dentry *dset, int dcount, + off_t dev_off, off_t *next_dev_off) +{ + clus_t clus; + unsigned int clus_off, dent_len, first_half_len, sec_half_len; + off_t first_half_off, sec_half_off = 0; + + if (exfat_o2c(exfat, dev_off, &clus, &clus_off)) + return -ERANGE; + + dent_len = dcount * DENTRY_SIZE; + first_half_len = MIN(dent_len, exfat->clus_size - clus_off); + sec_half_len = dent_len - first_half_len; + + first_half_off = dev_off; + if (sec_half_len) { + clus_t next_clus; + + if (exfat_get_next_clus(exfat, clus, &next_clus)) + return -EIO; + if (!exfat_heap_clus(exfat, next_clus)) + return -EINVAL; + sec_half_off = exfat_c2o(exfat, next_clus); + } + + if (exfat_write(exfat->blk_dev->dev_fd, dset, first_half_len, + first_half_off) != (ssize_t)first_half_len) + return -EIO; + + if (sec_half_len) { + dset = (struct exfat_dentry *)((char *)dset + first_half_len); + if (exfat_write(exfat->blk_dev->dev_fd, dset, sec_half_len, + sec_half_off) != (ssize_t)sec_half_len) + return -EIO; + } + + if (next_dev_off) { + if (sec_half_len) + *next_dev_off = sec_half_off + sec_half_len; + else + *next_dev_off = first_half_off + first_half_len; + } + return 0; +} + +static int exfat_alloc_cluster(struct exfat *exfat, struct exfat_inode *inode, + clus_t *new_clu) +{ + clus_t last_clu; + int err; + bool need_dset = inode != exfat->root; + + if ((need_dset && !inode->dentry_set) || inode->is_contiguous) + return -EINVAL; + + err = find_free_cluster(exfat, exfat->start_clu, new_clu); + if (err) { + exfat->start_clu = EXFAT_FIRST_CLUSTER; + exfat_err("failed to find an free cluster\n"); + return -ENOSPC; + } + exfat->start_clu = *new_clu; + + if (exfat_set_fat(exfat, *new_clu, EXFAT_EOF_CLUSTER)) + return -EIO; + + /* zero out the new cluster */ + if (exfat_write(exfat->blk_dev->dev_fd, exfat->zero_cluster, + exfat->clus_size, exfat_c2o(exfat, *new_clu)) != + (ssize_t)exfat->clus_size) { + exfat_err("failed to fill new cluster with zeroes\n"); + return -EIO; + } + + if (inode->size) { + err = exfat_map_cluster(exfat, inode, EOF, &last_clu); + if (err) { + exfat_err("failed to get the last cluster\n"); + return err; + } + + if (exfat_set_fat(exfat, last_clu, *new_clu)) + return -EIO; + + if (need_dset) { + err = exfat_update_file_dentry_set(exfat, + inode->dentry_set, + inode->dentry_count, + NULL, 0, + DIV_ROUND_UP(inode->size, + exfat->clus_size) + 1); + if (err) + return -EINVAL; + } + } else { + if (need_dset) { + err = exfat_update_file_dentry_set(exfat, + inode->dentry_set, + inode->dentry_count, + NULL, *new_clu, 1); + if (err) + return -EINVAL; + } + } + + if (need_dset && exfat_write_dentry_set(exfat, inode->dentry_set, + inode->dentry_count, + inode->dev_offset, NULL)) + return -EIO; + + exfat_bitmap_set(exfat->alloc_bitmap, *new_clu); + if (inode->size == 0) + inode->first_clus = *new_clu; + inode->size += exfat->clus_size; + return 0; +} + +int exfat_add_dentry_set(struct exfat *exfat, struct exfat_dentry_loc *loc, + struct exfat_dentry *dset, int dcount, + bool need_next_loc) +{ + struct exfat_inode *parent = loc->parent; + off_t dev_off, next_dev_off; + + if (parent->is_contiguous || + (uint64_t)loc->file_offset > parent->size || + (unsigned int)dcount * DENTRY_SIZE > exfat->clus_size) + return -EINVAL; + + dev_off = loc->dev_offset; + if ((uint64_t)loc->file_offset + dcount * DENTRY_SIZE > parent->size) { + clus_t new_clus; + + if (exfat_alloc_cluster(exfat, parent, &new_clus)) + return -EIO; + if ((uint64_t)loc->file_offset == parent->size - exfat->clus_size) + dev_off = exfat_c2o(exfat, new_clus); + } + + if (exfat_write_dentry_set(exfat, dset, dcount, dev_off, &next_dev_off)) + return -EIO; + + if (need_next_loc) { + loc->file_offset += dcount * DENTRY_SIZE; + loc->dev_offset = next_dev_off; + } + return 0; +} + +int exfat_create_file(struct exfat *exfat, struct exfat_inode *parent, + const char *name, unsigned short attr) +{ + struct exfat_dentry *dset; + int err, dcount; + struct exfat_lookup_filter filter; + struct exfat_dentry_loc loc; + + err = exfat_lookup_file(exfat, parent, name, &filter); + if (err == 0) { + dset = filter.out.dentry_set; + dcount = filter.out.dentry_count; + if ((le16_to_cpu(dset->dentry.file.attr) & attr) != attr) + err = -EEXIST; + goto out; + } + + err = exfat_build_file_dentry_set(exfat, name, attr, + &dset, &dcount); + if (err) + return err; + + loc.parent = parent; + loc.file_offset = filter.out.file_offset; + loc.dev_offset = filter.out.dev_offset; + err = exfat_add_dentry_set(exfat, &loc, dset, dcount, false); +out: + free(dset); + return err; +} diff --git a/lib/exfat_fs.c b/lib/exfat_fs.c new file mode 100644 index 0000000..d563c61 --- /dev/null +++ b/lib/exfat_fs.c @@ -0,0 +1,304 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +/* + * Copyright (C) 2021 LG Electronics. + * + * Author(s): Hyunchul Lee + */ + +#include +#include +#include +#include + +#include "exfat_ondisk.h" +#include "libexfat.h" + +#include "exfat_fs.h" +#include "exfat_dir.h" + +struct exfat_inode *exfat_alloc_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->attr = attr; + return node; +} + +void exfat_free_inode(struct exfat_inode *node) +{ + if (node) { + if (node->dentry_set) + free(node->dentry_set); + free(node); + } +} + +void exfat_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); + exfat_free_inode(node); + } + } else { + list_del(&node->sibling); + list_del(&node->list); + exfat_free_inode(node); + } + } +} + +void exfat_free_file_children(struct exfat_inode *dir) +{ + exfat_free_children(dir, true); +} + +/* delete @child and all ancestors that does not have + * children + */ +void exfat_free_ancestors(struct exfat_inode *child) +{ + struct exfat_inode *parent; + + while (child && list_empty(&child->children)) { + if (!child->parent || !(child->attr & ATTR_SUBDIR)) + return; + + parent = child->parent; + list_del(&child->sibling); + exfat_free_inode(child); + + child = parent; + } + return; +} + +void exfat_free_dir_list(struct exfat *exfat) +{ + struct exfat_inode *dir, *i; + + list_for_each_entry_safe(dir, i, &exfat->dir_list, list) { + if (!dir->parent) + continue; + exfat_free_file_children(dir); + list_del(&dir->list); + exfat_free_inode(dir); + } +} + +void exfat_free_exfat(struct exfat *exfat) +{ + if (exfat) { + if (exfat->bs) + free(exfat->bs); + if (exfat->alloc_bitmap) + free(exfat->alloc_bitmap); + if (exfat->disk_bitmap) + free(exfat->disk_bitmap); + if (exfat->ohead_bitmap) + free(exfat->ohead_bitmap); + if (exfat->upcase_table) + free(exfat->upcase_table); + if (exfat->root) + exfat_free_inode(exfat->root); + if (exfat->zero_cluster) + free(exfat->zero_cluster); + free(exfat); + } +} + +struct exfat *exfat_alloc_exfat(struct exfat_blk_dev *blk_dev, struct pbr *bs) +{ + struct exfat *exfat; + + exfat = (struct exfat *)calloc(1, sizeof(*exfat)); + if (!exfat) + return NULL; + + INIT_LIST_HEAD(&exfat->dir_list); + exfat->blk_dev = blk_dev; + 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->ohead_bitmap = + calloc(1, EXFAT_BITMAP_SIZE(exfat->clus_count)); + if (!exfat->ohead_bitmap) { + exfat_err("failed to allocate bitmap\n"); + goto err; + } + + exfat->disk_bitmap = + calloc(1, EXFAT_BITMAP_SIZE(exfat->clus_count)); + if (!exfat->disk_bitmap) { + exfat_err("failed to allocate bitmap\n"); + goto err; + } + + exfat->zero_cluster = calloc(1, exfat->clus_size); + if (!exfat->zero_cluster) { + exfat_err("failed to allocate a zero-filled cluster buffer\n"); + goto err; + } + + exfat->start_clu = EXFAT_FIRST_CLUSTER; + return exfat; +err: + exfat_free_exfat(exfat); + return NULL; +} + +struct buffer_desc *exfat_alloc_buffer(int count, + unsigned int clu_size, unsigned int sect_size) +{ + struct buffer_desc *bd; + int i; + + bd = (struct buffer_desc *)calloc(sizeof(*bd), count); + if (!bd) + return NULL; + + for (i = 0; i < count; i++) { + bd[i].buffer = (char *)malloc(clu_size); + if (!bd[i].buffer) + goto err; + bd[i].dirty = (char *)calloc(clu_size / sect_size, 1); + if (!bd[i].dirty) + goto err; + } + return bd; +err: + exfat_free_buffer(bd, count); + return NULL; +} + +void exfat_free_buffer(struct buffer_desc *bd, int count) +{ + int i; + + for (i = 0; i < count; i++) { + if (bd[i].buffer) + free(bd[i].buffer); + if (bd[i].dirty) + free(bd[i].dirty); + } + free(bd); +} + +/* + * 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. + */ +static 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; +} + +int exfat_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 > 1) + 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)); +} + +int exfat_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 = exfat_resolve_path(ctx, child); + child->parent = old; + return ret; +} diff --git a/lib/libexfat.c b/lib/libexfat.c index c54a7c8..d7b8344 100644 --- a/lib/libexfat.c +++ b/lib/libexfat.c @@ -15,68 +15,62 @@ #include #include #include +#include #include "exfat_ondisk.h" #include "libexfat.h" #include "version.h" - -#define BITS_PER_LONG (sizeof(long) * CHAR_BIT) - -#ifdef WORDS_BIGENDIAN -#define BITOP_LE_SWIZZLE ((BITS_PER_LONG - 1) & ~0x7) -#else -#define BITOP_LE_SWIZZLE 0 -#endif - -#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) -#define BIT_WORD(nr) ((nr) / BITS_PER_LONG) +#include "exfat_fs.h" +#include "exfat_dir.h" unsigned int print_level = EXFAT_INFO; -static inline void set_bit(int nr, void *addr) +void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap, + clus_t start_clus, clus_t count) { - unsigned long mask = BIT_MASK(nr); - unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); + clus_t clus; - *p |= mask; -} + if (!exfat_heap_clus(exfat, start_clus) || + !exfat_heap_clus(exfat, start_clus + count - 1)) + return; -static inline void clear_bit(int nr, void *addr) -{ - unsigned long mask = BIT_MASK(nr); - unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); - - *p &= ~mask; -} - -static inline void set_bit_le(int nr, void *addr) -{ - set_bit(nr ^ BITOP_LE_SWIZZLE, addr); + clus = start_clus; + while (clus < start_clus + count) { + exfat_bitmap_set(bitmap, clus); + clus++; + } } -static inline void clear_bit_le(int nr, void *addr) +static int exfat_bitmap_find_bit(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next, + int bit) { - clear_bit(nr ^ BITOP_LE_SWIZZLE, addr); + clus_t last_clu; + + last_clu = le32_to_cpu(exfat->bs->bsx.clu_count) + + EXFAT_FIRST_CLUSTER; + while (start_clu < last_clu) { + if (!!exfat_bitmap_get(bmap, start_clu) == bit) { + *next = start_clu; + return 0; + } + start_clu++; + } + return 1; } -void exfat_set_bit(struct exfat_blk_dev *bd, char *bitmap, - unsigned int clu) +int exfat_bitmap_find_zero(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next) { - int b; - - b = clu & ((bd->sector_size << 3) - 1); - - set_bit_le(b, bitmap); + return exfat_bitmap_find_bit(exfat, bmap, + start_clu, next, 0); } -void exfat_clear_bit(struct exfat_blk_dev *bd, char *bitmap, - unsigned int clu) +int exfat_bitmap_find_one(struct exfat *exfat, char *bmap, + clus_t start_clu, clus_t *next) { - int b; - - b = clu & ((bd->sector_size << 3) - 1); - - clear_bit_le(b, bitmap); + return exfat_bitmap_find_bit(exfat, bmap, + start_clu, next, 1); } wchar_t exfat_bad_char(wchar_t w) @@ -374,6 +368,12 @@ off_t exfat_get_root_entry_offset(struct exfat_blk_dev *bd) return -1; } + if (memcmp(bs->bpb.oem_name, "EXFAT ", 8) != 0) { + exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n"); + free(bs); + return -1; + } + sector_size = 1 << bs->bsx.sect_size_bits; cluster_size = (1 << bs->bsx.sect_per_clus_bits) * sector_size; root_clu_off = le32_to_cpu(bs->bsx.clu_offset) * sector_size + @@ -405,78 +405,263 @@ char *exfat_conv_volume_label(struct exfat_dentry *vol_entry) return volume_label; } -int exfat_show_volume_label(struct exfat_blk_dev *bd, off_t root_clu_off) +int exfat_read_volume_label(struct exfat *exfat) { - struct exfat_dentry *vol_entry; - char *volume_label; - int nbytes; + struct exfat_dentry *dentry; + int err; + __le16 disk_label[VOLUME_LABEL_MAX_LEN]; + struct exfat_lookup_filter filter = { + .in.type = EXFAT_VOLUME, + .in.dentry_count = 0, + .in.filter = NULL, + }; - vol_entry = malloc(sizeof(struct exfat_dentry)); - if (!vol_entry) { - exfat_err("failed to allocate memory\n"); - return -ENOMEM; - } + err = exfat_lookup_dentry_set(exfat, exfat->root, &filter); + if (err) + return err; - nbytes = exfat_read(bd->dev_fd, vol_entry, - sizeof(struct exfat_dentry), root_clu_off); - if (nbytes != sizeof(struct exfat_dentry)) { - exfat_err("volume entry read failed: %d\n", errno); - free(vol_entry); - return -1; - } + dentry = filter.out.dentry_set; - volume_label = exfat_conv_volume_label(vol_entry); - if (!volume_label) { - free(vol_entry); - return -EINVAL; + if (dentry->vol_char_cnt == 0) + goto out; + + if (dentry->vol_char_cnt > VOLUME_LABEL_MAX_LEN) { + exfat_err("too long label. %d\n", dentry->vol_char_cnt); + err = -EINVAL; + goto out; } - exfat_info("label: %s\n", volume_label); + memcpy(disk_label, dentry->vol_label, sizeof(disk_label)); + if (exfat_utf16_dec(disk_label, dentry->vol_char_cnt*2, + exfat->volume_label, sizeof(exfat->volume_label)) < 0) { + exfat_err("failed to decode volume label\n"); + err = -EINVAL; + goto out; + } - free(volume_label); - free(vol_entry); - return 0; + exfat_info("label: %s\n", exfat->volume_label); +out: + free(filter.out.dentry_set); + return err; } -int exfat_set_volume_label(struct exfat_blk_dev *bd, - char *label_input, off_t root_clu_off) +int exfat_set_volume_label(struct exfat *exfat, char *label_input) { - struct exfat_dentry vol; - int nbytes; + struct exfat_dentry *pvol; + struct exfat_dentry_loc loc; __u16 volume_label[VOLUME_LABEL_MAX_LEN]; - int volume_label_len; + int volume_label_len, dcount, err; + + struct exfat_lookup_filter filter = { + .in.type = EXFAT_VOLUME, + .in.dentry_count = 1, + .in.filter = NULL, + }; + + err = exfat_lookup_dentry_set(exfat, exfat->root, &filter); + if (!err) { + pvol = filter.out.dentry_set; + dcount = filter.out.dentry_count; + memset(pvol->vol_label, 0, sizeof(pvol->vol_label)); + } else { + pvol = calloc(sizeof(struct exfat_dentry), 1); + if (!pvol) + return -ENOMEM; + + dcount = 1; + pvol->type = EXFAT_VOLUME; + } volume_label_len = exfat_utf16_enc(label_input, volume_label, sizeof(volume_label)); if (volume_label_len < 0) { exfat_err("failed to encode volume label\n"); + free(pvol); return -1; } - vol.type = EXFAT_VOLUME; - memset(vol.vol_label, 0, sizeof(vol.vol_label)); - memcpy(vol.vol_label, volume_label, volume_label_len); - vol.vol_char_cnt = volume_label_len/2; + memcpy(pvol->vol_label, volume_label, volume_label_len); + pvol->vol_char_cnt = volume_label_len/2; - nbytes = exfat_write(bd->dev_fd, &vol, sizeof(struct exfat_dentry), - root_clu_off); - if (nbytes != sizeof(struct exfat_dentry)) { - exfat_err("volume entry write failed: %d\n", errno); - return -1; + loc.parent = exfat->root; + loc.file_offset = filter.out.file_offset; + loc.dev_offset = filter.out.dev_offset; + err = exfat_add_dentry_set(exfat, &loc, pvol, dcount, false); + exfat_info("new label: %s\n", label_input); + + free(pvol); + + return err; +} + +static inline void print_guid(const char *msg, const __u8 *guid) +{ + exfat_info("%s: %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\n", + msg, + guid[0], guid[1], guid[2], guid[3], + guid[4], guid[5], guid[5], guid[7], + guid[8], guid[9], guid[10], guid[11], + guid[12], guid[13], guid[14], guid[15]); +} + +static int set_guid(__u8 *guid, const char *input) +{ + int i, j, zero_len = 0; + int len = strlen(input); + + if (len != VOLUME_GUID_LEN * 2 && len != VOLUME_GUID_LEN * 2 + 4) { + exfat_err("invalid format for volume guid\n"); + return -EINVAL; + } + + for (i = 0, j = 0; i < len; i++) { + unsigned char ch = input[i]; + + if (ch >= '0' && ch <= '9') + ch -= '0'; + else if (ch >= 'a' && ch <= 'f') + ch -= 'a' - 0xA; + else if (ch >= 'A' && ch <= 'F') + ch -= 'A' - 0xA; + else if (ch == '-' && len == VOLUME_GUID_LEN * 2 + 4 && + (i == 8 || i == 13 || i == 18 || i == 23)) + continue; + else { + exfat_err("invalid character '%c' for volume GUID\n", ch); + return -EINVAL; + } + + if (j & 1) + guid[j >> 1] |= ch; + else + guid[j >> 1] = ch << 4; + + j++; + + if (ch == 0) + zero_len++; + } + + if (zero_len == VOLUME_GUID_LEN * 2) { + exfat_err("%s is invalid for volume GUID\n", input); + return -EINVAL; } - fsync(bd->dev_fd); - exfat_info("new label: %s\n", label_input); return 0; } +int exfat_read_volume_guid(struct exfat *exfat) +{ + int err; + uint16_t checksum = 0; + struct exfat_dentry *dentry; + struct exfat_lookup_filter filter = { + .in.type = EXFAT_GUID, + .in.dentry_count = 1, + .in.filter = NULL, + }; + + err = exfat_lookup_dentry_set(exfat, exfat->root, &filter); + if (err) + return err; + + dentry = filter.out.dentry_set; + exfat_calc_dentry_checksum(dentry, &checksum, true); + + if (cpu_to_le16(checksum) == dentry->dentry.guid.checksum) + print_guid("GUID", dentry->dentry.guid.guid); + else + exfat_info("GUID is corrupted, please delete it or set a new one\n"); + + free(dentry); + + return err; +} + +int __exfat_set_volume_guid(struct exfat_dentry *dentry, const char *guid) +{ + int err; + uint16_t checksum = 0; + + memset(dentry, 0, sizeof(*dentry)); + dentry->type = EXFAT_GUID; + + err = set_guid(dentry->dentry.guid.guid, guid); + if (err) + return err; + + exfat_calc_dentry_checksum(dentry, &checksum, true); + dentry->dentry.guid.checksum = cpu_to_le16(checksum); + + return 0; +} + +/* + * Create/Update/Delete GUID dentry in root directory + * + * create/update GUID if @guid is not NULL. + * delete GUID if @guid is NULL. + */ +int exfat_set_volume_guid(struct exfat *exfat, const char *guid) +{ + struct exfat_dentry *dentry; + struct exfat_dentry_loc loc; + int err; + + struct exfat_lookup_filter filter = { + .in.type = EXFAT_GUID, + .in.dentry_count = 1, + .in.filter = NULL, + }; + + err = exfat_lookup_dentry_set(exfat, exfat->root, &filter); + if (!err) { + /* GUID entry is found */ + dentry = filter.out.dentry_set; + } else { + /* no GUID to delete */ + if (guid == NULL) + return 0; + + dentry = calloc(1, sizeof(*dentry)); + if (!dentry) + return -ENOMEM; + } + + if (guid) { + /* Set GUID */ + err = __exfat_set_volume_guid(dentry, guid); + if (err) + goto out; + } else { + /* Delete GUID */ + dentry->type &= ~EXFAT_INVAL; + } + + loc.parent = exfat->root; + loc.file_offset = filter.out.file_offset; + loc.dev_offset = filter.out.dev_offset; + err = exfat_add_dentry_set(exfat, &loc, dentry, 1, false); + if (!err) { + if (guid) + print_guid("new GUID", dentry->dentry.guid.guid); + else + exfat_info("GUID is deleted\n"); + } + +out: + free(dentry); + + return err; +} + int exfat_read_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off) { int ret; - unsigned long long offset = sec_off * bd->sector_size; + unsigned long long offset = + (unsigned long long)sec_off * bd->sector_size; - lseek(bd->dev_fd, offset, SEEK_SET); - ret = read(bd->dev_fd, buf, bd->sector_size); + ret = pread(bd->dev_fd, buf, bd->sector_size, offset); if (ret < 0) { exfat_err("read failed, sec_off : %u\n", sec_off); return -1; @@ -488,10 +673,10 @@ int exfat_write_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off) { int bytes; - unsigned long long offset = sec_off * bd->sector_size; + unsigned long long offset = + (unsigned long long)sec_off * bd->sector_size; - lseek(bd->dev_fd, offset, SEEK_SET); - bytes = write(bd->dev_fd, buf, bd->sector_size); + bytes = pwrite(bd->dev_fd, buf, bd->sector_size, offset); if (bytes != (int)bd->sector_size) { exfat_err("write failed, sec_off : %u, bytes : %d\n", sec_off, bytes); @@ -548,6 +733,12 @@ int exfat_show_volume_serial(int fd) goto free_ppbr; } + if (memcmp(ppbr->bpb.oem_name, "EXFAT ", 8) != 0) { + exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n"); + ret = -1; + goto free_ppbr; + } + exfat_info("volume serial : 0x%x\n", ppbr->bsx.vol_serial); free_ppbr: @@ -616,6 +807,12 @@ int exfat_set_volume_serial(struct exfat_blk_dev *bd, goto free_ppbr; } + if (memcmp(ppbr->bpb.oem_name, "EXFAT ", 8) != 0) { + exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n"); + ret = -1; + goto free_ppbr; + } + bd->sector_size = 1 << ppbr->bsx.sect_size_bits; ppbr->bsx.vol_serial = ui->volume_serial; @@ -658,3 +855,165 @@ unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd, return clu_off_sectnr * bd->sector_size + (clu - EXFAT_RESERVED_CLUSTERS) * bd->cluster_size; } + +int exfat_get_next_clus(struct exfat *exfat, clus_t clus, clus_t *next) +{ + off_t offset; + + *next = EXFAT_EOF_CLUSTER; + + if (!exfat_heap_clus(exfat, clus)) + return -EINVAL; + + 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; +} + +int exfat_get_inode_next_clus(struct exfat *exfat, struct exfat_inode *node, + clus_t clus, clus_t *next) +{ + *next = EXFAT_EOF_CLUSTER; + + if (node->is_contiguous) { + if (!exfat_heap_clus(exfat, clus)) + return -EINVAL; + *next = clus + 1; + return 0; + } + + return exfat_get_next_clus(exfat, clus, next); +} + +int exfat_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; +} + +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) +{ + assert(clus >= EXFAT_FIRST_CLUSTER); + + 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)); +} + +int exfat_o2c(struct exfat *exfat, off_t device_offset, + unsigned int *clu, unsigned int *offset) +{ + off_t heap_offset; + + heap_offset = exfat_s2o(exfat, le32_to_cpu(exfat->bs->bsx.clu_offset)); + if (device_offset < heap_offset) + return -ERANGE; + + *clu = (unsigned int)((device_offset - heap_offset) / + exfat->clus_size) + EXFAT_FIRST_CLUSTER; + if (!exfat_heap_clus(exfat, *clu)) + return -ERANGE; + *offset = (device_offset - heap_offset) % exfat->clus_size; + return 0; +} + +bool exfat_heap_clus(struct exfat *exfat, clus_t clus) +{ + return clus >= EXFAT_FIRST_CLUSTER && + (clus - EXFAT_FIRST_CLUSTER) < exfat->clus_count; +} + +int exfat_root_clus_count(struct exfat *exfat) +{ + struct exfat_inode *node = exfat->root; + clus_t clus, next; + int clus_count = 0; + + if (!exfat_heap_clus(exfat, node->first_clus)) + return -EIO; + + clus = node->first_clus; + do { + if (exfat_bitmap_get(exfat->alloc_bitmap, clus)) + return -EINVAL; + + exfat_bitmap_set(exfat->alloc_bitmap, clus); + + if (exfat_get_inode_next_clus(exfat, node, clus, &next)) { + exfat_err("ERROR: failed to read the fat entry of root"); + return -EIO; + } + + if (next != EXFAT_EOF_CLUSTER && !exfat_heap_clus(exfat, next)) + return -EINVAL; + + clus = next; + clus_count++; + } while (clus != EXFAT_EOF_CLUSTER); + + node->size = clus_count * exfat->clus_size; + return 0; +} + +int read_boot_sect(struct exfat_blk_dev *bdev, struct pbr **bs) +{ + struct pbr *pbr; + int err = 0; + unsigned int sect_size, clu_size; + + pbr = malloc(sizeof(struct pbr)); + + if (exfat_read(bdev->dev_fd, pbr, sizeof(*pbr), 0) != + (ssize_t)sizeof(*pbr)) { + exfat_err("failed to read a boot sector\n"); + err = -EIO; + goto err; + } + + err = -EINVAL; + if (memcmp(pbr->bpb.oem_name, "EXFAT ", 8) != 0) { + exfat_err("failed to find exfat file system\n"); + goto err; + } + + sect_size = 1 << pbr->bsx.sect_size_bits; + clu_size = 1 << (pbr->bsx.sect_size_bits + + pbr->bsx.sect_per_clus_bits); + + if (sect_size < 512 || sect_size > 4 * KB) { + exfat_err("too small or big sector size: %d\n", + sect_size); + goto err; + } + + if (clu_size < sect_size || clu_size > 32 * MB) { + exfat_err("too small or big cluster size: %d\n", + clu_size); + goto err; + } + + *bs = pbr; + return 0; +err: + free(pbr); + return err; +} diff --git a/m4/libtool.m4 b/m4/libtool.m4 index a6d21ae..ee80844 100644 --- a/m4/libtool.m4 +++ b/m4/libtool.m4 @@ -1041,8 +1041,8 @@ int forced_loaded() { return 2;} _LT_EOF echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD - echo "$AR cr libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD - $AR cr libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD + echo "$AR cru libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD + $AR cru libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD cat > conftest.c << _LT_EOF @@ -1492,7 +1492,7 @@ need_locks=$enable_libtool_lock m4_defun([_LT_PROG_AR], [AC_CHECK_TOOLS(AR, [ar], false) : ${AR=ar} -: ${AR_FLAGS=cr} +: ${AR_FLAGS=cru} _LT_DECL([], [AR], [1], [The archiver]) _LT_DECL([], [AR_FLAGS], [1], [Flags to create an archive]) @@ -4063,8 +4063,7 @@ _LT_EOF if AC_TRY_EVAL(ac_compile); then # Now try to grab the symbols. nlist=conftest.nm - $ECHO "$as_me:$LINENO: $NM conftest.$ac_objext | $lt_cv_sys_global_symbol_pipe > $nlist" >&AS_MESSAGE_LOG_FD - if eval "$NM" conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist 2>&AS_MESSAGE_LOG_FD && test -s "$nlist"; then + if AC_TRY_EVAL(NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) && test -s "$nlist"; then # Try sorting and uniquifying the output. if sort "$nlist" | uniq > "$nlist"T; then mv -f "$nlist"T "$nlist" @@ -4704,12 +4703,6 @@ m4_if([$1], [CXX], [ _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' ;; - # flang / f18. f95 an alias for gfortran or flang on Debian - flang* | f18* | f95*) - _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' - _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' - _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' - ;; # icc used to be incompatible with GCC. # ICC 10 doesn't accept -KPIC any more. icc* | ifort*) @@ -6445,7 +6438,7 @@ if test yes != "$_lt_caught_CXX_error"; then # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. - output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' else GXX=no @@ -6820,7 +6813,7 @@ if test yes != "$_lt_caught_CXX_error"; then # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. - output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "\-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes = "$GXX"; then @@ -6885,7 +6878,7 @@ if test yes != "$_lt_caught_CXX_error"; then # explicitly linking system object files so we need to strip them # from the output so that they don't get included in the library # dependencies. - output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP " \-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP "\-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' ;; *) if test yes = "$GXX"; then @@ -7224,7 +7217,7 @@ if test yes != "$_lt_caught_CXX_error"; then # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. - output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' else # FIXME: insert proper C++ library support @@ -7308,7 +7301,7 @@ if test yes != "$_lt_caught_CXX_error"; then # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. - output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' else # g++ 2.7 appears to require '-G' NOT '-shared' on this # platform. @@ -7319,7 +7312,7 @@ if test yes != "$_lt_caught_CXX_error"; then # Commands to make compiler produce verbose output that lists # what "hidden" libraries, object files and flags are used when # linking a shared library. - output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP " \-L"' + output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' fi _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $wl$libdir' diff --git a/manpages/exfat2img.8 b/manpages/exfat2img.8 new file mode 100644 index 0000000..bceaa08 --- /dev/null +++ b/manpages/exfat2img.8 @@ -0,0 +1,36 @@ +.TH exfat2img 8 +.SH NAME +exfat2img \- dump metadata of an exFAT filesystem +.SH SYNOPSIS +.B exfat2img +[ +.B \-o \fIpath\fB\ +] [ +.B \-V +] +.I device +.br +.B exfat2img \-V +.SH DESCRIPTION +.B exfat2img +dump metadata of exFAT filesystems for debugging. \fBexfat2img\fP dump boot sector, File Allcation Table, Bitmap and all metadata which can reach from root directory. + +.SH OPTIONS +.TP +.BI \-o\ \-\-output +Specify output result file. If filesystem to which output file is written does not support sparse file, you should use '-' in place of \fIpath\fP. +Because a dump image generated from stdout has a special format, when restoring a partition from it, exfat2img should be used. See Examples. +.TP +.B \-V +Prints the version number and exits. + +.SH EXAMPLES +.PP +Dump metadata into a sparse file. +.EX +.RB "$" " exfat2img -o sda1.dump /dev/sda1 + +Dump metadata into standard out and restore a partition +.EX +.RB "$" " exfat2img -o - /dev/sda1 | bzip2 > sda1.dump.bz2" +.RB "$" " bzip2 -dc sda1.dump.bz2 | exfat2img -o /dev/sdb1 -" diff --git a/manpages/exfatlabel.8 b/manpages/exfatlabel.8 index f3274bb..40c8d56 100644 --- a/manpages/exfatlabel.8 +++ b/manpages/exfatlabel.8 @@ -5,9 +5,8 @@ exfatlabel \- Get or Set volume label or volume serial of an exFAT filesystem .B exfatlabel [ .B \-i -.I volume-label ] [ -.B \-v +.B \-V ] .I device [ @@ -21,15 +20,14 @@ Print or set volume label of an existing exFAT filesystem. If there is a .I label_string -in argument of exfatlabel, It will be written to volume label -field on given device. If not, exfatlabel will just print out -after reading volume label field from given device. If -i or ---volume-serial is given, It can be switched to volume serial -mode. +in the argument of exfatlabel, it will be written to the volume +label field on a given device. If not, exfatlabel will just print +it after reading the volume label field from the given device. If -i +or --volume-serial is given, it will switch to volume serial mode. .PP .SH OPTIONS .TP -.BI \-i +.BI \-i\ \-\-volume-serial Switch to volume serial mode. .TP .B \-V diff --git a/manpages/fsck.exfat.8 b/manpages/fsck.exfat.8 index 83f7815..5faecf0 100644 --- a/manpages/fsck.exfat.8 +++ b/manpages/fsck.exfat.8 @@ -14,6 +14,8 @@ fsck.exfat \- check an exFAT filesystem ] [ .B \-y ] [ +.B \-b +] [ .B \-v ] .I device @@ -22,7 +24,29 @@ fsck.exfat \- check an exFAT filesystem .SH DESCRIPTION .B fsck.exfat checks an exFAT filesystem and repairs the filesystem -depending on the options passed. +depending on the options passed. The following corruptions can be repaired, and see the option, '-s'. +.IP - +Boot sector has invalid parameters. If backup boot sector is valid, replace the boot sector with it. +.IP - +Cluster is marked as free but belonged to a file, or vise versa. The bitmap for the cluster is marked properly. +.IP - +File size is abnormally large considering the count of clusters. The size is changed. +.IP - +File size is abnormally small considering the count of clusters. The remaining clusters are deleted. +.IP - +File's cluster chain has an invalid cluster number. The number are changed to EOF, and the file size is also changed. +.IP - +File's cluster chain contains a loop. The loop is broken. +.IP - +Files share the same cluster. Cluster chains for files except one are broken. +.IP - +Start cluster number is invalid. The cluster number and file size are changed to 0. +.IP - +Checksum value of directory entry set is invalid. Directory entry set is deleted. +.IP - +Bad hash value of a file name. The hash value is changed properly. +.IP - +Fields of directory entry set have invalid values. Directory entry set is deleted. .PP .SH OPTIONS .TP @@ -38,6 +62,9 @@ Repair the filesystem without user interaction if it can be done safely. .BI \-r Repair the filesystem interactively. .TP +.BI \-s +Create files in /LOST+FOUND for orphan clusters. These files have clusters allocated but not belonged to any files when reparing the filesystem. clusters unused and contiguous in bitmap are allocated to the same file. +.TP .BI \-v Prints verbose debugging information while checking the exFAT filesystem. .TP @@ -46,6 +73,16 @@ Prints the version number and exits. .TP .B \-y Repair the filesystem answering yes to all questions. +.TP +.B \-b +Try to repair the filesystem even if the exFAT filesystem is not found. + +.SH EXAMPLES +.PP +repair a corrupted device and create files in /LOST+FOUND, which have clusters allocated but not belonged to any files when reparing the device. +.EX +.RB "$" " fsck.exfat -p -s /dev/sda1" + .SH SEE ALSO .BR fsck (8), .BR fstab (5), diff --git a/manpages/mkfs.exfat.8 b/manpages/mkfs.exfat.8 index 9f867d3..76a0065 100644 --- a/manpages/mkfs.exfat.8 +++ b/manpages/mkfs.exfat.8 @@ -17,6 +17,9 @@ mkfs.exfat \- create an exFAT filesystem .B \-L .I volume_label ] [ +.B \-U +.I volume_guid +] [ .B \-\-pack\-bitmap ] [ .B \-v @@ -67,12 +70,23 @@ _ >128 GiB \[<=]512 GiB 256 KiB 32 MiB >512 GiB \[<=]2 TiB 512 KiB 64 MiB .TE +The default is always 1 MiB. .TP .BR \-c ", " \-\-cluster\-size =\fIsize\fR Specifies the cluster size of the exFAT file system. The \fIsize\fR argument is specified in bytes or may be specified with \fBm\fR/\fBM\fR suffix for mebibytes or \fBk\fR/\fBK\fR suffix for kibibytes and must be a power of two. +The default value is described in the following table: +.TS +center; +cb1s6cb,nnn. +Card Capacity Range Cluster Size +_ + \[<=]256 MiB 4 KiB +>256 MiB \[<=]32 GiB 32 KiB +>32 GiB 128 KiB +.TE .TP .BR \-f ", " \-\-full\-format Performs a full format. @@ -84,6 +98,9 @@ Prints the help and exit. .BR \-L ", " \-\-volume\-label =\fIlabel\fR Specifies the volume label to be associated with the exFAT filesystem. .TP +.BR \-U ", " \-\-volume\-guid =\fIguid\fR +Specifies the volume GUID to be associated with the exFAT filesystem. +.TP .B \-\-pack\-bitmap Attempts to relocate the exFAT allocation bitmap so that it ends at the alignment boundary immediately following the FAT rather than beginning at that @@ -101,6 +118,9 @@ allocation unit with the FAT. If there is insufficient space for the bitmap there, then this option will have no effect, and the bitmap will be aligned at the boundary as by default. .TP +.BR \-q ", " \-\-quiet +Prints only error messages while creating the exFAT filesystem. +.TP .BR \-v ", " \-\-verbose Prints verbose debugging information while creating the exFAT filesystem. .TP diff --git a/manpages/tune.exfat.8 b/manpages/tune.exfat.8 index 865dc07..b57c746 100644 --- a/manpages/tune.exfat.8 +++ b/manpages/tune.exfat.8 @@ -10,6 +10,12 @@ tune.exfat \- adjust tunable filesystem parameters on an exFAT filesystem .B \-L .I set-label ] [ +.B \-u +.I print-guid +] [ +.B \-U +.I set-guid +] [ .B \-i .I print-serial ] [ @@ -33,6 +39,12 @@ Print the volume label of the exFAT filesystem. .BI \-L " set-label" Set the volume label of the filesystem to the provided argument. .TP +.BI \-u " print-guid" +Print the volume GUID of the exFAT filesystem. +.TP +.BI \-U " set-guid" +Set the volume GUID of the filesystem to the provided argument. +.TP .BI \-i " print-serial" Print the volume serial of the exFAT filesystem. .TP diff --git a/mkfs/Makefile.in b/mkfs/Makefile.in index f89addc..d43dece 100644 --- a/mkfs/Makefile.in +++ b/mkfs/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.16.1 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2018 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -125,8 +125,7 @@ am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp -am__maybe_remake_depfiles = depfiles -am__depfiles_remade = ./$(DEPDIR)/mkfs.Po ./$(DEPDIR)/upcase.Po +am__depfiles_maybe = depfiles am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -315,8 +314,8 @@ Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) @@ -387,14 +386,8 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mkfs.Po@am__quote@ # am--include-marker -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/upcase.Po@am__quote@ # am--include-marker - -$(am__depfiles_remade): - @$(MKDIR_P) $(@D) - @echo '# dummy' >$@-t && $(am__mv) $@-t $@ - -am--depfiles: $(am__depfiles_remade) +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/mkfs.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/upcase.Po@am__quote@ .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @@ -478,10 +471,7 @@ cscopelist-am: $(am__tagged_files) distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags -distdir: $(BUILT_SOURCES) - $(MAKE) $(AM_MAKEFLAGS) distdir-am - -distdir-am: $(DISTFILES) +distdir: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ @@ -554,8 +544,7 @@ clean-am: clean-generic clean-libtool clean-sbinPROGRAMS \ mostlyclean-am distclean: distclean-am - -rm -f ./$(DEPDIR)/mkfs.Po - -rm -f ./$(DEPDIR)/upcase.Po + -rm -rf ./$(DEPDIR) -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags @@ -601,8 +590,7 @@ install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am - -rm -f ./$(DEPDIR)/mkfs.Po - -rm -f ./$(DEPDIR)/upcase.Po + -rm -rf ./$(DEPDIR) -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic @@ -623,9 +611,9 @@ uninstall-am: uninstall-sbinPROGRAMS .MAKE: install-am install-strip -.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ - clean-generic clean-libtool clean-sbinPROGRAMS cscopelist-am \ - ctags ctags-am distclean distclean-compile distclean-generic \ +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-sbinPROGRAMS cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-data \ install-data-am install-dvi install-dvi-am install-exec \ diff --git a/mkfs/mkfs.c b/mkfs/mkfs.c index 8c81221..773c64e 100644 --- a/mkfs/mkfs.c +++ b/mkfs/mkfs.c @@ -46,14 +46,14 @@ static void exfat_setup_boot_sector(struct pbr *ppbr, struct bsx64 *pbsx = &ppbr->bsx; unsigned int i; - /* Fill exfat BIOS paramemter block */ + /* Fill exfat BIOS parameter block */ pbpb->jmp_boot[0] = 0xeb; pbpb->jmp_boot[1] = 0x76; pbpb->jmp_boot[2] = 0x90; memcpy(pbpb->oem_name, "EXFAT ", 8); memset(pbpb->res_zero, 0, 53); - /* Fill exfat extend BIOS paramemter block */ + /* Fill exfat extend BIOS parameter block */ pbsx->vol_offset = cpu_to_le64(bd->offset / bd->sector_size); pbsx->vol_length = cpu_to_le64(bd->size / bd->sector_size); pbsx->fat_offset = cpu_to_le32(finfo.fat_byte_off / bd->sector_size); @@ -235,9 +235,9 @@ static int write_fat_entry(int fd, __le32 clu, unsigned long long offset) { int nbyte; + off_t fat_entry_offset = finfo.fat_byte_off + (offset * sizeof(__le32)); - lseek(fd, finfo.fat_byte_off + (offset * sizeof(__le32)), SEEK_SET); - nbyte = write(fd, (__u8 *) &clu, sizeof(__le32)); + nbyte = pwrite(fd, (__u8 *) &clu, sizeof(__le32), fat_entry_offset); if (nbyte != sizeof(int)) { exfat_err("write failed, offset : %llu, clu : %x\n", offset, clu); @@ -314,15 +314,15 @@ static int exfat_create_bitmap(struct exfat_blk_dev *bd) char *bitmap; unsigned int i, nbytes; - bitmap = calloc(finfo.bitmap_byte_len, sizeof(*bitmap)); + bitmap = calloc(round_up(finfo.bitmap_byte_len, sizeof(bitmap_t)), + sizeof(*bitmap)); if (!bitmap) return -1; - for (i = 0; i < finfo.used_clu_cnt - EXFAT_FIRST_CLUSTER; i++) - exfat_set_bit(bd, bitmap, i); + for (i = EXFAT_FIRST_CLUSTER; i < finfo.used_clu_cnt; i++) + exfat_bitmap_set(bitmap, i); - lseek(bd->dev_fd, finfo.bitmap_byte_off, SEEK_SET); - nbytes = write(bd->dev_fd, bitmap, finfo.bitmap_byte_len); + nbytes = pwrite(bd->dev_fd, bitmap, finfo.bitmap_byte_len, finfo.bitmap_byte_off); if (nbytes != finfo.bitmap_byte_len) { exfat_err("write failed, nbytes : %d, bitmap_len : %d\n", nbytes, finfo.bitmap_byte_len); @@ -337,8 +337,8 @@ static int exfat_create_bitmap(struct exfat_blk_dev *bd) static int exfat_create_root_dir(struct exfat_blk_dev *bd, struct exfat_user_input *ui) { - struct exfat_dentry ed[3]; - int dentries_len = sizeof(struct exfat_dentry) * 3; + struct exfat_dentry ed[4] = {0}; + int dentries_len = sizeof(ed); int nbytes; /* Set volume label entry */ @@ -347,20 +347,31 @@ static int exfat_create_root_dir(struct exfat_blk_dev *bd, memcpy(ed[0].vol_label, ui->volume_label, ui->volume_label_len); ed[0].vol_char_cnt = ui->volume_label_len/2; + /* Set volume GUID entry */ + if (ui->guid) { + if (__exfat_set_volume_guid(&ed[1], ui->guid)) + return -1; + } else { + /* + * Since a single empty entry cannot be allocated for a + * file, this can reserve the entry for volume GUID. + */ + ed[1].type = EXFAT_GUID & ~EXFAT_INVAL; + } + /* Set bitmap entry */ - ed[1].type = EXFAT_BITMAP; - ed[1].bitmap_flags = 0; - ed[1].bitmap_start_clu = cpu_to_le32(EXFAT_FIRST_CLUSTER); - ed[1].bitmap_size = cpu_to_le64(finfo.bitmap_byte_len); + ed[2].type = EXFAT_BITMAP; + ed[2].bitmap_flags = 0; + ed[2].bitmap_start_clu = cpu_to_le32(EXFAT_FIRST_CLUSTER); + ed[2].bitmap_size = cpu_to_le64(finfo.bitmap_byte_len); /* Set upcase table entry */ - ed[2].type = EXFAT_UPCASE; - ed[2].upcase_checksum = cpu_to_le32(0xe619d30d); - ed[2].upcase_start_clu = cpu_to_le32(finfo.ut_start_clu); - ed[2].upcase_size = cpu_to_le64(EXFAT_UPCASE_TABLE_SIZE); + ed[3].type = EXFAT_UPCASE; + ed[3].upcase_checksum = cpu_to_le32(0xe619d30d); + ed[3].upcase_start_clu = cpu_to_le32(finfo.ut_start_clu); + ed[3].upcase_size = cpu_to_le64(EXFAT_UPCASE_TABLE_SIZE); - lseek(bd->dev_fd, finfo.root_byte_off, SEEK_SET); - nbytes = write(bd->dev_fd, ed, dentries_len); + nbytes = pwrite(bd->dev_fd, ed, dentries_len, finfo.root_byte_off); if (nbytes != dentries_len) { exfat_err("write failed, nbytes : %d, dentries_len : %d\n", nbytes, dentries_len); @@ -374,11 +385,13 @@ static void usage(void) { fputs("Usage: mkfs.exfat\n" "\t-L | --volume-label=label Set volume label\n" + "\t-U | --volume-guid=guid Set volume GUID\n" "\t-c | --cluster-size=size(or suffixed by 'K' or 'M') Specify cluster size\n" "\t-b | --boundary-align=size(or suffixed by 'K' or 'M') Specify boundary alignment\n" "\t --pack-bitmap Move bitmap into FAT segment\n" "\t-f | --full-format Full format\n" "\t-V | --version Show version\n" + "\t-q | --quiet Print only errors\n" "\t-v | --verbose Print debug\n" "\t-h | --help Show help\n", stderr); @@ -390,11 +403,13 @@ static void usage(void) static const struct option opts[] = { {"volume-label", required_argument, NULL, 'L' }, + {"volume-guid", required_argument, NULL, 'U' }, {"cluster-size", required_argument, NULL, 'c' }, {"boundary-align", required_argument, NULL, 'b' }, {"pack-bitmap", no_argument, NULL, PACK_BITMAP }, {"full-format", no_argument, NULL, 'f' }, {"version", no_argument, NULL, 'V' }, + {"quiet", no_argument, NULL, 'q' }, {"verbose", no_argument, NULL, 'v' }, {"help", no_argument, NULL, 'h' }, {"?", no_argument, NULL, '?' }, @@ -440,6 +455,11 @@ static int exfat_build_mkfs_info(struct exfat_blk_dev *bd, unsigned long long total_clu_cnt; int clu_len; + if (ui->cluster_size < bd->sector_size) { + exfat_err("cluster size (%u bytes) is smaller than sector size (%u bytes)\n", + ui->cluster_size, bd->sector_size); + return -1; + } if (ui->boundary_align < bd->sector_size) { exfat_err("boundary alignment is too small (min %d)\n", bd->sector_size); @@ -447,8 +467,12 @@ static int exfat_build_mkfs_info(struct exfat_blk_dev *bd, } finfo.fat_byte_off = round_up(bd->offset + 24 * bd->sector_size, ui->boundary_align) - bd->offset; - finfo.fat_byte_len = round_up((bd->num_clusters * sizeof(int)), - ui->cluster_size); + /* Prevent integer overflow when computing the FAT length */ + if (bd->num_clusters > UINT32_MAX / 4) { + exfat_err("cluster size (%u bytes) is too small\n", ui->cluster_size); + return -1; + } + finfo.fat_byte_len = round_up((bd->num_clusters * 4), ui->cluster_size); finfo.clu_byte_off = round_up(bd->offset + finfo.fat_byte_off + finfo.fat_byte_len, ui->boundary_align) - bd->offset; if (bd->size <= finfo.clu_byte_off) { @@ -597,6 +621,7 @@ int main(int argc, char *argv[]) struct exfat_blk_dev bd; struct exfat_user_input ui; bool version_only = false; + bool quiet = false; init_user_input(&ui); @@ -604,7 +629,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:b:fVvh", opts, NULL)) != EOF) + while ((c = getopt_long(argc, argv, "n:L:U:c:b:fVqvh", opts, NULL)) != EOF) switch (c) { /* * Make 'n' option fallthrough to 'L' option for for backward @@ -621,6 +646,10 @@ int main(int argc, char *argv[]) ui.volume_label_len = ret; break; } + case 'U': + if (*optarg != '\0' && *optarg != '\r') + ui.guid = optarg; + break; case 'c': ret = parse_size(optarg); if (ret < 0) @@ -656,6 +685,10 @@ int main(int argc, char *argv[]) case 'V': version_only = true; break; + case 'q': + print_level = EXFAT_ERROR; + quiet = true; + break; case 'v': print_level = EXFAT_DEBUG; break; @@ -665,9 +698,12 @@ int main(int argc, char *argv[]) usage(); } - show_version(); - if (version_only) + if (version_only) { + show_version(); exit(EXIT_FAILURE); + } else if (!quiet) { + show_version(); + } if (argc - optind != 1) { usage(); @@ -700,6 +736,6 @@ out: if (!ret) exfat_info("\nexFAT format complete!\n"); else - exfat_info("\nexFAT format fail!\n"); + exfat_err("\nexFAT format fail!\n"); return ret; } diff --git a/mkfs/upcase.c b/mkfs/upcase.c index 8d5ef1a..f86fa23 100644 --- a/mkfs/upcase.c +++ b/mkfs/upcase.c @@ -506,8 +506,7 @@ int exfat_create_upcase_table(struct exfat_blk_dev *bd) { int nbytes; - lseek(bd->dev_fd, finfo.ut_byte_off, SEEK_SET); - nbytes = write(bd->dev_fd, upcase_table, EXFAT_UPCASE_TABLE_SIZE); + nbytes = pwrite(bd->dev_fd, upcase_table, EXFAT_UPCASE_TABLE_SIZE, finfo.ut_byte_off); if (nbytes != EXFAT_UPCASE_TABLE_SIZE) return -1; diff --git a/tune/Makefile.in b/tune/Makefile.in index 4bc7ca5..030f811 100644 --- a/tune/Makefile.in +++ b/tune/Makefile.in @@ -1,7 +1,7 @@ -# Makefile.in generated by automake 1.16.1 from Makefile.am. +# Makefile.in generated by automake 1.15.1 from Makefile.am. # @configure_input@ -# Copyright (C) 1994-2018 Free Software Foundation, Inc. +# Copyright (C) 1994-2017 Free Software Foundation, Inc. # This Makefile.in is free software; the Free Software Foundation # gives unlimited permission to copy and/or distribute it, @@ -125,8 +125,7 @@ am__v_at_0 = @ am__v_at_1 = DEFAULT_INCLUDES = -I.@am__isrc@ -I$(top_builddir) depcomp = $(SHELL) $(top_srcdir)/build-aux/depcomp -am__maybe_remake_depfiles = depfiles -am__depfiles_remade = ./$(DEPDIR)/tune.Po +am__depfiles_maybe = depfiles am__mv = mv -f COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) @@ -315,8 +314,8 @@ Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status *config.status*) \ cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ *) \ - echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles)'; \ - cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ esac; $(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) @@ -387,13 +386,7 @@ mostlyclean-compile: distclean-compile: -rm -f *.tab.c -@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tune.Po@am__quote@ # am--include-marker - -$(am__depfiles_remade): - @$(MKDIR_P) $(@D) - @echo '# dummy' >$@-t && $(am__mv) $@-t $@ - -am--depfiles: $(am__depfiles_remade) +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/tune.Po@am__quote@ .c.o: @am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ @@ -477,10 +470,7 @@ cscopelist-am: $(am__tagged_files) distclean-tags: -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags -distdir: $(BUILT_SOURCES) - $(MAKE) $(AM_MAKEFLAGS) distdir-am - -distdir-am: $(DISTFILES) +distdir: $(DISTFILES) @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ list='$(DISTFILES)'; \ @@ -553,7 +543,7 @@ clean-am: clean-generic clean-libtool clean-sbinPROGRAMS \ mostlyclean-am distclean: distclean-am - -rm -f ./$(DEPDIR)/tune.Po + -rm -rf ./$(DEPDIR) -rm -f Makefile distclean-am: clean-am distclean-compile distclean-generic \ distclean-tags @@ -599,7 +589,7 @@ install-ps-am: installcheck-am: maintainer-clean: maintainer-clean-am - -rm -f ./$(DEPDIR)/tune.Po + -rm -rf ./$(DEPDIR) -rm -f Makefile maintainer-clean-am: distclean-am maintainer-clean-generic @@ -620,9 +610,9 @@ uninstall-am: uninstall-sbinPROGRAMS .MAKE: install-am install-strip -.PHONY: CTAGS GTAGS TAGS all all-am am--depfiles check check-am clean \ - clean-generic clean-libtool clean-sbinPROGRAMS cscopelist-am \ - ctags ctags-am distclean distclean-compile distclean-generic \ +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-sbinPROGRAMS cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic \ distclean-libtool distclean-tags distdir dvi dvi-am html \ html-am info info-am install install-am install-data \ install-data-am install-dvi install-dvi-am install-exec \ diff --git a/tune/tune.c b/tune/tune.c index a53be59..f883556 100644 --- a/tune/tune.c +++ b/tune/tune.c @@ -13,12 +13,15 @@ #include "exfat_ondisk.h" #include "libexfat.h" +#include "exfat_fs.h" static void usage(void) { fprintf(stderr, "Usage: tune.exfat\n"); fprintf(stderr, "\t-l | --print-label Print volume label\n"); fprintf(stderr, "\t-L | --set-label=label Set volume label\n"); + fprintf(stderr, "\t-u | --print-guid Print volume GUID\n"); + fprintf(stderr, "\t-U | --set-guid=guid Set volume GUID\n"); fprintf(stderr, "\t-i | --print-serial Print volume serial\n"); fprintf(stderr, "\t-I | --set-serial=value Set volume serial\n"); fprintf(stderr, "\t-V | --version Show version\n"); @@ -31,6 +34,8 @@ static void usage(void) static struct option opts[] = { {"print-label", no_argument, NULL, 'l' }, {"set-label", required_argument, NULL, 'L' }, + {"print-guid", no_argument, NULL, 'u' }, + {"set-guid", required_argument, NULL, 'U' }, {"print-serial", no_argument, NULL, 'i' }, {"set-serial", required_argument, NULL, 'I' }, {"version", no_argument, NULL, 'V' }, @@ -49,7 +54,8 @@ int main(int argc, char *argv[]) bool version_only = false; int flags = 0; char label_input[VOLUME_LABEL_BUFFER_SIZE]; - off_t root_clu_off; + struct exfat *exfat = NULL; + struct pbr *bs; init_user_input(&ui); @@ -57,7 +63,7 @@ int main(int argc, char *argv[]) exfat_err("failed to init locale/codeset\n"); opterr = 0; - while ((c = getopt_long(argc, argv, "I:iL:lVvh", opts, NULL)) != EOF) + while ((c = getopt_long(argc, argv, "I:iL:lU:uVvh", opts, NULL)) != EOF) switch (c) { case 'l': flags = EXFAT_GET_VOLUME_LABEL; @@ -67,6 +73,14 @@ int main(int argc, char *argv[]) optarg); flags = EXFAT_SET_VOLUME_LABEL; break; + case 'u': + flags = EXFAT_GET_VOLUME_GUID; + break; + case 'U': + if (*optarg != '\0' && *optarg != '\r') + ui.guid = optarg; + flags = EXFAT_SET_VOLUME_GUID; + break; case 'i': flags = EXFAT_GET_VOLUME_SERIAL; break; @@ -109,16 +123,44 @@ int main(int argc, char *argv[]) goto close_fd_out; } - root_clu_off = exfat_get_root_entry_offset(&bd); - if (root_clu_off < 0) + ret = read_boot_sect(&bd, &bs); + if (ret) + goto close_fd_out; + + exfat = exfat_alloc_exfat(&bd, bs); + if (!exfat) { + free(bs); + ret = -ENOMEM; + goto close_fd_out; + } + + exfat->root = exfat_alloc_inode(ATTR_SUBDIR); + if (!exfat->root) { + ret = -ENOMEM; + goto close_fd_out; + } + + exfat->root->first_clus = le32_to_cpu(exfat->bs->bsx.root_cluster); + if (exfat_root_clus_count(exfat)) { + exfat_err("failed to follow the cluster chain of root\n"); + exfat_free_inode(exfat->root); + ret = -EINVAL; goto close_fd_out; + } if (flags == EXFAT_GET_VOLUME_LABEL) - ret = exfat_show_volume_label(&bd, root_clu_off); + ret = exfat_read_volume_label(exfat); else if (flags == EXFAT_SET_VOLUME_LABEL) - ret = exfat_set_volume_label(&bd, label_input, root_clu_off); + ret = exfat_set_volume_label(exfat, label_input); + else if (flags == EXFAT_GET_VOLUME_GUID) + ret = exfat_read_volume_guid(exfat); + else if (flags == EXFAT_SET_VOLUME_GUID) + ret = exfat_set_volume_guid(exfat, ui.guid); + close_fd_out: close(bd.dev_fd); + if (exfat) + exfat_free_exfat(exfat); out: return ret; }