diff mbox series

[v8,02/13] exfat: add super block operations

Message ID 20191220062419.23516-3-namjae.jeon@samsung.com (mailing list archive)
State New, archived
Headers show
Series add the latest exfat driver | expand

Commit Message

Namjae Jeon Dec. 20, 2019, 6:24 a.m. UTC
This adds the implementation of superblock operations for exfat.

Signed-off-by: Namjae Jeon <namjae.jeon@samsung.com>
Signed-off-by: Sungjong Seo <sj1557.seo@samsung.com>
---
 fs/exfat/super.c | 732 +++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 732 insertions(+)
 create mode 100644 fs/exfat/super.c

Comments

Pali Rohár Dec. 29, 2019, 1:40 p.m. UTC | #1
On Friday 20 December 2019 01:24:08 Namjae Jeon wrote:
> This adds the implementation of superblock operations for exfat.
> 
> Signed-off-by: Namjae Jeon <namjae.jeon@samsung.com>
> Signed-off-by: Sungjong Seo <sj1557.seo@samsung.com>
> ---
>  fs/exfat/super.c | 732 +++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 732 insertions(+)
>  create mode 100644 fs/exfat/super.c
> 
> diff --git a/fs/exfat/super.c b/fs/exfat/super.c
> new file mode 100644
> index 000000000000..07687997c7f8
> --- /dev/null
> +++ b/fs/exfat/super.c

...

> +enum {
> +	Opt_uid,
> +	Opt_gid,
> +	Opt_umask,
> +	Opt_dmask,
> +	Opt_fmask,
> +	Opt_allow_utime,
> +	Opt_charset,
> +	Opt_utf8,
> +	Opt_case_sensitive,
> +	Opt_tz,
> +	Opt_errors,
> +	Opt_discard,
> +};
> +
> +static const struct fs_parameter_spec exfat_param_specs[] = {
> +	fsparam_u32("uid",			Opt_uid),
> +	fsparam_u32("gid",			Opt_gid),
> +	fsparam_u32oct("umask",			Opt_umask),
> +	fsparam_u32oct("dmask",			Opt_dmask),
> +	fsparam_u32oct("fmask",			Opt_fmask),
> +	fsparam_u32oct("allow_utime",		Opt_allow_utime),
> +	fsparam_string("iocharset",		Opt_charset),
> +	fsparam_flag("utf8",			Opt_utf8),

Hello! What is the purpose of having extra special "utf8" mount option?
Is not one "iocharset=utf8" option enough?

> +	fsparam_flag("case_sensitive",		Opt_case_sensitive),
> +	fsparam_string("tz",			Opt_tz),
> +	fsparam_enum("errors",			Opt_errors),
> +	fsparam_flag("discard",			Opt_discard),
> +	{}
> +};
Pali Rohár Dec. 29, 2019, 1:55 p.m. UTC | #2
On Friday 20 December 2019 01:24:08 Namjae Jeon wrote:
> This adds the implementation of superblock operations for exfat.
> 
> Signed-off-by: Namjae Jeon <namjae.jeon@samsung.com>
> Signed-off-by: Sungjong Seo <sj1557.seo@samsung.com>
> ---
>  fs/exfat/super.c | 732 +++++++++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 732 insertions(+)
>  create mode 100644 fs/exfat/super.c
> 
> diff --git a/fs/exfat/super.c b/fs/exfat/super.c
> new file mode 100644
> index 000000000000..07687997c7f8
> --- /dev/null
> +++ b/fs/exfat/super.c

...

> +
> +	if (le16_to_cpu(p_bpb->bsx.vol_flags) & VOL_DIRTY) {
> +		sbi->vol_flag |= VOL_DIRTY;
> +		exfat_msg(sb, KERN_WARNING,
> +			"Volume was not properly unmounted. Some data may be corrupt. Please run fsck.");

Hello, do you have some pointers which fsck tool should user run in this
case?

> +	}
> +
> +	ret = exfat_create_upcase_table(sb);
> +	if (ret) {
> +		exfat_msg(sb, KERN_ERR, "failed to load upcase table");
> +		goto free_bh;
> +	}
> +
> +	/* allocate-bitmap is only for exFAT */

It looks like that this comment is relict from previous version which
had also FAT32 code included...

> +	ret = exfat_load_bitmap(sb);
> +	if (ret) {
> +		exfat_msg(sb, KERN_ERR, "failed to load alloc-bitmap");
> +		goto free_upcase_table;
> +	}
Namjae Jeon Dec. 31, 2019, 1:32 p.m. UTC | #3
>> +	if (le16_to_cpu(p_bpb->bsx.vol_flags) & VOL_DIRTY) {
>> +		sbi->vol_flag |= VOL_DIRTY;
>> +		exfat_msg(sb, KERN_WARNING,
>> +			"Volume was not properly unmounted. Some data may be corrupt. Please
>> run fsck.");
>
> Hello, do you have some pointers which fsck tool should user run in this
> case?
Hello,
Only windows recovery tool for now. fsck in fuse-exfat is early stage yet.
So I am preparing exfat-tools included a reliable fsck.

>
>> +	}
>> +
>> +	ret = exfat_create_upcase_table(sb);
>> +	if (ret) {
>> +		exfat_msg(sb, KERN_ERR, "failed to load upcase table");
>> +		goto free_bh;
>> +	}
>> +
>> +	/* allocate-bitmap is only for exFAT */
>
> It looks like that this comment is relict from previous version which
> had also FAT32 code included...
Yes, It is leftover from vfat support in sdfat, Will remove it.

Thanks!
Namjae Jeon Jan. 2, 2020, 6:06 a.m. UTC | #4
> > +static const struct fs_parameter_spec exfat_param_specs[] = {
> > +	fsparam_u32("uid",			Opt_uid),
> > +	fsparam_u32("gid",			Opt_gid),
> > +	fsparam_u32oct("umask",			Opt_umask),
> > +	fsparam_u32oct("dmask",			Opt_dmask),
> > +	fsparam_u32oct("fmask",			Opt_fmask),
> > +	fsparam_u32oct("allow_utime",		Opt_allow_utime),
> > +	fsparam_string("iocharset",		Opt_charset),
> > +	fsparam_flag("utf8",			Opt_utf8),
> 
> Hello! What is the purpose of having extra special "utf8" mount option?
> Is not one "iocharset=utf8" option enough?
utf8 nls_table supports utf8<->utf32 conversion and does not support
surrogate character conversion. The utf8 option can support the surrogate
character conversion of utf16 using utf16s_to_utf8s/utf8s_to_utf16s of
the nls base.

Thanks!
Pali Rohár Jan. 2, 2020, 8:30 a.m. UTC | #5
On Thursday 02 January 2020 15:06:16 Namjae Jeon wrote:
> > > +static const struct fs_parameter_spec exfat_param_specs[] = {
> > > +	fsparam_u32("uid",			Opt_uid),
> > > +	fsparam_u32("gid",			Opt_gid),
> > > +	fsparam_u32oct("umask",			Opt_umask),
> > > +	fsparam_u32oct("dmask",			Opt_dmask),
> > > +	fsparam_u32oct("fmask",			Opt_fmask),
> > > +	fsparam_u32oct("allow_utime",		Opt_allow_utime),
> > > +	fsparam_string("iocharset",		Opt_charset),
> > > +	fsparam_flag("utf8",			Opt_utf8),
> > 
> > Hello! What is the purpose of having extra special "utf8" mount option?
> > Is not one "iocharset=utf8" option enough?
> utf8 nls_table supports utf8<->utf32 conversion and does not support
> surrogate character conversion.

So in other words, this is just subset of UTF-8 just to 3 byte long
sequences (for Unicode code points up to the U+FFFF).

> The utf8 option can support the surrogate
> character conversion of utf16 using utf16s_to_utf8s/utf8s_to_utf16s of
> the nls base.

So this is full UTF-8 support, right?

And what is the point to have two options for UTF-8 support, when one is
incomplete / broken? I see no benefit to have first option at all.
Providing incomplete / broken support to userspace does not make much
sense if we already have full and working support via different mount
option. Maybe second option with full UTF-8 support should be used also
by iocharset=utf8 and then we do not need utf8 option at all?
Namjae Jeon Jan. 2, 2020, 1:13 p.m. UTC | #6
2020-01-02 17:30 GMT+09:00, Pali Rohár <pali.rohar@gmail.com>:
> On Thursday 02 January 2020 15:06:16 Namjae Jeon wrote:
>> > > +static const struct fs_parameter_spec exfat_param_specs[] = {
>> > > +	fsparam_u32("uid",			Opt_uid),
>> > > +	fsparam_u32("gid",			Opt_gid),
>> > > +	fsparam_u32oct("umask",			Opt_umask),
>> > > +	fsparam_u32oct("dmask",			Opt_dmask),
>> > > +	fsparam_u32oct("fmask",			Opt_fmask),
>> > > +	fsparam_u32oct("allow_utime",		Opt_allow_utime),
>> > > +	fsparam_string("iocharset",		Opt_charset),
>> > > +	fsparam_flag("utf8",			Opt_utf8),
>> >
>> > Hello! What is the purpose of having extra special "utf8" mount option?
>> > Is not one "iocharset=utf8" option enough?
>> utf8 nls_table supports utf8<->utf32 conversion and does not support
>> surrogate character conversion.
>
> So in other words, this is just subset of UTF-8 just to 3 byte long
> sequences (for Unicode code points up to the U+FFFF).
>
>> The utf8 option can support the surrogate
>> character conversion of utf16 using utf16s_to_utf8s/utf8s_to_utf16s of
>> the nls base.
>
> So this is full UTF-8 support, right?
>
> And what is the point to have two options for UTF-8 support, when one is
> incomplete / broken? I see no benefit to have first option at all.
> Providing incomplete / broken support to userspace does not make much
> sense if we already have full and working support via different mount
> option. Maybe second option with full UTF-8 support should be used also
> by iocharset=utf8 and then we do not need utf8 option at all?
Make sense. I will make it one option.

Thanks!
>
> --
> Pali Rohár
> pali.rohar@gmail.com
>
Pali Rohár Jan. 2, 2020, 1:16 p.m. UTC | #7
On Thursday 02 January 2020 09:30:29 Pali Rohár wrote:
> On Thursday 02 January 2020 15:06:16 Namjae Jeon wrote:
> > > > +static const struct fs_parameter_spec exfat_param_specs[] = {
> > > > +	fsparam_u32("uid",			Opt_uid),
> > > > +	fsparam_u32("gid",			Opt_gid),
> > > > +	fsparam_u32oct("umask",			Opt_umask),
> > > > +	fsparam_u32oct("dmask",			Opt_dmask),
> > > > +	fsparam_u32oct("fmask",			Opt_fmask),
> > > > +	fsparam_u32oct("allow_utime",		Opt_allow_utime),
> > > > +	fsparam_string("iocharset",		Opt_charset),
> > > > +	fsparam_flag("utf8",			Opt_utf8),
> > > 
> > > Hello! What is the purpose of having extra special "utf8" mount option?
> > > Is not one "iocharset=utf8" option enough?
> > utf8 nls_table supports utf8<->utf32 conversion and does not support
> > surrogate character conversion.
> 
> So in other words, this is just subset of UTF-8 just to 3 byte long
> sequences (for Unicode code points up to the U+FFFF).

Anyway, this is limitation of kernel's NLS framework? Or limitation in
current exfat driver implementation?

Because if it is in kernel's NLS framework then all kernel drivers would
be affected by this limitation, and not only exfat.
Namjae Jeon Jan. 2, 2020, 1:41 p.m. UTC | #8
2020-01-02 22:16 GMT+09:00, Pali Rohár <pali.rohar@gmail.com>:
> On Thursday 02 January 2020 09:30:29 Pali Rohár wrote:
>> On Thursday 02 January 2020 15:06:16 Namjae Jeon wrote:
>> > > > +static const struct fs_parameter_spec exfat_param_specs[] = {
>> > > > +	fsparam_u32("uid",			Opt_uid),
>> > > > +	fsparam_u32("gid",			Opt_gid),
>> > > > +	fsparam_u32oct("umask",			Opt_umask),
>> > > > +	fsparam_u32oct("dmask",			Opt_dmask),
>> > > > +	fsparam_u32oct("fmask",			Opt_fmask),
>> > > > +	fsparam_u32oct("allow_utime",		Opt_allow_utime),
>> > > > +	fsparam_string("iocharset",		Opt_charset),
>> > > > +	fsparam_flag("utf8",			Opt_utf8),
>> > >
>> > > Hello! What is the purpose of having extra special "utf8" mount
>> > > option?
>> > > Is not one "iocharset=utf8" option enough?
>> > utf8 nls_table supports utf8<->utf32 conversion and does not support
>> > surrogate character conversion.
>>
>> So in other words, this is just subset of UTF-8 just to 3 byte long
>> sequences (for Unicode code points up to the U+FFFF).
>
> Anyway, this is limitation of kernel's NLS framework? Or limitation in
> current exfat driver implementation?
This is not exfat driver issue. Please check fatfs, cifs, etc..
>
> Because if it is in kernel's NLS framework then all kernel drivers would
> be affected by this limitation, and not only exfat.
Yes, FATfs also has two options and There seems to be
CONFIG_FAT_DEFAULT_UTF8 option to avoid the issue you said.

config FAT_DEFAULT_UTF8
        bool "Enable FAT UTF-8 option by default"
        depends on VFAT_FS
        default n
        help
          Set this if you would like to have "utf8" mount option set
          by default when mounting FAT filesystems.

          Even if you say Y here can always disable UTF-8 for
          particular mount by adding "utf8=0" to mount options.

          Say Y if you use UTF-8 encoding for file names, N otherwise.

But the way you suggested looks better.

Thanks!
>
> --
> Pali Rohár
> pali.rohar@gmail.com
>
Pali Rohár Jan. 2, 2020, 2:13 p.m. UTC | #9
On Thursday 02 January 2020 22:41:40 Namjae Jeon wrote:
> 2020-01-02 22:16 GMT+09:00, Pali Rohár <pali.rohar@gmail.com>:
> > On Thursday 02 January 2020 09:30:29 Pali Rohár wrote:
> >> On Thursday 02 January 2020 15:06:16 Namjae Jeon wrote:
> >> > > > +static const struct fs_parameter_spec exfat_param_specs[] = {
> >> > > > +	fsparam_u32("uid",			Opt_uid),
> >> > > > +	fsparam_u32("gid",			Opt_gid),
> >> > > > +	fsparam_u32oct("umask",			Opt_umask),
> >> > > > +	fsparam_u32oct("dmask",			Opt_dmask),
> >> > > > +	fsparam_u32oct("fmask",			Opt_fmask),
> >> > > > +	fsparam_u32oct("allow_utime",		Opt_allow_utime),
> >> > > > +	fsparam_string("iocharset",		Opt_charset),
> >> > > > +	fsparam_flag("utf8",			Opt_utf8),
> >> > >
> >> > > Hello! What is the purpose of having extra special "utf8" mount
> >> > > option?
> >> > > Is not one "iocharset=utf8" option enough?
> >> > utf8 nls_table supports utf8<->utf32 conversion and does not support
> >> > surrogate character conversion.
> >>
> >> So in other words, this is just subset of UTF-8 just to 3 byte long
> >> sequences (for Unicode code points up to the U+FFFF).
> >
> > Anyway, this is limitation of kernel's NLS framework? Or limitation in
> > current exfat driver implementation?
> This is not exfat driver issue. Please check fatfs, cifs, etc..
> >
> > Because if it is in kernel's NLS framework then all kernel drivers would
> > be affected by this limitation, and not only exfat.
> Yes, FATfs also has two options and There seems to be
> CONFIG_FAT_DEFAULT_UTF8 option to avoid the issue you said.
> 
> config FAT_DEFAULT_UTF8
>         bool "Enable FAT UTF-8 option by default"
>         depends on VFAT_FS
>         default n
>         help
>           Set this if you would like to have "utf8" mount option set
>           by default when mounting FAT filesystems.
> 
>           Even if you say Y here can always disable UTF-8 for
>           particular mount by adding "utf8=0" to mount options.
> 
>           Say Y if you use UTF-8 encoding for file names, N otherwise.

I know that VFAT has two options for it, but I think this is historic
relict (backward compatibility). There are also other suspicious options
which today do nothing (e.g. cvf_format). So I would rather do not
compare it with 20 years old fat code...

But I have already looked at kernel's NLS implementation and it is
really limited to Unicode code points up to the U+FFFF. Kernel's wchar_t
type is is just for Plane-0 (u16) and all NLS encodings works with
wchar_t.

For "full Unicode" there is kernel type unicode_t.

And there is another thing, utf8 NLS encoding does not have defined
charset2upper/charset2lower tables.

So for iocharset=utf8 you really do not want to use utf8 NLS module.

But seem that above limitation for Plane-0 in kernel's NLS is not a
problem as all encodings (except utf8) fit into Plane-0. So only UTF-8
needs special handling, other encodings can use kernel's NLS tables.

> But the way you suggested looks better.
> 
> Thanks!
Arnd Bergmann Jan. 8, 2020, 7:55 p.m. UTC | #10
On Fri, Dec 20, 2019 at 7:28 AM Namjae Jeon <namjae.jeon@samsung.com> wrote:

> +static int exfat_fill_super(struct super_block *sb, struct fs_context *fc)
> +{
> +       struct exfat_sb_info *sbi = sb->s_fs_info;
> +       struct exfat_mount_options *opts = &sbi->options;
> +       struct inode *root_inode;
> +       int err;
> +
> +       if (opts->allow_utime == (unsigned short)-1)
> +               opts->allow_utime = ~opts->fs_dmask & 0022;
> +
> +       if (opts->utf8 && strcmp(opts->iocharset, exfat_iocharset_with_utf8)) {
> +               exfat_msg(sb, KERN_WARNING,
> +                       "utf8 enabled, \"iocharset=%s\" is recommended",
> +                       exfat_iocharset_with_utf8);
> +       }
> +
> +       if (opts->discard) {
> +               struct request_queue *q = bdev_get_queue(sb->s_bdev);
> +
> +               if (!blk_queue_discard(q))
> +                       exfat_msg(sb, KERN_WARNING,
> +                               "mounting with \"discard\" option, but the device does not support discard");
> +               opts->discard = 0;
> +       }
> +
> +       sb->s_flags |= SB_NODIRATIME;
> +       sb->s_magic = EXFAT_SUPER_MAGIC;
> +       sb->s_op = &exfat_sops;

I don't see you set up s_time_gran, s_time_min and s_time_max
anywhere. Please fill those to get the correct behavior. That also lets
you drop the manual truncation of the values.

       Arnd
Namjae Jeon Jan. 9, 2020, 11:36 p.m. UTC | #11
> > +static int exfat_fill_super(struct super_block *sb, struct fs_context
> > +*fc) {
> > +       struct exfat_sb_info *sbi = sb->s_fs_info;
> > +       struct exfat_mount_options *opts = &sbi->options;
> > +       struct inode *root_inode;
> > +       int err;
> > +
> > +       if (opts->allow_utime == (unsigned short)-1)
> > +               opts->allow_utime = ~opts->fs_dmask & 0022;
> > +
> > +       if (opts->utf8 && strcmp(opts->iocharset,
> exfat_iocharset_with_utf8)) {
> > +               exfat_msg(sb, KERN_WARNING,
> > +                       "utf8 enabled, \"iocharset=%s\" is recommended",
> > +                       exfat_iocharset_with_utf8);
> > +       }
> > +
> > +       if (opts->discard) {
> > +               struct request_queue *q = bdev_get_queue(sb->s_bdev);
> > +
> > +               if (!blk_queue_discard(q))
> > +                       exfat_msg(sb, KERN_WARNING,
> > +                               "mounting with \"discard\" option, but the
> device does not support discard");
> > +               opts->discard = 0;
> > +       }
> > +
> > +       sb->s_flags |= SB_NODIRATIME;
> > +       sb->s_magic = EXFAT_SUPER_MAGIC;
> > +       sb->s_op = &exfat_sops;
> 
> I don't see you set up s_time_gran, s_time_min and s_time_max anywhere.
> Please fill those to get the correct behavior. That also lets you drop the
> manual truncation of the values.
Okay!

Thanks!
> 
>        Arnd
diff mbox series

Patch

diff --git a/fs/exfat/super.c b/fs/exfat/super.c
new file mode 100644
index 000000000000..07687997c7f8
--- /dev/null
+++ b/fs/exfat/super.c
@@ -0,0 +1,732 @@ 
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *  Copyright (C) 2012-2013 Samsung Electronics Co., Ltd.
+ */
+
+#include <linux/fs_context.h>
+#include <linux/fs_parser.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/time.h>
+#include <linux/mount.h>
+#include <linux/cred.h>
+#include <linux/statfs.h>
+#include <linux/seq_file.h>
+#include <linux/blkdev.h>
+#include <linux/fs_struct.h>
+#include <linux/iversion.h>
+#include <linux/nls.h>
+#include <linux/buffer_head.h>
+
+#include "exfat_raw.h"
+#include "exfat_fs.h"
+
+static char exfat_default_iocharset[] = CONFIG_EXFAT_FS_DEFAULT_IOCHARSET;
+static const char exfat_iocharset_with_utf8[] = "iso8859-1";
+static struct kmem_cache *exfat_inode_cachep;
+
+static void exfat_free_iocharset(struct exfat_sb_info *sbi)
+{
+	if (sbi->options.iocharset != exfat_default_iocharset)
+		kfree(sbi->options.iocharset);
+}
+
+static void exfat_put_super(struct super_block *sb)
+{
+	struct exfat_sb_info *sbi = EXFAT_SB(sb);
+
+	mutex_lock(&sbi->s_lock);
+	if (test_and_clear_bit(EXFAT_SB_DIRTY, &sbi->s_state))
+		sync_blockdev(sb->s_bdev);
+	exfat_set_vol_flags(sb, VOL_CLEAN);
+	exfat_free_upcase_table(sb);
+	exfat_free_bitmap(sb);
+	mutex_unlock(&sbi->s_lock);
+
+	if (sbi->nls_io) {
+		unload_nls(sbi->nls_io);
+		sbi->nls_io = NULL;
+	}
+	exfat_free_iocharset(sbi);
+	sb->s_fs_info = NULL;
+	kfree(sbi);
+}
+
+static int exfat_sync_fs(struct super_block *sb, int wait)
+{
+	struct exfat_sb_info *sbi = EXFAT_SB(sb);
+	int err = 0;
+
+	/* If there are some dirty buffers in the bdev inode */
+	mutex_lock(&sbi->s_lock);
+	if (test_and_clear_bit(EXFAT_SB_DIRTY, &sbi->s_state)) {
+		sync_blockdev(sb->s_bdev);
+		if (exfat_set_vol_flags(sb, VOL_CLEAN))
+			err = -EIO;
+	}
+	mutex_unlock(&sbi->s_lock);
+	return err;
+}
+
+static int exfat_statfs(struct dentry *dentry, struct kstatfs *buf)
+{
+	struct super_block *sb = dentry->d_sb;
+	struct exfat_sb_info *sbi = EXFAT_SB(sb);
+	unsigned long long id = huge_encode_dev(sb->s_bdev->bd_dev);
+
+	if (sbi->used_clusters == ~0u) {
+		mutex_lock(&sbi->s_lock);
+		if (exfat_count_used_clusters(sb, &sbi->used_clusters)) {
+			mutex_unlock(&sbi->s_lock);
+			return -EIO;
+		}
+		mutex_unlock(&sbi->s_lock);
+	}
+
+	buf->f_type = sb->s_magic;
+	buf->f_bsize = sbi->cluster_size;
+	buf->f_blocks = sbi->num_clusters - 2; /* clu 0 & 1 */
+	buf->f_bfree = buf->f_blocks - sbi->used_clusters;
+	buf->f_bavail = buf->f_bfree;
+	buf->f_fsid.val[0] = (unsigned int)id;
+	buf->f_fsid.val[1] = (unsigned int)(id >> 32);
+	/* Unicode utf16 255 characters */
+	buf->f_namelen = EXFAT_MAX_FILE_LEN * NLS_MAX_CHARSET_SIZE;
+	return 0;
+}
+
+int exfat_set_vol_flags(struct super_block *sb, unsigned short new_flag)
+{
+	struct exfat_sb_info *sbi = EXFAT_SB(sb);
+	struct pbr64 *bpb;
+	bool sync = 0;
+
+	/* flags are not changed */
+	if (sbi->vol_flag == new_flag)
+		return 0;
+
+	sbi->vol_flag = new_flag;
+
+	/* skip updating volume dirty flag,
+	 * if this volume has been mounted with read-only
+	 */
+	if (sb_rdonly(sb))
+		return 0;
+
+	if (!sbi->pbr_bh) {
+		sbi->pbr_bh = sb_bread(sb, 0);
+		if (!sbi->pbr_bh) {
+			exfat_msg(sb, KERN_ERR, "failed to read boot sector");
+			return -ENOMEM;
+		}
+	}
+
+	bpb = (struct pbr64 *)sbi->pbr_bh->b_data;
+	bpb->bsx.vol_flags = cpu_to_le16(new_flag);
+
+	if ((new_flag == VOL_DIRTY) && (!buffer_dirty(sbi->pbr_bh)))
+		sync = true;
+	else
+		sync = false;
+
+	set_buffer_uptodate(sbi->pbr_bh);
+	mark_buffer_dirty(sbi->pbr_bh);
+
+	if (sync)
+		sync_dirty_buffer(sbi->pbr_bh);
+	return 0;
+}
+
+static int exfat_show_options(struct seq_file *m, struct dentry *root)
+{
+	struct super_block *sb = root->d_sb;
+	struct exfat_sb_info *sbi = EXFAT_SB(sb);
+	struct exfat_mount_options *opts = &sbi->options;
+
+	/* Show partition info */
+	if (!uid_eq(opts->fs_uid, GLOBAL_ROOT_UID))
+		seq_printf(m, ",uid=%u",
+				from_kuid_munged(&init_user_ns, opts->fs_uid));
+	if (!gid_eq(opts->fs_gid, GLOBAL_ROOT_GID))
+		seq_printf(m, ",gid=%u",
+				from_kgid_munged(&init_user_ns, opts->fs_gid));
+	seq_printf(m, ",fmask=%04o,dmask=%04o", opts->fs_fmask, opts->fs_dmask);
+	if (opts->allow_utime)
+		seq_printf(m, ",allow_utime=%04o", opts->allow_utime);
+	if (sbi->nls_io)
+		seq_printf(m, ",iocharset=%s", sbi->nls_io->charset);
+	if (opts->utf8)
+		seq_puts(m, ",utf8");
+	seq_printf(m, ",case_sensitive=%u", opts->case_sensitive);
+	if (opts->tz_utc)
+		seq_puts(m, ",tz=UTC");
+	seq_printf(m, ",bps=%ld", sb->s_blocksize);
+	if (opts->errors == EXFAT_ERRORS_CONT)
+		seq_puts(m, ",errors=continue");
+	else if (opts->errors == EXFAT_ERRORS_PANIC)
+		seq_puts(m, ",errors=panic");
+	else
+		seq_puts(m, ",errors=remount-ro");
+	if (opts->discard)
+		seq_puts(m, ",discard");
+	return 0;
+}
+
+static struct inode *exfat_alloc_inode(struct super_block *sb)
+{
+	struct exfat_inode_info *ei;
+
+	ei = kmem_cache_alloc(exfat_inode_cachep, GFP_NOFS);
+	if (!ei)
+		return NULL;
+
+	init_rwsem(&ei->truncate_lock);
+	return &ei->vfs_inode;
+}
+
+static void exfat_destroy_inode(struct inode *inode)
+{
+	kmem_cache_free(exfat_inode_cachep, EXFAT_I(inode));
+}
+
+static const struct super_operations exfat_sops = {
+	.alloc_inode   = exfat_alloc_inode,
+	.destroy_inode = exfat_destroy_inode,
+	.write_inode   = exfat_write_inode,
+	.evict_inode  = exfat_evict_inode,
+	.put_super     = exfat_put_super,
+	.sync_fs       = exfat_sync_fs,
+	.statfs        = exfat_statfs,
+	.show_options  = exfat_show_options,
+};
+
+enum {
+	Opt_uid,
+	Opt_gid,
+	Opt_umask,
+	Opt_dmask,
+	Opt_fmask,
+	Opt_allow_utime,
+	Opt_charset,
+	Opt_utf8,
+	Opt_case_sensitive,
+	Opt_tz,
+	Opt_errors,
+	Opt_discard,
+};
+
+static const struct fs_parameter_spec exfat_param_specs[] = {
+	fsparam_u32("uid",			Opt_uid),
+	fsparam_u32("gid",			Opt_gid),
+	fsparam_u32oct("umask",			Opt_umask),
+	fsparam_u32oct("dmask",			Opt_dmask),
+	fsparam_u32oct("fmask",			Opt_fmask),
+	fsparam_u32oct("allow_utime",		Opt_allow_utime),
+	fsparam_string("iocharset",		Opt_charset),
+	fsparam_flag("utf8",			Opt_utf8),
+	fsparam_flag("case_sensitive",		Opt_case_sensitive),
+	fsparam_string("tz",			Opt_tz),
+	fsparam_enum("errors",			Opt_errors),
+	fsparam_flag("discard",			Opt_discard),
+	{}
+};
+
+static const struct fs_parameter_enum exfat_param_enums[] = {
+	{ Opt_errors,	"continue",		EXFAT_ERRORS_CONT },
+	{ Opt_errors,	"panic",		EXFAT_ERRORS_PANIC },
+	{ Opt_errors,	"remount-ro",		EXFAT_ERRORS_RO },
+	{}
+};
+
+static const struct fs_parameter_description exfat_parameters = {
+	.name		= "exfat",
+	.specs		= exfat_param_specs,
+	.enums		= exfat_param_enums,
+};
+
+static int exfat_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+	struct exfat_sb_info *sbi = fc->s_fs_info;
+	struct exfat_mount_options *opts = &sbi->options;
+	struct fs_parse_result result;
+	int opt;
+
+	opt = fs_parse(fc, &exfat_parameters, param, &result);
+	if (opt < 0)
+		return opt;
+
+	switch (opt) {
+	case Opt_uid:
+		opts->fs_uid = make_kuid(current_user_ns(), result.uint_32);
+		break;
+	case Opt_gid:
+		opts->fs_gid = make_kgid(current_user_ns(), result.uint_32);
+		break;
+	case Opt_umask:
+		opts->fs_fmask = result.uint_32;
+		opts->fs_dmask = result.uint_32;
+		break;
+	case Opt_dmask:
+		opts->fs_dmask = result.uint_32;
+		break;
+	case Opt_fmask:
+		opts->fs_fmask = result.uint_32;
+		break;
+	case Opt_allow_utime:
+		opts->allow_utime = result.uint_32 & 0022;
+		break;
+	case Opt_charset:
+		exfat_free_iocharset(sbi);
+		opts->iocharset = kstrdup(param->string, GFP_KERNEL);
+		if (!opts->iocharset)
+			return -ENOMEM;
+		break;
+	case Opt_case_sensitive:
+		opts->case_sensitive = 1;
+		break;
+	case Opt_utf8:
+		opts->utf8 = 1;
+		break;
+	case Opt_tz:
+		if (!strcmp(param->string, "UTC"))
+			opts->tz_utc = 1;
+		break;
+	case Opt_errors:
+		opts->errors = result.uint_32;
+		break;
+	case Opt_discard:
+		opts->discard = 1;
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static void exfat_hash_init(struct super_block *sb)
+{
+	struct exfat_sb_info *sbi = EXFAT_SB(sb);
+	int i;
+
+	spin_lock_init(&sbi->inode_hash_lock);
+	for (i = 0; i < EXFAT_HASH_SIZE; i++)
+		INIT_HLIST_HEAD(&sbi->inode_hashtable[i]);
+}
+
+static int exfat_read_root(struct inode *inode)
+{
+	struct super_block *sb = inode->i_sb;
+	struct exfat_sb_info *sbi = EXFAT_SB(sb);
+	struct exfat_inode_info *ei = EXFAT_I(inode);
+	struct exfat_chain cdir;
+	int num_subdirs, num_clu = 0;
+
+	exfat_chain_set(&ei->dir, sbi->root_dir, 0, ALLOC_FAT_CHAIN);
+	ei->entry = -1;
+	ei->start_clu = sbi->root_dir;
+	ei->flags = ALLOC_FAT_CHAIN;
+	ei->type = TYPE_DIR;
+	ei->version = 0;
+	ei->rwoffset = 0;
+	ei->hint_bmap.off = EXFAT_EOF_CLUSTER;
+	ei->hint_stat.eidx = 0;
+	ei->hint_stat.clu = sbi->root_dir;
+	ei->hint_femp.eidx = EXFAT_HINT_NONE;
+
+	exfat_chain_set(&cdir, sbi->root_dir, 0, ALLOC_FAT_CHAIN);
+	if (exfat_count_num_clusters(sb, &cdir, &num_clu))
+		return -EIO;
+	i_size_write(inode, num_clu << sbi->cluster_size_bits);
+
+	num_subdirs = exfat_count_dir_entries(sb, &cdir);
+	if (num_subdirs < 0)
+		return -EIO;
+	set_nlink(inode, num_subdirs + EXFAT_MIN_SUBDIR);
+
+	inode->i_uid = sbi->options.fs_uid;
+	inode->i_gid = sbi->options.fs_gid;
+	inode_inc_iversion(inode);
+	inode->i_generation = 0;
+	inode->i_mode = exfat_make_mode(sbi, ATTR_SUBDIR, 0777);
+	inode->i_op = &exfat_dir_inode_operations;
+	inode->i_fop = &exfat_dir_operations;
+
+	inode->i_blocks = ((i_size_read(inode) + (sbi->cluster_size - 1))
+			& ~(sbi->cluster_size - 1)) >> inode->i_blkbits;
+	EXFAT_I(inode)->i_pos = ((loff_t)sbi->root_dir << 32) | 0xffffffff;
+	EXFAT_I(inode)->i_size_aligned = i_size_read(inode);
+	EXFAT_I(inode)->i_size_ondisk = i_size_read(inode);
+
+	exfat_save_attr(inode, ATTR_SUBDIR);
+	inode->i_mtime = inode->i_atime = inode->i_ctime = current_time(inode);
+	exfat_cache_init_inode(inode);
+	return 0;
+}
+
+static bool is_exfat(struct pbr *pbr)
+{
+	int i = MUST_BE_ZERO_LEN;
+
+	do {
+		if (pbr->bpb.f64.res_zero[i - 1])
+			break;
+	} while (--i);
+	return i ? false : true;
+}
+
+static struct pbr *exfat_read_pbr_with_logical_sector(struct super_block *sb,
+		struct buffer_head **prev_bh)
+{
+	struct pbr *p_pbr = (struct pbr *) (*prev_bh)->b_data;
+	unsigned short logical_sect = 0;
+
+	logical_sect = 1 << p_pbr->bsx.f64.sect_size_bits;
+
+	if (!is_power_of_2(logical_sect) ||
+	    logical_sect < 512 || logical_sect > 4096) {
+		exfat_msg(sb, KERN_ERR, "bogus logical sector size %u",
+				logical_sect);
+		return NULL;
+	}
+
+	if (logical_sect < sb->s_blocksize) {
+		exfat_msg(sb, KERN_ERR,
+			"logical sector size too small for device (logical sector size = %u)",
+			logical_sect);
+		return NULL;
+	}
+
+	if (logical_sect > sb->s_blocksize) {
+		struct buffer_head *bh = NULL;
+
+		__brelse(*prev_bh);
+		*prev_bh = NULL;
+
+		if (!sb_set_blocksize(sb, logical_sect)) {
+			exfat_msg(sb, KERN_ERR,
+				"unable to set blocksize %u", logical_sect);
+			return NULL;
+		}
+		bh = sb_bread(sb, 0);
+		if (!bh) {
+			exfat_msg(sb, KERN_ERR,
+				"unable to read boot sector (logical sector size = %lu)",
+				sb->s_blocksize);
+			return NULL;
+		}
+
+		*prev_bh = bh;
+		p_pbr = (struct pbr *) bh->b_data;
+	}
+	return p_pbr;
+}
+
+/* mount the file system volume */
+static int __exfat_fill_super(struct super_block *sb)
+{
+	int ret;
+	struct pbr *p_pbr;
+	struct pbr64 *p_bpb;
+	struct buffer_head *bh;
+	struct exfat_sb_info *sbi = EXFAT_SB(sb);
+
+	/* set block size to read super block */
+	sb_min_blocksize(sb, 512);
+
+	/* read boot sector */
+	bh = sb_bread(sb, 0);
+	if (!bh) {
+		exfat_msg(sb, KERN_ERR, "unable to read boot sector");
+		return -EIO;
+	}
+
+	/* PRB is read */
+	p_pbr = (struct pbr *)bh->b_data;
+
+	/* check the validity of PBR */
+	if (le16_to_cpu((p_pbr->signature)) != PBR_SIGNATURE) {
+		exfat_msg(sb, KERN_ERR, "invalid boot record signature");
+		ret = -EINVAL;
+		goto free_bh;
+	}
+
+
+	/* check logical sector size */
+	p_pbr = exfat_read_pbr_with_logical_sector(sb, &bh);
+	if (!p_pbr) {
+		ret = -EIO;
+		goto free_bh;
+	}
+
+	if (!is_exfat(p_pbr)) {
+		ret = -EINVAL;
+		goto free_bh;
+	}
+
+	/* set maximum file size for exFAT */
+	sb->s_maxbytes = 0x7fffffffffffffffLL;
+
+	p_bpb = (struct pbr64 *)p_pbr;
+	if (!p_bpb->bsx.num_fats) {
+		exfat_msg(sb, KERN_ERR, "bogus number of FAT structure");
+		ret = -EINVAL;
+		goto free_bh;
+	}
+
+	sbi->sect_per_clus = 1 << p_bpb->bsx.sect_per_clus_bits;
+	sbi->sect_per_clus_bits = p_bpb->bsx.sect_per_clus_bits;
+	sbi->cluster_size_bits = sbi->sect_per_clus_bits + sb->s_blocksize_bits;
+	sbi->cluster_size = 1 << sbi->cluster_size_bits;
+	sbi->num_FAT_sectors = le32_to_cpu(p_bpb->bsx.fat_length);
+	sbi->FAT1_start_sector = le32_to_cpu(p_bpb->bsx.fat_offset);
+	sbi->FAT2_start_sector = p_bpb->bsx.num_fats == 1 ?
+		sbi->FAT1_start_sector :
+			sbi->FAT1_start_sector + sbi->num_FAT_sectors;
+	sbi->root_start_sector = le32_to_cpu(p_bpb->bsx.clu_offset);
+	sbi->data_start_sector = sbi->root_start_sector;
+	sbi->num_sectors = le64_to_cpu(p_bpb->bsx.vol_length);
+	/* because the cluster index starts with 2 */
+	sbi->num_clusters = le32_to_cpu(p_bpb->bsx.clu_count) +
+		EXFAT_RESERVED_CLUSTERS;
+
+	sbi->vol_id = le32_to_cpu(p_bpb->bsx.vol_serial);
+	sbi->root_dir = le32_to_cpu(p_bpb->bsx.root_cluster);
+	sbi->dentries_in_root = 0;
+	sbi->dentries_per_clu = 1 <<
+		(sbi->cluster_size_bits - DENTRY_SIZE_BITS);
+
+	sbi->vol_flag = le16_to_cpu(p_bpb->bsx.vol_flags);
+	sbi->clu_srch_ptr = EXFAT_FIRST_CLUSTER;
+	sbi->used_clusters = ~0u;
+
+	if (le16_to_cpu(p_bpb->bsx.vol_flags) & VOL_DIRTY) {
+		sbi->vol_flag |= VOL_DIRTY;
+		exfat_msg(sb, KERN_WARNING,
+			"Volume was not properly unmounted. Some data may be corrupt. Please run fsck.");
+	}
+
+	ret = exfat_create_upcase_table(sb);
+	if (ret) {
+		exfat_msg(sb, KERN_ERR, "failed to load upcase table");
+		goto free_bh;
+	}
+
+	/* allocate-bitmap is only for exFAT */
+	ret = exfat_load_bitmap(sb);
+	if (ret) {
+		exfat_msg(sb, KERN_ERR, "failed to load alloc-bitmap");
+		goto free_upcase_table;
+	}
+
+	ret = exfat_count_used_clusters(sb, &sbi->used_clusters);
+	if (ret) {
+		exfat_msg(sb, KERN_ERR, "failed to scan clusters");
+		goto free_alloc_bitmap;
+	}
+
+	return 0;
+
+free_alloc_bitmap:
+	exfat_free_bitmap(sb);
+free_upcase_table:
+	exfat_free_upcase_table(sb);
+free_bh:
+	brelse(bh);
+	return ret;
+}
+
+static int exfat_fill_super(struct super_block *sb, struct fs_context *fc)
+{
+	struct exfat_sb_info *sbi = sb->s_fs_info;
+	struct exfat_mount_options *opts = &sbi->options;
+	struct inode *root_inode;
+	int err;
+
+	if (opts->allow_utime == (unsigned short)-1)
+		opts->allow_utime = ~opts->fs_dmask & 0022;
+
+	if (opts->utf8 && strcmp(opts->iocharset, exfat_iocharset_with_utf8)) {
+		exfat_msg(sb, KERN_WARNING,
+			"utf8 enabled, \"iocharset=%s\" is recommended",
+			exfat_iocharset_with_utf8);
+	}
+
+	if (opts->discard) {
+		struct request_queue *q = bdev_get_queue(sb->s_bdev);
+
+		if (!blk_queue_discard(q))
+			exfat_msg(sb, KERN_WARNING,
+				"mounting with \"discard\" option, but the device does not support discard");
+		opts->discard = 0;
+	}
+
+	sb->s_flags |= SB_NODIRATIME;
+	sb->s_magic = EXFAT_SUPER_MAGIC;
+	sb->s_op = &exfat_sops;
+
+	sb->s_d_op = EXFAT_SB(sb)->options.case_sensitive ?
+			&exfat_dentry_ops : &exfat_ci_dentry_ops;
+
+	err = __exfat_fill_super(sb);
+	if (err) {
+		exfat_msg(sb, KERN_ERR, "failed to recognize exfat type");
+		goto check_nls_io;
+	}
+
+	/* set up enough so that it can read an inode */
+	exfat_hash_init(sb);
+
+	sbi->nls_io = load_nls(sbi->options.iocharset);
+	if (!sbi->nls_io) {
+		exfat_msg(sb, KERN_ERR, "IO charset %s not found",
+				sbi->options.iocharset);
+		err = -EINVAL;
+		goto free_table;
+	}
+
+	root_inode = new_inode(sb);
+	if (!root_inode) {
+		exfat_msg(sb, KERN_ERR, "failed to allocate root inode.");
+		err = -ENOMEM;
+		goto free_table;
+	}
+
+	root_inode->i_ino = EXFAT_ROOT_INO;
+	inode_set_iversion(root_inode, 1);
+	err = exfat_read_root(root_inode);
+	if (err) {
+		exfat_msg(sb, KERN_ERR, "failed to initialize root inode.");
+		goto put_inode;
+	}
+
+	exfat_hash_inode(root_inode, EXFAT_I(root_inode)->i_pos);
+	insert_inode_hash(root_inode);
+
+	sb->s_root = d_make_root(root_inode);
+	if (!sb->s_root) {
+		exfat_msg(sb, KERN_ERR, "failed to get the root dentry");
+		err = -ENOMEM;
+		goto put_inode;
+	}
+
+	return 0;
+
+put_inode:
+	iput(root_inode);
+	sb->s_root = NULL;
+
+free_table:
+	exfat_free_upcase_table(sb);
+	exfat_free_bitmap(sb);
+
+check_nls_io:
+	if (sbi->nls_io)
+		unload_nls(sbi->nls_io);
+	exfat_free_iocharset(sbi);
+	sb->s_fs_info = NULL;
+	kfree(sbi);
+	return err;
+}
+
+static int exfat_get_tree(struct fs_context *fc)
+{
+	return get_tree_bdev(fc, exfat_fill_super);
+}
+
+static void exfat_free(struct fs_context *fc)
+{
+	kfree(fc->s_fs_info);
+}
+
+static const struct fs_context_operations exfat_context_ops = {
+	.parse_param	= exfat_parse_param,
+	.get_tree	= exfat_get_tree,
+	.free		= exfat_free,
+};
+
+static int exfat_init_fs_context(struct fs_context *fc)
+{
+	struct exfat_sb_info *sbi;
+
+	sbi = kzalloc(sizeof(struct exfat_sb_info), GFP_KERNEL);
+	if (!sbi)
+		return -ENOMEM;
+
+	mutex_init(&sbi->s_lock);
+	ratelimit_state_init(&sbi->ratelimit, DEFAULT_RATELIMIT_INTERVAL,
+			DEFAULT_RATELIMIT_BURST);
+
+	sbi->options.fs_uid = current_uid();
+	sbi->options.fs_gid = current_gid();
+	sbi->options.fs_fmask = current->fs->umask;
+	sbi->options.fs_dmask = current->fs->umask;
+	sbi->options.allow_utime = -1;
+	sbi->options.iocharset = exfat_default_iocharset;
+	sbi->options.errors = EXFAT_ERRORS_RO;
+
+	fc->s_fs_info = sbi;
+	fc->ops = &exfat_context_ops;
+	return 0;
+}
+
+static struct file_system_type exfat_fs_type = {
+	.owner			= THIS_MODULE,
+	.name			= "exfat",
+	.init_fs_context	= exfat_init_fs_context,
+	.parameters		= &exfat_parameters,
+	.kill_sb		= kill_block_super,
+	.fs_flags		= FS_REQUIRES_DEV,
+};
+
+static void exfat_inode_init_once(void *foo)
+{
+	struct exfat_inode_info *ei = (struct exfat_inode_info *)foo;
+
+	INIT_HLIST_NODE(&ei->i_hash_fat);
+	inode_init_once(&ei->vfs_inode);
+}
+
+static int __init init_exfat_fs(void)
+{
+	int err;
+
+	err = exfat_cache_init();
+	if (err)
+		return err;
+
+	exfat_inode_cachep = kmem_cache_create("exfat_inode_cache",
+			sizeof(struct exfat_inode_info),
+			0, SLAB_RECLAIM_ACCOUNT | SLAB_MEM_SPREAD,
+			exfat_inode_init_once);
+	if (!exfat_inode_cachep) {
+		err = -ENOMEM;
+		goto shutdown_cache;
+	}
+
+	err = register_filesystem(&exfat_fs_type);
+	if (err)
+		goto destroy_cache;
+
+	return 0;
+
+destroy_cache:
+	kmem_cache_destroy(exfat_inode_cachep);
+shutdown_cache:
+	exfat_cache_shutdown();
+	return err;
+}
+
+static void __exit exit_exfat_fs(void)
+{
+	kmem_cache_destroy(exfat_inode_cachep);
+	unregister_filesystem(&exfat_fs_type);
+	exfat_cache_shutdown();
+}
+
+module_init(init_exfat_fs);
+module_exit(exit_exfat_fs);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("exFAT filesystem support");
+MODULE_AUTHOR("Samsung Electronics Co., Ltd.");