summaryrefslogtreecommitdiffstats
path: root/fsck
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:16:44 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-15 20:16:44 +0000
commit367f23a94b60c40f76716e2e50566b686baf36b8 (patch)
tree3b6280f7c05bee0df7f9109958a44bfbc54d7639 /fsck
parentInitial commit. (diff)
downloadexfatprogs-367f23a94b60c40f76716e2e50566b686baf36b8.tar.xz
exfatprogs-367f23a94b60c40f76716e2e50566b686baf36b8.zip
Adding upstream version 1.2.2.upstream/1.2.2upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'fsck')
-rw-r--r--fsck/Android.bp12
-rw-r--r--fsck/Makefile.am6
-rw-r--r--fsck/Makefile.in633
-rw-r--r--fsck/fsck.c1616
-rw-r--r--fsck/fsck.h37
-rw-r--r--fsck/repair.c295
-rw-r--r--fsck/repair.h38
7 files changed, 2637 insertions, 0 deletions
diff --git a/fsck/Android.bp b/fsck/Android.bp
new file mode 100644
index 0000000..7473fea
--- /dev/null
+++ b/fsck/Android.bp
@@ -0,0 +1,12 @@
+// Copyright 2020 The Android Open Source Project
+
+cc_binary {
+ name: "fsck.exfat",
+
+ srcs: [
+ "fsck.c",
+ "repair.c",
+ ],
+ defaults: ["exfatprogs-defaults"],
+ static_libs: ["libexfat"],
+}
diff --git a/fsck/Makefile.am b/fsck/Makefile.am
new file mode 100644
index 0000000..519b13a
--- /dev/null
+++ b/fsck/Makefile.am
@@ -0,0 +1,6 @@
+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 repair.h
diff --git a/fsck/Makefile.in b/fsck/Makefile.in
new file mode 100644
index 0000000..56fa169
--- /dev/null
+++ b/fsck/Makefile.in
@@ -0,0 +1,633 @@
+# 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 = fsck.exfat$(EXEEXT)
+subdir = fsck
+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_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@)
+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 = $(fsck_exfat_SOURCES)
+DIST_SOURCES = $(fsck_exfat_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
+fsck_exfat_LDADD = $(top_builddir)/lib/libexfat.a
+fsck_exfat_SOURCES = fsck.c repair.c fsck.h repair.h
+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 fsck/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign fsck/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
+
+fsck.exfat$(EXEEXT): $(fsck_exfat_OBJECTS) $(fsck_exfat_DEPENDENCIES) $(EXTRA_fsck_exfat_DEPENDENCIES)
+ @rm -f fsck.exfat$(EXEEXT)
+ $(AM_V_CCLD)$(LINK) $(fsck_exfat_OBJECTS) $(fsck_exfat_LDADD) $(LIBS)
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@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$$||'`;\
+@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/fsck/fsck.c b/fsck/fsck.c
new file mode 100644
index 0000000..77272aa
--- /dev/null
+++ b/fsck/fsck.c
@@ -0,0 +1,1616 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2019 Namjae Jeon <linkinjeon@kernel.org>
+ * Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <getopt.h>
+#include <inttypes.h>
+#include <string.h>
+#include <errno.h>
+#include <locale.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+#include "repair.h"
+#include "exfat_fs.h"
+#include "exfat_dir.h"
+#include "fsck.h"
+
+struct fsck_user_input {
+ struct exfat_user_input ei;
+ enum fsck_ui_options options;
+};
+
+#define EXFAT_MAX_UPCASE_CHARS 0x10000
+
+#define FSCK_EXIT_NO_ERRORS 0x00
+#define FSCK_EXIT_CORRECTED 0x01
+#define FSCK_EXIT_NEED_REBOOT 0x02
+#define FSCK_EXIT_ERRORS_LEFT 0x04
+#define FSCK_EXIT_OPERATION_ERROR 0x08
+#define FSCK_EXIT_SYNTAX_ERROR 0x10
+#define FSCK_EXIT_USER_CANCEL 0x20
+#define FSCK_EXIT_LIBRARY_ERROR 0x80
+
+struct exfat_stat {
+ long dir_count;
+ long file_count;
+ long error_count;
+ long fixed_count;
+};
+
+struct exfat_fsck exfat_fsck;
+struct exfat_stat exfat_stat;
+struct path_resolve_ctx path_resolve_ctx;
+
+static struct option opts[] = {
+ {"repair", no_argument, NULL, 'r' },
+ {"repair-yes", no_argument, NULL, 'y' },
+ {"repair-no", no_argument, NULL, 'n' },
+ {"repair-auto", no_argument, NULL, 'p' },
+ {"rescue", no_argument, NULL, 's' },
+ {"version", no_argument, NULL, 'V' },
+ {"verbose", no_argument, NULL, 'v' },
+ {"help", no_argument, NULL, 'h' },
+ {"?", no_argument, NULL, '?' },
+ {"ignore-bad-fs", no_argument, NULL, 'b' },
+ {NULL, 0, NULL, 0 }
+};
+
+static void usage(char *name)
+{
+ fprintf(stderr, "Usage: %s\n", name);
+ fprintf(stderr, "\t-r | --repair Repair interactively\n");
+ fprintf(stderr, "\t-y | --repair-yes Repair without ask\n");
+ fprintf(stderr, "\t-n | --repair-no No repair\n");
+ fprintf(stderr, "\t-p | --repair-auto Repair automatically\n");
+ fprintf(stderr, "\t-a Repair automatically\n");
+ fprintf(stderr, "\t-b | --ignore-bad-fs Try to recover even if exfat is not found\n");
+ fprintf(stderr, "\t-s | --rescue Assign orphaned clusters to files\n");
+ fprintf(stderr, "\t-V | --version Show version\n");
+ fprintf(stderr, "\t-v | --verbose Print debug\n");
+ fprintf(stderr, "\t-h | --help Show help\n");
+
+ exit(FSCK_EXIT_SYNTAX_ERROR);
+}
+
+#define fsck_err(parent, inode, fmt, ...) \
+({ \
+ exfat_resolve_path_parent(&path_resolve_ctx, \
+ parent, inode); \
+ exfat_err("ERROR: %s: " fmt, \
+ path_resolve_ctx.local_path, \
+ ##__VA_ARGS__); \
+})
+
+#define repair_file_ask(iter, inode, code, fmt, ...) \
+({ \
+ if (inode) \
+ exfat_resolve_path_parent(&path_resolve_ctx, \
+ (iter)->parent, inode); \
+ else \
+ exfat_resolve_path(&path_resolve_ctx, \
+ (iter)->parent); \
+ exfat_repair_ask(&exfat_fsck, code, \
+ "ERROR: %s: " fmt " at %#" PRIx64, \
+ path_resolve_ctx.local_path, \
+ ##__VA_ARGS__, \
+ exfat_de_iter_device_offset(iter)); \
+})
+
+static int check_clus_chain(struct exfat_de_iter *de_iter,
+ struct exfat_inode *node)
+{
+ struct exfat *exfat = de_iter->exfat;
+ struct exfat_dentry *stream_de;
+ clus_t clus, prev, next;
+ uint64_t count, max_count;
+
+ clus = node->first_clus;
+ prev = EXFAT_EOF_CLUSTER;
+ count = 0;
+ max_count = DIV_ROUND_UP(node->size, exfat->clus_size);
+
+ if (node->size == 0 && node->first_clus == EXFAT_FREE_CLUSTER)
+ return 0;
+
+ /* the first cluster is wrong */
+ if ((node->size == 0 && node->first_clus != EXFAT_FREE_CLUSTER) ||
+ (node->size > 0 && !exfat_heap_clus(exfat, node->first_clus))) {
+ if (repair_file_ask(de_iter, node,
+ ER_FILE_FIRST_CLUS,
+ "size %#" PRIx64 ", but the first cluster %#x",
+ node->size, node->first_clus))
+ goto truncate_file;
+ else
+ return -EINVAL;
+ }
+
+ while (clus != EXFAT_EOF_CLUSTER) {
+ if (count >= max_count) {
+ if (node->is_contiguous)
+ break;
+ if (repair_file_ask(de_iter, node,
+ ER_FILE_SMALLER_SIZE,
+ "more clusters are allocated. truncate to %"
+ PRIu64 " bytes",
+ count * exfat->clus_size))
+ goto truncate_file;
+ else
+ return -EINVAL;
+ }
+
+ /*
+ * This cluster is already allocated. it may be shared with
+ * the other file, or there is a loop in cluster chain.
+ */
+ if (exfat_bitmap_get(exfat->alloc_bitmap, clus)) {
+ if (repair_file_ask(de_iter, node,
+ ER_FILE_DUPLICATED_CLUS,
+ "cluster is already allocated for the other file. truncated to %"
+ PRIu64 " bytes",
+ count * exfat->clus_size))
+ goto truncate_file;
+ else
+ return -EINVAL;
+ }
+
+ if (!exfat_bitmap_get(exfat->disk_bitmap, clus)) {
+ if (!repair_file_ask(de_iter, node,
+ ER_FILE_INVALID_CLUS,
+ "cluster %#x is marked as free",
+ clus))
+ return -EINVAL;
+ }
+
+ /* This cluster is allocated or not */
+ if (exfat_get_inode_next_clus(exfat, node, clus, &next))
+ goto truncate_file;
+ if (next == EXFAT_BAD_CLUSTER) {
+ if (repair_file_ask(de_iter, node,
+ ER_FILE_INVALID_CLUS,
+ "BAD cluster. truncate to %"
+ PRIu64 " bytes",
+ count * exfat->clus_size))
+ goto truncate_file;
+ else
+ return -EINVAL;
+ } else if (!node->is_contiguous) {
+ if (next != EXFAT_EOF_CLUSTER &&
+ !exfat_heap_clus(exfat, next)) {
+ if (repair_file_ask(de_iter, node,
+ ER_FILE_INVALID_CLUS,
+ "broken cluster chain. truncate to %"
+ PRIu64 " bytes",
+ (count + 1) * exfat->clus_size)) {
+ count++;
+ prev = clus;
+ exfat_bitmap_set(exfat->alloc_bitmap,
+ clus);
+ goto truncate_file;
+ } else {
+ return -EINVAL;
+ }
+ }
+ }
+
+ count++;
+ exfat_bitmap_set(exfat->alloc_bitmap, clus);
+ prev = clus;
+ clus = next;
+ }
+
+ if (count < max_count) {
+ if (repair_file_ask(de_iter, node, ER_FILE_LARGER_SIZE,
+ "less clusters are allocated. truncates to %"
+ PRIu64 " bytes",
+ count * exfat->clus_size))
+ goto truncate_file;
+ else
+ return -EINVAL;
+ }
+
+ return 0;
+truncate_file:
+ node->size = count * exfat->clus_size;
+ if (!exfat_heap_clus(exfat, prev))
+ node->first_clus = EXFAT_FREE_CLUSTER;
+
+ exfat_de_iter_get_dirty(de_iter, 1, &stream_de);
+ if (count * exfat->clus_size <
+ le64_to_cpu(stream_de->stream_valid_size))
+ stream_de->stream_valid_size = cpu_to_le64(
+ count * exfat->clus_size);
+ if (!exfat_heap_clus(exfat, prev))
+ stream_de->stream_start_clu = EXFAT_FREE_CLUSTER;
+ stream_de->stream_size = cpu_to_le64(
+ count * exfat->clus_size);
+
+ /* remaining clusters will be freed while FAT is compared with
+ * alloc_bitmap.
+ */
+ if (!node->is_contiguous && exfat_heap_clus(exfat, prev)) {
+ if (exfat_set_fat(exfat, prev, EXFAT_EOF_CLUSTER))
+ return -EIO;
+ }
+ return 1;
+}
+
+static int root_check_clus_chain(struct exfat *exfat,
+ struct exfat_inode *node,
+ clus_t *clus_count)
+{
+ 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 (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;
+ }
+
+ 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");
+ goto out_trunc;
+ }
+
+ 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 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,
+ int bs_offset, unsigned int sect_size)
+{
+ void *sect;
+ unsigned int i;
+ uint32_t checksum;
+ int ret = 0;
+
+ sect = malloc(sect_size);
+ if (!sect)
+ return -ENOMEM;
+
+ checksum = 0;
+ for (i = 0; i < 11; i++) {
+ if (exfat_read(dev_fd, sect, sect_size,
+ bs_offset * sect_size + i * sect_size) !=
+ (ssize_t)sect_size) {
+ exfat_err("failed to read boot region\n");
+ ret = -EIO;
+ goto out;
+ }
+ boot_calc_checksum(sect, sect_size, i == 0, &checksum);
+ }
+
+ if (exfat_read(dev_fd, sect, sect_size,
+ bs_offset * sect_size + 11 * sect_size) !=
+ (ssize_t)sect_size) {
+ exfat_err("failed to read a boot checksum sector\n");
+ ret = -EIO;
+ goto out;
+ }
+
+ for (i = 0; i < sect_size/sizeof(checksum); i++) {
+ if (le32_to_cpu(((__le32 *)sect)[i]) != checksum) {
+ exfat_err("checksum of boot region is not correct. %#x, but expected %#x\n",
+ le32_to_cpu(((__le32 *)sect)[i]), checksum);
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+out:
+ free(sect);
+ return ret;
+}
+
+static int exfat_mark_volume_dirty(struct exfat *exfat, bool dirty)
+{
+ uint16_t flags;
+
+ flags = le16_to_cpu(exfat->bs->bsx.vol_flags);
+ if (dirty)
+ flags |= 0x02;
+ else
+ flags &= ~0x02;
+
+ exfat->bs->bsx.vol_flags = cpu_to_le16(flags);
+ if (exfat_write(exfat->blk_dev->dev_fd, exfat->bs,
+ sizeof(struct pbr), 0) != (ssize_t)sizeof(struct pbr)) {
+ exfat_err("failed to set VolumeDirty\n");
+ return -EIO;
+ }
+
+ if (fsync(exfat->blk_dev->dev_fd) != 0) {
+ exfat_err("failed to set VolumeDirty\n");
+ return -EIO;
+ }
+ return 0;
+}
+
+static int read_boot_region(struct exfat_blk_dev *bd, struct pbr **pbr,
+ int bs_offset, unsigned int sect_size,
+ bool verbose)
+{
+ struct pbr *bs;
+ int ret = -EINVAL;
+
+ *pbr = NULL;
+ bs = (struct pbr *)malloc(sizeof(struct pbr));
+ if (!bs) {
+ exfat_err("failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ if (exfat_read(bd->dev_fd, bs, sizeof(*bs),
+ bs_offset * sect_size) != (ssize_t)sizeof(*bs)) {
+ exfat_err("failed to read a boot sector\n");
+ ret = -EIO;
+ goto err;
+ }
+
+ if (memcmp(bs->bpb.oem_name, "EXFAT ", 8) != 0) {
+ if (verbose)
+ exfat_err("failed to find exfat file system\n");
+ goto err;
+ }
+
+ ret = boot_region_checksum(bd->dev_fd, bs_offset, sect_size);
+ if (ret < 0)
+ goto err;
+
+ ret = -EINVAL;
+ if (EXFAT_SECTOR_SIZE(bs) < 512 || EXFAT_SECTOR_SIZE(bs) > 4 * KB) {
+ if (verbose)
+ exfat_err("too small or big sector size: %d\n",
+ EXFAT_SECTOR_SIZE(bs));
+ goto err;
+ }
+
+ if (EXFAT_CLUSTER_SIZE(bs) > 32 * MB) {
+ if (verbose)
+ exfat_err("too big cluster size: %d\n",
+ EXFAT_CLUSTER_SIZE(bs));
+ goto err;
+ }
+
+ if (bs->bsx.fs_version[1] != 1 || bs->bsx.fs_version[0] != 0) {
+ if (verbose)
+ exfat_err("unsupported exfat version: %d.%d\n",
+ bs->bsx.fs_version[1], bs->bsx.fs_version[0]);
+ goto err;
+ }
+
+ if (bs->bsx.num_fats != 1) {
+ if (verbose)
+ exfat_err("unsupported FAT count: %d\n",
+ bs->bsx.num_fats);
+ goto err;
+ }
+
+ if (le64_to_cpu(bs->bsx.vol_length) * EXFAT_SECTOR_SIZE(bs) >
+ bd->size) {
+ if (verbose)
+ exfat_err("too large sector count: %" PRIu64 ", expected: %llu\n",
+ le64_to_cpu(bs->bsx.vol_length),
+ bd->num_sectors);
+ goto err;
+ }
+
+ if (le32_to_cpu(bs->bsx.clu_count) * EXFAT_CLUSTER_SIZE(bs) >
+ bd->size) {
+ if (verbose)
+ exfat_err("too large cluster count: %u, expected: %u\n",
+ le32_to_cpu(bs->bsx.clu_count),
+ bd->num_clusters);
+ goto err;
+ }
+
+ *pbr = bs;
+ return 0;
+err:
+ free(bs);
+ return ret;
+}
+
+static int restore_boot_region(struct exfat_blk_dev *bd, unsigned int sect_size)
+{
+ int i;
+ char *sector;
+ int ret;
+
+ sector = malloc(sect_size);
+ if (!sector)
+ return -ENOMEM;
+
+ for (i = 0; i < 12; i++) {
+ if (exfat_read(bd->dev_fd, sector, sect_size,
+ BACKUP_BOOT_SEC_IDX * sect_size +
+ i * sect_size) !=
+ (ssize_t)sect_size) {
+ ret = -EIO;
+ goto free_sector;
+ }
+ if (i == 0)
+ ((struct pbr *)sector)->bsx.perc_in_use = 0xff;
+
+ if (exfat_write(bd->dev_fd, sector, sect_size,
+ BOOT_SEC_IDX * sect_size +
+ i * sect_size) !=
+ (ssize_t)sect_size) {
+ ret = -EIO;
+ goto free_sector;
+ }
+ }
+
+ if (fsync(bd->dev_fd)) {
+ ret = -EIO;
+ goto free_sector;
+ }
+ ret = 0;
+
+free_sector:
+ free(sector);
+ return ret;
+}
+
+static int exfat_boot_region_check(struct exfat_blk_dev *blkdev,
+ struct pbr **bs,
+ bool ignore_bad_fs_name)
+{
+ struct pbr *boot_sect;
+ unsigned int sect_size;
+ int ret;
+
+ /* First, find out the exfat sector size */
+ boot_sect = malloc(sizeof(*boot_sect));
+ if (boot_sect == NULL)
+ return -ENOMEM;
+
+ if (exfat_read(blkdev->dev_fd, boot_sect,
+ sizeof(*boot_sect), 0) != (ssize_t)sizeof(*boot_sect)) {
+ exfat_err("failed to read Main boot sector\n");
+ free(boot_sect);
+ return -EIO;
+ }
+
+ if (memcmp(boot_sect->bpb.oem_name, "EXFAT ", 8) != 0 &&
+ !ignore_bad_fs_name) {
+ exfat_err("Bad fs_name in boot sector, which does not describe a valid exfat filesystem\n");
+ free(boot_sect);
+ return -ENOTSUP;
+ }
+
+ sect_size = 1 << boot_sect->bsx.sect_size_bits;
+ free(boot_sect);
+
+ /* check boot regions */
+ ret = read_boot_region(blkdev, bs,
+ BOOT_SEC_IDX, sect_size, true);
+ if (ret == -EINVAL &&
+ exfat_repair_ask(&exfat_fsck, ER_BS_BOOT_REGION,
+ "boot region is corrupted. try to restore the region from backup"
+ )) {
+ const unsigned int sector_sizes[] = {512, 4096, 1024, 2048};
+ unsigned int i;
+
+ if (sect_size >= 512 && sect_size <= EXFAT_MAX_SECTOR_SIZE) {
+ ret = read_boot_region(blkdev, bs,
+ BACKUP_BOOT_SEC_IDX, sect_size,
+ false);
+ if (!ret)
+ goto restore;
+ }
+
+ for (i = 0; i < sizeof(sector_sizes)/sizeof(sector_sizes[0]); i++) {
+ if (sector_sizes[i] == sect_size)
+ continue;
+
+ ret = read_boot_region(blkdev, bs,
+ BACKUP_BOOT_SEC_IDX,
+ sector_sizes[i], false);
+ if (!ret) {
+ sect_size = sector_sizes[i];
+ goto restore;
+ }
+ }
+ exfat_err("backup boot region is also corrupted\n");
+ }
+
+ return ret;
+restore:
+ ret = restore_boot_region(blkdev, sect_size);
+ if (ret) {
+ exfat_err("failed to restore boot region from backup\n");
+ free(*bs);
+ *bs = NULL;
+ }
+ return ret;
+}
+
+static uint16_t file_calc_checksum(struct exfat_de_iter *iter)
+{
+ uint16_t checksum;
+ struct exfat_dentry *file_de, *de;
+ int i;
+
+ checksum = 0;
+ exfat_de_iter_get(iter, 0, &file_de);
+
+ exfat_calc_dentry_checksum(file_de, &checksum, true);
+ for (i = 1; i <= file_de->file_num_ext; i++) {
+ exfat_de_iter_get(iter, i, &de);
+ exfat_calc_dentry_checksum(de, &checksum, false);
+ }
+ return checksum;
+}
+
+/*
+ * return 0 if there are no errors, or 1 if errors are fixed, or
+ * an error code
+ */
+static int check_inode(struct exfat_de_iter *iter, struct exfat_inode *node)
+{
+ struct exfat *exfat = iter->exfat;
+ struct exfat_dentry *dentry;
+ int ret = 0;
+ uint16_t checksum;
+ bool valid = true;
+
+ ret = check_clus_chain(iter, node);
+ if (ret < 0)
+ return ret;
+
+ if (node->size > le32_to_cpu(exfat->bs->bsx.clu_count) *
+ (uint64_t)exfat->clus_size) {
+ fsck_err(iter->parent, node,
+ "size %" PRIu64 " is greater than cluster heap\n",
+ node->size);
+ valid = false;
+ }
+
+ if (node->size == 0 && node->is_contiguous) {
+ if (repair_file_ask(iter, node, ER_FILE_ZERO_NOFAT,
+ "empty, but has no Fat chain")) {
+ exfat_de_iter_get_dirty(iter, 1, &dentry);
+ dentry->stream_flags &= ~EXFAT_SF_CONTIGUOUS;
+ ret = 1;
+ } else
+ valid = false;
+ }
+
+ if ((node->attr & ATTR_SUBDIR) &&
+ node->size % exfat->clus_size != 0) {
+ fsck_err(iter->parent, node,
+ "directory size %" PRIu64 " is not divisible by %d\n",
+ node->size, exfat->clus_size);
+ valid = false;
+ }
+
+ checksum = file_calc_checksum(iter);
+ exfat_de_iter_get(iter, 0, &dentry);
+ if (checksum != le16_to_cpu(dentry->file_checksum)) {
+ exfat_de_iter_get_dirty(iter, 0, &dentry);
+ dentry->file_checksum = cpu_to_le16(checksum);
+ ret = 1;
+ }
+
+ return valid ? ret : -EINVAL;
+}
+
+static int handle_duplicated_filename(struct exfat_de_iter *iter,
+ struct exfat_inode *inode)
+{
+ 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;
+
+ 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\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) {
+ 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 = 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) {
+ 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, dentry->name_unicode,
+ sizeof(dentry->name_unicode));
+ }
+
+ ret = check_name_dentry_set(iter, node);
+ if (ret) {
+ *skip_dentries = file_de->file_num_ext + 1;
+ goto skip_dset;
+ }
+
+ if (file_de->file_num_ext == 2 && stream_de->stream_name_len <= 2) {
+ ret = handle_dot_dotdot_filename(iter, dentry,
+ stream_de->stream_name_len);
+ if (ret < 0) {
+ *skip_dentries = file_de->file_num_ext + 1;
+ goto skip_dset;
+ }
+ }
+
+ node->first_clus = le32_to_cpu(stream_de->stream_start_clu);
+ node->is_contiguous =
+ ((stream_de->stream_flags & EXFAT_SF_CONTIGUOUS) != 0);
+ node->size = le64_to_cpu(stream_de->stream_size);
+
+ if (node->size < le64_to_cpu(stream_de->stream_valid_size)) {
+ *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)) {
+ exfat_de_iter_get_dirty(iter, 1, &stream_de);
+ stream_de->stream_valid_size =
+ stream_de->stream_size;
+ } else {
+ *skip_dentries = file_de->file_num_ext + 1;
+ goto skip_dset;
+ }
+ }
+
+ *skip_dentries = (file_de->file_num_ext + 1);
+ *new_node = node;
+ return 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;
+ exfat_free_inode(node);
+ return need_delete ? 1 : -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;
+
+ ret = check_inode(de_iter, node);
+ if (ret < 0) {
+ exfat_free_inode(node);
+ return -EINVAL;
+ }
+
+ if (node->attr & ATTR_SUBDIR)
+ exfat_stat.dir_count++;
+ else
+ exfat_stat.file_count++;
+ *new_node = node;
+ return ret;
+}
+
+static int read_bitmap(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;
+ int retval;
+
+ retval = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
+ if (retval)
+ return retval;
+
+ dentry = filter.out.dentry_set;
+ exfat_debug("start cluster %#x, size %#" PRIx64 "\n",
+ le32_to_cpu(dentry->bitmap_start_clu),
+ le64_to_cpu(dentry->bitmap_size));
+
+ if (le64_to_cpu(dentry->bitmap_size) <
+ DIV_ROUND_UP(exfat->clus_count, 8)) {
+ exfat_err("invalid size of allocation bitmap. 0x%" PRIx64 "\n",
+ le64_to_cpu(dentry->bitmap_size));
+ return -EINVAL;
+ }
+ if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->bitmap_start_clu))) {
+ exfat_err("invalid start cluster of allocate bitmap. 0x%x\n",
+ le32_to_cpu(dentry->bitmap_start_clu));
+ return -EINVAL;
+ }
+
+ exfat->disk_bitmap_clus = le32_to_cpu(dentry->bitmap_start_clu);
+ exfat->disk_bitmap_size = DIV_ROUND_UP(exfat->clus_count, 8);
+
+ exfat_bitmap_set_range(exfat, exfat->alloc_bitmap,
+ le64_to_cpu(dentry->bitmap_start_clu),
+ DIV_ROUND_UP(exfat->disk_bitmap_size,
+ exfat->clus_size));
+ free(filter.out.dentry_set);
+
+ if (exfat_read(exfat->blk_dev->dev_fd, exfat->disk_bitmap,
+ exfat->disk_bitmap_size,
+ exfat_c2o(exfat, exfat->disk_bitmap_clus)) !=
+ (ssize_t)exfat->disk_bitmap_size)
+ return -EIO;
+ return 0;
+}
+
+static int decompress_upcase_table(const __le16 *in_table, size_t in_len,
+ __u16 *out_table, size_t out_len)
+{
+ size_t i, k;
+ uint16_t ch;
+
+ if (in_len > out_len)
+ return -E2BIG;
+
+ for (k = 0; k < out_len; k++)
+ out_table[k] = k;
+
+ for (i = 0, k = 0; i < in_len && k < out_len; i++) {
+ ch = le16_to_cpu(in_table[i]);
+
+ if (ch == 0xFFFF && i + 1 < in_len) {
+ uint16_t len = le16_to_cpu(in_table[++i]);
+
+ k += len;
+ } else {
+ out_table[k++] = ch;
+ }
+ }
+ return 0;
+}
+
+static int read_upcase_table(struct exfat *exfat)
+{
+ struct exfat_lookup_filter filter = {
+ .in.type = EXFAT_UPCASE,
+ .in.dentry_count = 0,
+ .in.filter = NULL,
+ .in.param = NULL,
+ };
+ struct exfat_dentry *dentry = NULL;
+ __le16 *upcase = NULL;
+ int retval;
+ ssize_t size;
+ __le32 checksum;
+
+ retval = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
+ if (retval)
+ return retval;
+
+ dentry = filter.out.dentry_set;
+
+ if (!exfat_heap_clus(exfat, le32_to_cpu(dentry->upcase_start_clu))) {
+ exfat_err("invalid start cluster of upcase table. 0x%x\n",
+ le32_to_cpu(dentry->upcase_start_clu));
+ retval = -EINVAL;
+ goto out;
+ }
+
+ size = (ssize_t)le64_to_cpu(dentry->upcase_size);
+ if (size > (ssize_t)(EXFAT_MAX_UPCASE_CHARS * sizeof(__le16)) ||
+ size == 0 || size % sizeof(__le16)) {
+ exfat_err("invalid size of upcase table. 0x%" PRIx64 "\n",
+ le64_to_cpu(dentry->upcase_size));
+ retval = -EINVAL;
+ goto out;
+ }
+
+ upcase = (__le16 *)malloc(size);
+ if (!upcase) {
+ exfat_err("failed to allocate upcase table\n");
+ retval = -ENOMEM;
+ goto out;
+ }
+
+ if (exfat_read(exfat->blk_dev->dev_fd, upcase, size,
+ exfat_c2o(exfat,
+ le32_to_cpu(dentry->upcase_start_clu))) != size) {
+ exfat_err("failed to read upcase table\n");
+ retval = -EIO;
+ goto out;
+ }
+
+ checksum = 0;
+ boot_calc_checksum((unsigned char *)upcase, size, false, &checksum);
+ if (le32_to_cpu(dentry->upcase_checksum) != checksum) {
+ exfat_err("corrupted upcase table %#x (expected: %#x)\n",
+ checksum, le32_to_cpu(dentry->upcase_checksum));
+ retval = -EINVAL;
+ goto out;
+ }
+
+ exfat_bitmap_set_range(exfat, exfat->alloc_bitmap,
+ le32_to_cpu(dentry->upcase_start_clu),
+ DIV_ROUND_UP(le64_to_cpu(dentry->upcase_size),
+ exfat->clus_size));
+
+ exfat->upcase_table = calloc(1,
+ sizeof(uint16_t) * EXFAT_UPCASE_TABLE_CHARS);
+ if (!exfat->upcase_table) {
+ retval = -EIO;
+ goto out;
+ }
+
+ decompress_upcase_table(upcase, size / 2,
+ exfat->upcase_table, EXFAT_UPCASE_TABLE_CHARS);
+out:
+ if (dentry)
+ free(dentry);
+ if (upcase)
+ free(upcase);
+ return retval;
+}
+
+static int read_children(struct exfat_fsck *fsck, struct exfat_inode *dir)
+{
+ struct exfat *exfat = fsck->exfat;
+ struct exfat_inode *node = NULL;
+ struct exfat_dentry *dentry;
+ struct exfat_de_iter *de_iter;
+ int dentry_count;
+ int ret;
+
+ de_iter = &fsck->de_iter;
+ ret = exfat_de_iter_init(de_iter, exfat, dir, fsck->buffer_desc);
+ if (ret == EOF)
+ return 0;
+ else if (ret)
+ return ret;
+
+ 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) {
+ break;
+ } else if (ret) {
+ fsck_err(dir->parent, dir,
+ "failed to get a dentry. %d\n", ret);
+ goto err;
+ }
+
+ dentry_count = 1;
+
+ switch (dentry->type) {
+ case EXFAT_FILE:
+ ret = read_file(de_iter, &node, &dentry_count);
+ if (ret < 0) {
+ exfat_stat.error_count++;
+ break;
+ } else if (ret) {
+ exfat_stat.error_count++;
+ exfat_stat.fixed_count++;
+ }
+
+ if (node) {
+ 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;
+ if (repair_file_ask(de_iter, NULL, ER_DE_UNKNOWN,
+ "unknown entry type %#x at %07" PRIx64,
+ dentry->type,
+ exfat_de_iter_file_offset(de_iter))) {
+ struct exfat_dentry *dentry;
+
+ exfat_de_iter_get_dirty(de_iter, 0, &dentry);
+ dentry->type &= EXFAT_DELETE;
+ }
+ break;
+ }
+
+ exfat_de_iter_advance(de_iter, dentry_count);
+ }
+out:
+ exfat_de_iter_flush(de_iter);
+ return 0;
+err:
+ exfat_free_children(dir, false);
+ INIT_LIST_HEAD(&dir->children);
+ exfat_de_iter_flush(de_iter);
+ return ret;
+}
+
+/* write bitmap segments for clusters which are marked
+ * as free, but allocated to files.
+ */
+static int write_bitmap(struct exfat_fsck *fsck)
+{
+ struct exfat *exfat = fsck->exfat;
+ bitmap_t *disk_b, *alloc_b, *ohead_b;
+ off_t dev_offset;
+ unsigned int i, bitmap_bytes, byte_offset, write_bytes;
+
+ dev_offset = exfat_c2o(exfat, exfat->disk_bitmap_clus);
+ bitmap_bytes = EXFAT_BITMAP_SIZE(le32_to_cpu(exfat->bs->bsx.clu_count));
+
+ disk_b = (bitmap_t *)exfat->disk_bitmap;
+ alloc_b = (bitmap_t *)exfat->alloc_bitmap;
+ ohead_b = (bitmap_t *)exfat->ohead_bitmap;
+
+ for (i = 0; i < bitmap_bytes / sizeof(bitmap_t); i++)
+ ohead_b[i] = alloc_b[i] | disk_b[i];
+
+ i = 0;
+ while (i < bitmap_bytes / sizeof(bitmap_t)) {
+ if (ohead_b[i] == disk_b[i]) {
+ i++;
+ continue;
+ }
+
+ byte_offset = ((i * sizeof(bitmap_t)) / 512) * 512;
+ write_bytes = MIN(512, bitmap_bytes - byte_offset);
+
+ if (exfat_write(exfat->blk_dev->dev_fd,
+ (char *)ohead_b + byte_offset, write_bytes,
+ dev_offset + byte_offset) != (ssize_t)write_bytes)
+ return -EIO;
+
+ i = (byte_offset + write_bytes) / sizeof(bitmap_t);
+ }
+ return 0;
+
+}
+
+/*
+ * for each directory in @dir_list.
+ * 1. read all dentries and allocate exfat_nodes for files and directories.
+ * and append directory exfat_nodes to the head of @dir_list
+ * 2. free all of file exfat_nodes.
+ * 3. if the directory does not have children, free its exfat_node.
+ */
+static int exfat_filesystem_check(struct exfat_fsck *fsck)
+{
+ struct exfat *exfat = fsck->exfat;
+ struct exfat_inode *dir;
+ int ret = 0, dir_errors;
+
+ if (!exfat->root) {
+ exfat_err("root is NULL\n");
+ return -ENOENT;
+ }
+
+ 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);
+
+ if (!(dir->attr & ATTR_SUBDIR)) {
+ fsck_err(dir->parent, dir,
+ "failed to travel directories. "
+ "the node is not directory\n");
+ ret = -EINVAL;
+ goto out;
+ }
+
+ dir_errors = read_children(fsck, dir);
+ if (dir_errors) {
+ exfat_resolve_path(&path_resolve_ctx, dir);
+ exfat_debug("failed to check dentries: %s\n",
+ path_resolve_ctx.local_path);
+ ret = dir_errors;
+ }
+
+ list_del(&dir->list);
+ exfat_free_file_children(dir);
+ exfat_free_ancestors(dir);
+ }
+out:
+ exfat_free_dir_list(exfat);
+ free(fsck->name_hash_bitmap);
+ return ret;
+}
+
+static int exfat_root_dir_check(struct exfat *exfat)
+{
+ struct exfat_inode *root;
+ clus_t clus_count = 0;
+ int err;
+
+ 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_check_clus_chain(exfat, root, &clus_count)) {
+ exfat_err("failed to follow the cluster chain of root\n");
+ exfat_free_inode(root);
+ exfat->root = NULL;
+ return -EINVAL;
+ }
+ root->size = clus_count * exfat->clus_size;
+
+ 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;
+
+ while (i && (bytes >> i * 10) == 0)
+ i--;
+
+ shift = i * 10;
+ quoti = (unsigned int)(bytes / (1ULL << shift));
+ remain = 0;
+ if (shift > 0) {
+ remain = (unsigned int)
+ ((bytes & ((1ULL << shift) - 1)) >> (shift - 10));
+ remain = (remain * 100) / 1024;
+ }
+
+ snprintf(buf, sizeof(buf), "%u.%02u %s", quoti, remain, units[i]);
+ return buf;
+}
+
+static void exfat_show_info(struct exfat_fsck *fsck, const char *dev_name)
+{
+ struct exfat *exfat = fsck->exfat;
+ bool clean;
+
+ exfat_info("sector size: %s\n",
+ bytes_to_human_readable(1 << exfat->bs->bsx.sect_size_bits));
+ exfat_info("cluster size: %s\n",
+ bytes_to_human_readable(exfat->clus_size));
+ exfat_info("volume size: %s\n",
+ bytes_to_human_readable(exfat->blk_dev->size));
+
+ clean = exfat_stat.error_count == 0 ||
+ exfat_stat.error_count == exfat_stat.fixed_count;
+ printf("%s: %s. directories %ld, files %ld\n", dev_name,
+ clean ? "clean" : "corrupted",
+ exfat_stat.dir_count, exfat_stat.file_count);
+ if (exfat_stat.error_count)
+ printf("%s: files corrupted %ld, files fixed %ld\n", dev_name,
+ exfat_stat.error_count - exfat_stat.fixed_count,
+ exfat_stat.fixed_count);
+}
+
+int main(int argc, char * const argv[])
+{
+ struct fsck_user_input ui;
+ struct exfat_blk_dev bd;
+ struct pbr *bs = NULL;
+ int c, ret, exit_code;
+ bool version_only = false;
+
+ memset(&ui, 0, sizeof(ui));
+ memset(&bd, 0, sizeof(bd));
+
+ print_level = EXFAT_ERROR;
+
+ if (!setlocale(LC_CTYPE, ""))
+ exfat_err("failed to init locale/codeset\n");
+
+ opterr = 0;
+ while ((c = getopt_long(argc, argv, "arynpbsVvh", opts, NULL)) != EOF) {
+ switch (c) {
+ case 'n':
+ if (ui.options & FSCK_OPTS_REPAIR_ALL)
+ usage(argv[0]);
+ ui.options |= FSCK_OPTS_REPAIR_NO;
+ break;
+ case 'r':
+ if (ui.options & FSCK_OPTS_REPAIR_ALL)
+ usage(argv[0]);
+ ui.options |= FSCK_OPTS_REPAIR_ASK;
+ break;
+ case 'y':
+ if (ui.options & FSCK_OPTS_REPAIR_ALL)
+ usage(argv[0]);
+ ui.options |= FSCK_OPTS_REPAIR_YES;
+ break;
+ case 'a':
+ case 'p':
+ if (ui.options & FSCK_OPTS_REPAIR_ALL)
+ usage(argv[0]);
+ ui.options |= FSCK_OPTS_REPAIR_AUTO;
+ break;
+ case 'b':
+ ui.options |= FSCK_OPTS_IGNORE_BAD_FS_NAME;
+ break;
+ case 's':
+ ui.options |= FSCK_OPTS_RESCUE_CLUS;
+ break;
+ case 'V':
+ version_only = true;
+ break;
+ case 'v':
+ if (print_level < EXFAT_DEBUG)
+ print_level++;
+ break;
+ case '?':
+ case 'h':
+ default:
+ usage(argv[0]);
+ }
+ }
+
+ show_version();
+ if (optind != argc - 1)
+ usage(argv[0]);
+
+ if (version_only)
+ exit(FSCK_EXIT_SYNTAX_ERROR);
+ if (ui.options & FSCK_OPTS_REPAIR_WRITE)
+ ui.ei.writeable = true;
+ else {
+ if (ui.options & (FSCK_OPTS_IGNORE_BAD_FS_NAME |
+ FSCK_OPTS_RESCUE_CLUS))
+ usage(argv[0]);
+ ui.options |= FSCK_OPTS_REPAIR_NO;
+ ui.ei.writeable = false;
+ }
+
+ exfat_fsck.options = ui.options;
+
+ snprintf(ui.ei.dev_name, sizeof(ui.ei.dev_name), "%s", argv[optind]);
+ ret = exfat_get_blk_dev_info(&ui.ei, &bd);
+ if (ret < 0) {
+ exfat_err("failed to open %s. %d\n", ui.ei.dev_name, ret);
+ return FSCK_EXIT_OPERATION_ERROR;
+ }
+
+ ret = exfat_boot_region_check(&bd, &bs,
+ ui.options & FSCK_OPTS_IGNORE_BAD_FS_NAME ?
+ true : false);
+ if (ret)
+ goto err;
+
+ exfat_fsck.exfat = exfat_alloc_exfat(&bd, bs);
+ if (!exfat_fsck.exfat) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ exfat_fsck.buffer_desc = exfat_alloc_buffer(2,
+ exfat_fsck.exfat->clus_size,
+ exfat_fsck.exfat->sect_size);
+ if (!exfat_fsck.buffer_desc) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ if ((exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) &&
+ exfat_mark_volume_dirty(exfat_fsck.exfat, true)) {
+ ret = -EIO;
+ goto err;
+ }
+
+ exfat_debug("verifying root directory...\n");
+ ret = exfat_root_dir_check(exfat_fsck.exfat);
+ if (ret) {
+ exfat_err("failed to verify root directory.\n");
+ goto out;
+ }
+
+ exfat_debug("verifying directory entries...\n");
+ ret = exfat_filesystem_check(&exfat_fsck);
+ if (ret)
+ goto out;
+
+ if (exfat_fsck.options & FSCK_OPTS_RESCUE_CLUS) {
+ rescue_orphan_clusters(&exfat_fsck);
+ exfat_fsck.dirty = true;
+ exfat_fsck.dirty_fat = true;
+ }
+
+ if (exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE) {
+ ret = write_bitmap(&exfat_fsck);
+ if (ret) {
+ exfat_err("failed to write bitmap\n");
+ goto out;
+ }
+ }
+
+ if (ui.ei.writeable && fsync(bd.dev_fd)) {
+ exfat_err("failed to sync\n");
+ ret = -EIO;
+ goto out;
+ }
+ if (exfat_fsck.options & FSCK_OPTS_REPAIR_WRITE)
+ exfat_mark_volume_dirty(exfat_fsck.exfat, false);
+
+out:
+ exfat_show_info(&exfat_fsck, ui.ei.dev_name);
+err:
+ if (ret && ret != -EINVAL)
+ exit_code = FSCK_EXIT_OPERATION_ERROR;
+ else if (ret == -EINVAL ||
+ exfat_stat.error_count != exfat_stat.fixed_count)
+ exit_code = FSCK_EXIT_ERRORS_LEFT;
+ else if (exfat_fsck.dirty)
+ exit_code = FSCK_EXIT_CORRECTED;
+ else
+ exit_code = FSCK_EXIT_NO_ERRORS;
+
+ if (exfat_fsck.buffer_desc)
+ exfat_free_buffer(exfat_fsck.buffer_desc, 2);
+ if (exfat_fsck.exfat)
+ exfat_free_exfat(exfat_fsck.exfat);
+ close(bd.dev_fd);
+ return exit_code;
+}
diff --git a/fsck/fsck.h b/fsck/fsck.h
new file mode 100644
index 0000000..ee0cb30
--- /dev/null
+++ b/fsck/fsck.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+#ifndef _FSCK_H
+#define _FSCK_H
+
+#include "list.h"
+
+enum fsck_ui_options {
+ FSCK_OPTS_REPAIR_ASK = 0x01,
+ FSCK_OPTS_REPAIR_YES = 0x02,
+ FSCK_OPTS_REPAIR_NO = 0x04,
+ 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_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;
+
+ char *name_hash_bitmap;
+};
+
+off_t exfat_c2o(struct exfat *exfat, unsigned int clus);
+
+#endif
diff --git a/fsck/repair.c b/fsck/repair.c
new file mode 100644
index 0000000..f983ee1
--- /dev/null
+++ b/fsck/repair.c
@@ -0,0 +1,295 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <errno.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.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 */
+#define ERF_PREEN_YES 0x00000001
+#define ERF_DEFAULT_YES 0x00000002
+#define ERF_DEFAULT_NO 0x00000004
+
+/* 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, 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)
+{
+ unsigned int i;
+
+ for (i = 0; i < sizeof(problems)/sizeof(problems[0]); i++) {
+ if (problems[i].prcode == prcode) {
+ return &problems[i];
+ }
+ }
+ return NULL;
+}
+
+static int ask_repair(struct exfat_fsck *fsck, struct exfat_repair_problem *pr)
+{
+ int repair = 0;
+ char answer[8];
+
+ 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 (fsck->options & FSCK_OPTS_REPAIR_ASK) {
+ do {
+ 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))
+ 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 1;
+ else if (strcasecmp(answer, "\n") == 0 ||
+ strcasecmp(answer, "N\n") == 0)
+ return 0;
+ }
+ } while (1);
+ } else if (fsck->options & FSCK_OPTS_REPAIR_AUTO &&
+ pr->flags & ERF_PREEN_YES)
+ repair = 1;
+ }
+
+ 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;
+}
+
+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 0;
+ }
+
+ va_start(ap, desc);
+ vprintf(desc, ap);
+ va_end(ap);
+
+ repair = ask_repair(fsck, pr);
+ if (repair) {
+ if (pr->prompt_type & ERP_TRUNCATE)
+ 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
new file mode 100644
index 0000000..634cc49
--- /dev/null
+++ b/fsck/repair.h
@@ -0,0 +1,38 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2020 Hyunchul Lee <hyc.lee@gmail.com>
+ */
+#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
+#define ER_FILE_SMALLER_SIZE 0x00002004
+#define ER_FILE_LARGER_SIZE 0x00002005
+#define ER_FILE_DUPLICATED_CLUS 0x00002006
+#define ER_FILE_ZERO_NOFAT 0x00002007
+
+typedef unsigned int er_problem_code_t;
+struct exfat_fsck;
+
+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