summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/Android.bp12
-rw-r--r--lib/Makefile.am4
-rw-r--r--lib/Makefile.in606
-rw-r--r--lib/exfat_dir.c930
-rw-r--r--lib/exfat_fs.c304
-rw-r--r--lib/libexfat.c856
6 files changed, 2712 insertions, 0 deletions
diff --git a/lib/Android.bp b/lib/Android.bp
new file mode 100644
index 0000000..f97aff5
--- /dev/null
+++ b/lib/Android.bp
@@ -0,0 +1,12 @@
+// Copyright 2020 The Android Open Source Project
+
+cc_library_static {
+ name: "libexfat",
+
+ srcs: [
+ "libexfat.c",
+ "exfat_fs.c",
+ "exfat_dir.c",
+ ],
+ defaults: ["exfatprogs-defaults"],
+}
diff --git a/lib/Makefile.am b/lib/Makefile.am
new file mode 100644
index 0000000..d732cfd
--- /dev/null
+++ b/lib/Makefile.am
@@ -0,0 +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 exfat_fs.c exfat_dir.c
diff --git a/lib/Makefile.in b/lib/Makefile.in
new file mode 100644
index 0000000..6b7fe70
--- /dev/null
+++ b/lib/Makefile.in
@@ -0,0 +1,606 @@
+# Makefile.in generated by automake 1.16.1 from Makefile.am.
+# @configure_input@
+
+# Copyright (C) 1994-2018 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@
+subdir = lib
+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 =
+LIBRARIES = $(noinst_LIBRARIES)
+ARFLAGS = cru
+AM_V_AR = $(am__v_AR_@AM_V@)
+am__v_AR_ = $(am__v_AR_@AM_DEFAULT_V@)
+am__v_AR_0 = @echo " AR " $@;
+am__v_AR_1 =
+libexfat_a_AR = $(AR) $(ARFLAGS)
+libexfat_a_LIBADD =
+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@)
+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__maybe_remake_depfiles = depfiles
+am__depfiles_remade = ./$(DEPDIR)/exfat_dir.Po ./$(DEPDIR)/exfat_fs.Po \
+ ./$(DEPDIR)/libexfat.Po
+am__mv = mv -f
+COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
+ $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
+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 =
+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 = $(libexfat_a_SOURCES)
+DIST_SOURCES = $(libexfat_a_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 -include $(top_builddir)/config.h -I$(top_srcdir)/include -fno-common
+noinst_LIBRARIES = libexfat.a
+libexfat_a_SOURCES = libexfat.c exfat_fs.c exfat_dir.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 lib/Makefile'; \
+ $(am__cd) $(top_srcdir) && \
+ $(AUTOMAKE) --foreign lib/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__maybe_remake_depfiles)'; \
+ cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__maybe_remake_depfiles);; \
+ 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):
+
+clean-noinstLIBRARIES:
+ -test -z "$(noinst_LIBRARIES)" || rm -f $(noinst_LIBRARIES)
+
+libexfat.a: $(libexfat_a_OBJECTS) $(libexfat_a_DEPENDENCIES) $(EXTRA_libexfat_a_DEPENDENCIES)
+ $(AM_V_at)-rm -f libexfat.a
+ $(AM_V_AR)$(libexfat_a_AR) libexfat.a $(libexfat_a_OBJECTS) $(libexfat_a_LIBADD)
+ $(AM_V_at)$(RANLIB) libexfat.a
+
+mostlyclean-compile:
+ -rm -f *.$(OBJEXT)
+
+distclean-compile:
+ -rm -f *.tab.c
+
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exfat_dir.Po@am__quote@ # am--include-marker
+@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/exfat_fs.Po@am__quote@ # am--include-marker
+@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)
+
+.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: $(BUILT_SOURCES)
+ $(MAKE) $(AM_MAKEFLAGS) distdir-am
+
+distdir-am: $(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 $(LIBRARIES)
+installdirs:
+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-noinstLIBRARIES \
+ mostlyclean-am
+
+distclean: distclean-am
+ -rm -f ./$(DEPDIR)/exfat_dir.Po
+ -rm -f ./$(DEPDIR)/exfat_fs.Po
+ -rm -f ./$(DEPDIR)/libexfat.Po
+ -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-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 -f ./$(DEPDIR)/exfat_dir.Po
+ -rm -f ./$(DEPDIR)/exfat_fs.Po
+ -rm -f ./$(DEPDIR)/libexfat.Po
+ -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:
+
+.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 \
+ maintainer-clean-generic mostlyclean mostlyclean-compile \
+ mostlyclean-generic mostlyclean-libtool pdf pdf-am ps ps-am \
+ tags tags-am uninstall uninstall-am
+
+.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/lib/exfat_dir.c b/lib/exfat_dir.c
new file mode 100644
index 0000000..499b672
--- /dev/null
+++ b/lib/exfat_dir.c
@@ -0,0 +1,930 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2021 LG Electronics.
+ *
+ * Author(s): Hyunchul Lee <hyc.lee@gmail.com>
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <string.h>
+#include <time.h>
+
+#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];
+ 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 = 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->dot_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;
+ int retval;
+ bool last_is_free = false;
+
+ 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;
+ }
+
+ 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;
+ }
+ last_is_free = false;
+ } else if ((dentry->type == EXFAT_LAST ||
+ IS_EXFAT_DELETED(dentry->type))) {
+ if (!last_is_free) {
+ free_file_offset =
+ exfat_de_iter_file_offset(&de_iter);
+ free_dev_offset =
+ exfat_de_iter_device_offset(&de_iter);
+ last_is_free = true;
+ }
+ } else {
+ last_is_free = false;
+ }
+
+ 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 && last_is_free) {
+ 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, 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(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;
+
+ filter_out->in.type = EXFAT_FILE;
+ filter_out->in.filter = filter_lookup_file;
+ filter_out->in.param = utf16_name;
+
+ retval = exfat_lookup_dentry_set(exfat, parent, filter_out);
+ if (retval < 0)
+ return retval;
+
+ return 0;
+}
+
+void exfat_calc_dentry_checksum(struct exfat_dentry *dentry,
+ uint16_t *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 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);
+
+ chksum = ((chksum << 15) | (chksum >> 1)) + (ch & 0xFF);
+ chksum = ((chksum << 15) | (chksum >> 1)) + (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 <hyc.lee@gmail.com>
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+
+#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
new file mode 100644
index 0000000..d7c1df1
--- /dev/null
+++ b/lib/libexfat.c
@@ -0,0 +1,856 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ * Copyright (C) 2019 Namjae Jeon <linkinjeon@kernel.org>
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/sysmacros.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <wchar.h>
+#include <limits.h>
+
+#include "exfat_ondisk.h"
+#include "libexfat.h"
+#include "version.h"
+#include "exfat_fs.h"
+#include "exfat_dir.h"
+
+unsigned int print_level = EXFAT_INFO;
+
+void exfat_bitmap_set_range(struct exfat *exfat, char *bitmap,
+ clus_t start_clus, clus_t count)
+{
+ clus_t clus;
+
+ if (!exfat_heap_clus(exfat, start_clus) ||
+ !exfat_heap_clus(exfat, start_clus + count - 1))
+ return;
+
+ clus = start_clus;
+ while (clus < start_clus + count) {
+ exfat_bitmap_set(bitmap, clus);
+ clus++;
+ }
+}
+
+static int exfat_bitmap_find_bit(struct exfat *exfat, char *bmap,
+ clus_t start_clu, clus_t *next,
+ int bit)
+{
+ 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;
+}
+
+int exfat_bitmap_find_zero(struct exfat *exfat, char *bmap,
+ clus_t start_clu, clus_t *next)
+{
+ return exfat_bitmap_find_bit(exfat, bmap,
+ start_clu, next, 0);
+}
+
+int exfat_bitmap_find_one(struct exfat *exfat, char *bmap,
+ clus_t start_clu, clus_t *next)
+{
+ return exfat_bitmap_find_bit(exfat, bmap,
+ start_clu, next, 1);
+}
+
+wchar_t exfat_bad_char(wchar_t w)
+{
+ return (w < 0x0020)
+ || (w == '*') || (w == '?') || (w == '<') || (w == '>')
+ || (w == '|') || (w == '"') || (w == ':') || (w == '/')
+ || (w == '\\');
+}
+
+void boot_calc_checksum(unsigned char *sector, unsigned short size,
+ bool is_boot_sec, __le32 *checksum)
+{
+ unsigned int index;
+
+ if (is_boot_sec) {
+ for (index = 0; index < size; index++) {
+ if ((index == 106) || (index == 107) || (index == 112))
+ continue;
+ *checksum = ((*checksum & 1) ? 0x80000000 : 0) +
+ (*checksum >> 1) + sector[index];
+ }
+ } else {
+ for (index = 0; index < size; index++) {
+ *checksum = ((*checksum & 1) ? 0x80000000 : 0) +
+ (*checksum >> 1) + sector[index];
+ }
+ }
+}
+
+void show_version(void)
+{
+ printf("exfatprogs version : %s\n", EXFAT_PROGS_VERSION);
+}
+
+static inline unsigned int sector_size_bits(unsigned int size)
+{
+ unsigned int bits = 8;
+
+ do {
+ bits++;
+ size >>= 1;
+ } while (size > 256);
+
+ return bits;
+}
+
+static void exfat_set_default_cluster_size(struct exfat_blk_dev *bd,
+ struct exfat_user_input *ui)
+{
+ if (256 * MB >= bd->size)
+ ui->cluster_size = 4 * KB;
+ else if (32 * GB >= bd->size)
+ ui->cluster_size = 32 * KB;
+ else
+ ui->cluster_size = 128 * KB;
+}
+
+void init_user_input(struct exfat_user_input *ui)
+{
+ memset(ui, 0, sizeof(struct exfat_user_input));
+ ui->writeable = true;
+ ui->quick = true;
+}
+
+int exfat_get_blk_dev_info(struct exfat_user_input *ui,
+ struct exfat_blk_dev *bd)
+{
+ int fd, ret = -1;
+ off_t blk_dev_size;
+ struct stat st;
+ unsigned long long blk_dev_offset = 0;
+
+ fd = open(ui->dev_name, ui->writeable ? O_RDWR|O_EXCL : O_RDONLY);
+ if (fd < 0) {
+ exfat_err("open failed : %s, %s\n", ui->dev_name,
+ strerror(errno));
+ return -1;
+ }
+ blk_dev_size = lseek(fd, 0, SEEK_END);
+ if (blk_dev_size <= 0) {
+ exfat_err("invalid block device size(%s)\n",
+ ui->dev_name);
+ ret = blk_dev_size;
+ close(fd);
+ goto out;
+ }
+
+ if (fstat(fd, &st) == 0 && S_ISBLK(st.st_mode)) {
+ char pathname[sizeof("/sys/dev/block/4294967295:4294967295/start")];
+ FILE *fp;
+
+ snprintf(pathname, sizeof(pathname), "/sys/dev/block/%u:%u/start",
+ major(st.st_rdev), minor(st.st_rdev));
+ fp = fopen(pathname, "r");
+ if (fp != NULL) {
+ if (fscanf(fp, "%llu", &blk_dev_offset) == 1) {
+ /*
+ * Linux kernel always reports partition offset
+ * in 512-byte units, regardless of sector size
+ */
+ blk_dev_offset <<= 9;
+ }
+ fclose(fp);
+ }
+ }
+
+ bd->dev_fd = fd;
+ bd->offset = blk_dev_offset;
+ bd->size = blk_dev_size;
+ if (!ui->cluster_size)
+ exfat_set_default_cluster_size(bd, ui);
+
+ if (!ui->boundary_align)
+ ui->boundary_align = DEFAULT_BOUNDARY_ALIGNMENT;
+
+ if (ioctl(fd, BLKSSZGET, &bd->sector_size) < 0)
+ bd->sector_size = DEFAULT_SECTOR_SIZE;
+ bd->sector_size_bits = sector_size_bits(bd->sector_size);
+ bd->num_sectors = blk_dev_size / bd->sector_size;
+ bd->num_clusters = blk_dev_size / ui->cluster_size;
+
+ exfat_debug("Block device name : %s\n", ui->dev_name);
+ exfat_debug("Block device offset : %llu\n", bd->offset);
+ exfat_debug("Block device size : %llu\n", bd->size);
+ exfat_debug("Block sector size : %u\n", bd->sector_size);
+ exfat_debug("Number of the sectors : %llu\n",
+ bd->num_sectors);
+ exfat_debug("Number of the clusters : %u\n",
+ bd->num_clusters);
+
+ ret = 0;
+ bd->dev_fd = fd;
+out:
+ return ret;
+}
+
+ssize_t exfat_read(int fd, void *buf, size_t size, off_t offset)
+{
+ return pread(fd, buf, size, offset);
+}
+
+ssize_t exfat_write(int fd, void *buf, size_t size, off_t offset)
+{
+ return pwrite(fd, buf, size, offset);
+}
+
+size_t exfat_utf16_len(const __le16 *str, size_t max_size)
+{
+ size_t i = 0;
+
+ while (le16_to_cpu(str[i]) && i < max_size)
+ i++;
+ return i;
+}
+
+ssize_t exfat_utf16_enc(const char *in_str, __u16 *out_str, size_t out_size)
+{
+ size_t mbs_len, out_len, i;
+ wchar_t *wcs;
+
+ mbs_len = mbstowcs(NULL, in_str, 0);
+ if (mbs_len == (size_t)-1) {
+ if (errno == EINVAL || errno == EILSEQ)
+ exfat_err("invalid character sequence in current locale\n");
+ return -errno;
+ }
+
+ wcs = calloc(mbs_len+1, sizeof(wchar_t));
+ if (!wcs)
+ return -ENOMEM;
+
+ /* First convert multibyte char* string to wchar_t* string */
+ if (mbstowcs(wcs, in_str, mbs_len+1) == (size_t)-1) {
+ if (errno == EINVAL || errno == EILSEQ)
+ exfat_err("invalid character sequence in current locale\n");
+ free(wcs);
+ return -errno;
+ }
+
+ /* Convert wchar_t* string (sequence of code points) to UTF-16 string */
+ for (i = 0, out_len = 0; i < mbs_len; i++) {
+ if (2*(out_len+1) > out_size ||
+ (wcs[i] >= 0x10000 && 2*(out_len+2) > out_size)) {
+ exfat_err("input string is too long\n");
+ free(wcs);
+ return -E2BIG;
+ }
+
+ /* Encode code point above Plane0 as UTF-16 surrogate pair */
+ if (wcs[i] >= 0x10000) {
+ out_str[out_len++] =
+ cpu_to_le16(((wcs[i] - 0x10000) >> 10) + 0xD800);
+ wcs[i] = ((wcs[i] - 0x10000) & 0x3FF) + 0xDC00;
+ }
+
+ out_str[out_len++] = cpu_to_le16(wcs[i]);
+ }
+
+ free(wcs);
+ return 2*out_len;
+}
+
+ssize_t exfat_utf16_dec(const __u16 *in_str, size_t in_len,
+ char *out_str, size_t out_size)
+{
+ size_t wcs_len, out_len, c_len, i;
+ char c_str[MB_LEN_MAX];
+ wchar_t *wcs;
+ mbstate_t ps;
+ wchar_t w;
+
+ wcs = calloc(in_len/2+1, sizeof(wchar_t));
+ if (!wcs)
+ return -ENOMEM;
+
+ /* First convert UTF-16 string to wchar_t* string */
+ for (i = 0, wcs_len = 0; i < in_len/2; i++, wcs_len++) {
+ wcs[wcs_len] = le16_to_cpu(in_str[i]);
+ /*
+ * If wchar_t can store code point above Plane0
+ * then unpack UTF-16 surrogate pair to code point
+ */
+#if WCHAR_MAX >= 0x10FFFF
+ if (wcs[wcs_len] >= 0xD800 && wcs[wcs_len] <= 0xDBFF &&
+ i+1 < in_len/2) {
+ w = le16_to_cpu(in_str[i+1]);
+ if (w >= 0xDC00 && w <= 0xDFFF) {
+ wcs[wcs_len] = 0x10000 +
+ ((wcs[wcs_len] - 0xD800) << 10) +
+ (w - 0xDC00);
+ i++;
+ }
+ }
+#endif
+ }
+
+ memset(&ps, 0, sizeof(ps));
+
+ /* And then convert wchar_t* string to multibyte char* string */
+ for (i = 0, out_len = 0, c_len = 0; i <= wcs_len; i++) {
+ c_len = wcrtomb(c_str, wcs[i], &ps);
+ /*
+ * If character is non-representable in current locale then
+ * try to store it as Unicode replacement code point U+FFFD
+ */
+ if (c_len == (size_t)-1 && errno == EILSEQ)
+ c_len = wcrtomb(c_str, 0xFFFD, &ps);
+ /* If U+FFFD is also non-representable, try question mark */
+ if (c_len == (size_t)-1 && errno == EILSEQ)
+ c_len = wcrtomb(c_str, L'?', &ps);
+ /* If also (7bit) question mark fails then we cannot do more */
+ if (c_len == (size_t)-1) {
+ exfat_err("invalid UTF-16 sequence\n");
+ free(wcs);
+ return -errno;
+ }
+ if (out_len+c_len > out_size) {
+ exfat_err("input string is too long\n");
+ free(wcs);
+ return -E2BIG;
+ }
+ memcpy(out_str+out_len, c_str, c_len);
+ out_len += c_len;
+ }
+
+ free(wcs);
+
+ /* Last iteration of above loop should have produced null byte */
+ if (c_len == 0 || out_str[out_len-1] != 0) {
+ exfat_err("invalid UTF-16 sequence\n");
+ return -errno;
+ }
+
+ return out_len-1;
+}
+
+off_t exfat_get_root_entry_offset(struct exfat_blk_dev *bd)
+{
+ struct pbr *bs;
+ int nbytes;
+ unsigned int cluster_size, sector_size;
+ off_t root_clu_off;
+
+ bs = (struct pbr *)malloc(EXFAT_MAX_SECTOR_SIZE);
+ if (!bs) {
+ exfat_err("failed to allocate memory\n");
+ return -ENOMEM;
+ }
+
+ nbytes = exfat_read(bd->dev_fd, bs, EXFAT_MAX_SECTOR_SIZE, 0);
+ if (nbytes != EXFAT_MAX_SECTOR_SIZE) {
+ exfat_err("boot sector read failed: %d\n", errno);
+ free(bs);
+ 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 +
+ (le32_to_cpu(bs->bsx.root_cluster) - EXFAT_RESERVED_CLUSTERS) *
+ cluster_size;
+ free(bs);
+
+ return root_clu_off;
+}
+
+char *exfat_conv_volume_label(struct exfat_dentry *vol_entry)
+{
+ char *volume_label;
+ __le16 disk_label[VOLUME_LABEL_MAX_LEN];
+
+ volume_label = malloc(VOLUME_LABEL_BUFFER_SIZE);
+ if (!volume_label)
+ return NULL;
+
+ memcpy(disk_label, vol_entry->vol_label, sizeof(disk_label));
+ memset(volume_label, 0, VOLUME_LABEL_BUFFER_SIZE);
+ if (exfat_utf16_dec(disk_label, vol_entry->vol_char_cnt*2,
+ volume_label, VOLUME_LABEL_BUFFER_SIZE) < 0) {
+ exfat_err("failed to decode volume label\n");
+ free(volume_label);
+ return NULL;
+ }
+
+ return volume_label;
+}
+
+int exfat_read_volume_label(struct exfat *exfat)
+{
+ struct exfat_dentry *dentry;
+ int err;
+ __le16 disk_label[VOLUME_LABEL_MAX_LEN];
+ struct exfat_lookup_filter filter = {
+ .in.type = EXFAT_VOLUME,
+ .in.filter = NULL,
+ };
+
+ err = exfat_lookup_dentry_set(exfat, exfat->root, &filter);
+ if (err)
+ return err;
+
+ dentry = filter.out.dentry_set;
+
+ 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;
+ }
+
+ 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;
+ }
+
+ exfat_info("label: %s\n", exfat->volume_label);
+out:
+ free(filter.out.dentry_set);
+ return err;
+}
+
+int exfat_set_volume_label(struct exfat *exfat, char *label_input)
+{
+ struct exfat_dentry *pvol;
+ struct exfat_dentry_loc loc;
+ __u16 volume_label[VOLUME_LABEL_MAX_LEN];
+ int volume_label_len, dcount, err;
+
+ struct exfat_lookup_filter filter = {
+ .in.type = EXFAT_VOLUME,
+ .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;
+ }
+
+ memcpy(pvol->vol_label, volume_label, volume_label_len);
+ pvol->vol_char_cnt = volume_label_len/2;
+
+ 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;
+}
+
+int exfat_read_sector(struct exfat_blk_dev *bd, void *buf, unsigned int sec_off)
+{
+ int ret;
+ unsigned long long offset =
+ (unsigned long long)sec_off * 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;
+ }
+ return 0;
+}
+
+int exfat_write_sector(struct exfat_blk_dev *bd, void *buf,
+ unsigned int sec_off)
+{
+ int bytes;
+ unsigned long long offset =
+ (unsigned long long)sec_off * 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);
+ return -1;
+ }
+ return 0;
+}
+
+int exfat_write_checksum_sector(struct exfat_blk_dev *bd,
+ unsigned int checksum, bool is_backup)
+{
+ __le32 *checksum_buf;
+ int ret = 0;
+ unsigned int i;
+ unsigned int sec_idx = CHECKSUM_SEC_IDX;
+
+ checksum_buf = malloc(bd->sector_size);
+ if (!checksum_buf)
+ return -1;
+
+ if (is_backup)
+ sec_idx += BACKUP_BOOT_SEC_IDX;
+
+ for (i = 0; i < bd->sector_size / sizeof(int); i++)
+ checksum_buf[i] = cpu_to_le32(checksum);
+
+ ret = exfat_write_sector(bd, checksum_buf, sec_idx);
+ if (ret) {
+ exfat_err("checksum sector write failed\n");
+ goto free;
+ }
+
+free:
+ free(checksum_buf);
+ return ret;
+}
+
+int exfat_show_volume_serial(int fd)
+{
+ struct pbr *ppbr;
+ int ret;
+
+ ppbr = malloc(EXFAT_MAX_SECTOR_SIZE);
+ if (!ppbr) {
+ exfat_err("Cannot allocate pbr: out of memory\n");
+ return -1;
+ }
+
+ /* read main boot sector */
+ ret = exfat_read(fd, (char *)ppbr, EXFAT_MAX_SECTOR_SIZE, 0);
+ if (ret < 0) {
+ exfat_err("main boot sector read failed\n");
+ ret = -1;
+ 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:
+ free(ppbr);
+ return ret;
+}
+
+static int exfat_update_boot_checksum(struct exfat_blk_dev *bd, bool is_backup)
+{
+ unsigned int checksum = 0;
+ int ret, sec_idx, backup_sec_idx = 0;
+ unsigned char *buf;
+
+ buf = malloc(bd->sector_size);
+ if (!buf) {
+ exfat_err("Cannot allocate pbr: out of memory\n");
+ return -1;
+ }
+
+ if (is_backup)
+ backup_sec_idx = BACKUP_BOOT_SEC_IDX;
+
+ for (sec_idx = BOOT_SEC_IDX; sec_idx < CHECKSUM_SEC_IDX; sec_idx++) {
+ bool is_boot_sec = false;
+
+ ret = exfat_read_sector(bd, buf, sec_idx + backup_sec_idx);
+ if (ret < 0) {
+ exfat_err("sector(%d) read failed\n", sec_idx);
+ ret = -1;
+ goto free_buf;
+ }
+
+ if (sec_idx == BOOT_SEC_IDX)
+ is_boot_sec = true;
+
+ boot_calc_checksum(buf, bd->sector_size, is_boot_sec,
+ &checksum);
+ }
+
+ ret = exfat_write_checksum_sector(bd, checksum, is_backup);
+
+free_buf:
+ free(buf);
+
+ return ret;
+}
+
+int exfat_set_volume_serial(struct exfat_blk_dev *bd,
+ struct exfat_user_input *ui)
+{
+ int ret;
+ struct pbr *ppbr;
+
+ ppbr = malloc(EXFAT_MAX_SECTOR_SIZE);
+ if (!ppbr) {
+ exfat_err("Cannot allocate pbr: out of memory\n");
+ return -1;
+ }
+
+ /* read main boot sector */
+ ret = exfat_read(bd->dev_fd, (char *)ppbr, EXFAT_MAX_SECTOR_SIZE,
+ BOOT_SEC_IDX);
+ if (ret < 0) {
+ exfat_err("main boot sector read failed\n");
+ ret = -1;
+ 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;
+
+ /* update main boot sector */
+ ret = exfat_write_sector(bd, (char *)ppbr, BOOT_SEC_IDX);
+ if (ret < 0) {
+ exfat_err("main boot sector write failed\n");
+ ret = -1;
+ goto free_ppbr;
+ }
+
+ /* update backup boot sector */
+ ret = exfat_write_sector(bd, (char *)ppbr, BACKUP_BOOT_SEC_IDX);
+ if (ret < 0) {
+ exfat_err("backup boot sector write failed\n");
+ ret = -1;
+ goto free_ppbr;
+ }
+
+ ret = exfat_update_boot_checksum(bd, 0);
+ if (ret < 0) {
+ exfat_err("main checksum update failed\n");
+ goto free_ppbr;
+ }
+
+ ret = exfat_update_boot_checksum(bd, 1);
+ if (ret < 0)
+ exfat_err("backup checksum update failed\n");
+free_ppbr:
+ free(ppbr);
+
+ exfat_info("New volume serial : 0x%x\n", ui->volume_serial);
+
+ return ret;
+}
+
+unsigned int exfat_clus_to_blk_dev_off(struct exfat_blk_dev *bd,
+ unsigned int clu_off_sectnr, unsigned int clu)
+{
+ 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)
+{
+ 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));
+}
+
+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;
+}