#include <linux/buffer_head.h>
 #include <linux/exportfs.h>
 #include <linux/fs.h>
+#include <linux/fs_context.h>
+#include <linux/fs_parser.h>
 #include <linux/iversion.h>
 #include <linux/log2.h>
 #include <linux/module.h>
 #include <linux/nls.h>
-#include <linux/parser.h>
 #include <linux/seq_file.h>
 #include <linux/statfs.h>
 
        return ret;
 }
 
-static inline void clear_mount_options(struct ntfs_mount_options *options)
+static inline void put_mount_options(struct ntfs_mount_options *options)
 {
+       kfree(options->nls_name);
        unload_nls(options->nls);
+       kfree(options);
 }
 
 enum Opt {
        Opt_err,
 };
 
-static const match_table_t ntfs_tokens = {
-       { Opt_uid, "uid=%u" },
-       { Opt_gid, "gid=%u" },
-       { Opt_umask, "umask=%o" },
-       { Opt_dmask, "dmask=%o" },
-       { Opt_fmask, "fmask=%o" },
-       { Opt_immutable, "sys_immutable" },
-       { Opt_discard, "discard" },
-       { Opt_force, "force" },
-       { Opt_sparse, "sparse" },
-       { Opt_nohidden, "nohidden" },
-       { Opt_acl, "acl" },
-       { Opt_showmeta, "showmeta" },
-       { Opt_nls, "nls=%s" },
-       { Opt_prealloc, "prealloc" },
-       { Opt_no_acs_rules, "no_acs_rules" },
-       { Opt_err, NULL },
+static const struct fs_parameter_spec ntfs_fs_parameters[] = {
+       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_flag_no("sys_immutable",        Opt_immutable),
+       fsparam_flag_no("discard",              Opt_discard),
+       fsparam_flag_no("force",                Opt_force),
+       fsparam_flag_no("sparse",               Opt_sparse),
+       fsparam_flag("nohidden",                Opt_nohidden),
+       fsparam_flag_no("acl",                  Opt_acl),
+       fsparam_flag_no("showmeta",             Opt_showmeta),
+       fsparam_string("nls",                   Opt_nls),
+       fsparam_flag_no("prealloc",             Opt_prealloc),
+       fsparam_flag("no_acs_rules",            Opt_no_acs_rules),
+       {}
 };
 
-static noinline int ntfs_parse_options(struct super_block *sb, char *options,
-                                      int silent,
-                                      struct ntfs_mount_options *opts)
+/*
+ * Load nls table or if @nls is utf8 then return NULL.
+ */
+static struct nls_table *ntfs_load_nls(char *nls)
 {
-       char *p;
-       substring_t args[MAX_OPT_ARGS];
-       int option;
-       char nls_name[30];
-       struct nls_table *nls;
+       struct nls_table *ret;
 
-       opts->fs_uid = current_uid();
-       opts->fs_gid = current_gid();
-       opts->fs_fmask_inv = opts->fs_dmask_inv = ~current_umask();
-       nls_name[0] = 0;
+       if (!nls)
+               nls = CONFIG_NLS_DEFAULT;
 
-       if (!options)
-               goto out;
+       if (strcmp(nls, "utf8") == 0)
+               return NULL;
 
-       while ((p = strsep(&options, ","))) {
-               int token;
+       if (strcmp(nls, CONFIG_NLS_DEFAULT) == 0)
+               return load_nls_default();
 
-               if (!*p)
-                       continue;
+       ret = load_nls(nls);
+       if (ret)
+               return ret;
 
-               token = match_token(p, ntfs_tokens, args);
-               switch (token) {
-               case Opt_immutable:
-                       opts->sys_immutable = 1;
-                       break;
-               case Opt_uid:
-                       if (match_int(&args[0], &option))
-                               return -EINVAL;
-                       opts->fs_uid = make_kuid(current_user_ns(), option);
-                       if (!uid_valid(opts->fs_uid))
-                               return -EINVAL;
-                       opts->uid = 1;
-                       break;
-               case Opt_gid:
-                       if (match_int(&args[0], &option))
-                               return -EINVAL;
-                       opts->fs_gid = make_kgid(current_user_ns(), option);
-                       if (!gid_valid(opts->fs_gid))
-                               return -EINVAL;
-                       opts->gid = 1;
-                       break;
-               case Opt_umask:
-                       if (match_octal(&args[0], &option))
-                               return -EINVAL;
-                       opts->fs_fmask_inv = opts->fs_dmask_inv = ~option;
-                       opts->fmask = opts->dmask = 1;
-                       break;
-               case Opt_dmask:
-                       if (match_octal(&args[0], &option))
-                               return -EINVAL;
-                       opts->fs_dmask_inv = ~option;
-                       opts->dmask = 1;
-                       break;
-               case Opt_fmask:
-                       if (match_octal(&args[0], &option))
-                               return -EINVAL;
-                       opts->fs_fmask_inv = ~option;
-                       opts->fmask = 1;
-                       break;
-               case Opt_discard:
-                       opts->discard = 1;
-                       break;
-               case Opt_force:
-                       opts->force = 1;
-                       break;
-               case Opt_sparse:
-                       opts->sparse = 1;
-                       break;
-               case Opt_nohidden:
-                       opts->nohidden = 1;
-                       break;
-               case Opt_acl:
+       return ERR_PTR(-EINVAL);
+}
+
+static int ntfs_fs_parse_param(struct fs_context *fc,
+                              struct fs_parameter *param)
+{
+       struct ntfs_mount_options *opts = fc->fs_private;
+       struct fs_parse_result result;
+       int opt;
+
+       opt = fs_parse(fc, ntfs_fs_parameters, param, &result);
+       if (opt < 0)
+               return opt;
+
+       switch (opt) {
+       case Opt_uid:
+               opts->fs_uid = make_kuid(current_user_ns(), result.uint_32);
+               if (!uid_valid(opts->fs_uid))
+                       return invalf(fc, "ntfs3: Invalid value for uid.");
+               opts->uid = 1;
+               break;
+       case Opt_gid:
+               opts->fs_gid = make_kgid(current_user_ns(), result.uint_32);
+               if (!gid_valid(opts->fs_gid))
+                       return invalf(fc, "ntfs3: Invalid value for gid.");
+               opts->gid = 1;
+               break;
+       case Opt_umask:
+               if (result.uint_32 & ~07777)
+                       return invalf(fc, "ntfs3: Invalid value for umask.");
+               opts->fs_fmask_inv = ~result.uint_32;
+               opts->fs_dmask_inv = ~result.uint_32;
+               opts->fmask = 1;
+               opts->dmask = 1;
+               break;
+       case Opt_dmask:
+               if (result.uint_32 & ~07777)
+                       return invalf(fc, "ntfs3: Invalid value for dmask.");
+               opts->fs_dmask_inv = ~result.uint_32;
+               opts->dmask = 1;
+               break;
+       case Opt_fmask:
+               if (result.uint_32 & ~07777)
+                       return invalf(fc, "ntfs3: Invalid value for fmask.");
+               opts->fs_fmask_inv = ~result.uint_32;
+               opts->fmask = 1;
+               break;
+       case Opt_immutable:
+               opts->sys_immutable = result.negated ? 0 : 1;
+               break;
+       case Opt_discard:
+               opts->discard = result.negated ? 0 : 1;
+               break;
+       case Opt_force:
+               opts->force = result.negated ? 0 : 1;
+               break;
+       case Opt_sparse:
+               opts->sparse = result.negated ? 0 : 1;
+               break;
+       case Opt_nohidden:
+               opts->nohidden = 1;
+               break;
+       case Opt_acl:
+               if (!result.negated)
 #ifdef CONFIG_NTFS3_FS_POSIX_ACL
-                       sb->s_flags |= SB_POSIXACL;
-                       break;
+                       fc->sb_flags |= SB_POSIXACL;
 #else
-                       ntfs_err(sb, "support for ACL not compiled in!");
-                       return -EINVAL;
+                       return invalf(fc, "ntfs3: Support for ACL not compiled in!");
 #endif
-               case Opt_showmeta:
-                       opts->showmeta = 1;
-                       break;
-               case Opt_nls:
-                       match_strlcpy(nls_name, &args[0], sizeof(nls_name));
-                       break;
-               case Opt_prealloc:
-                       opts->prealloc = 1;
-                       break;
-               case Opt_no_acs_rules:
-                       opts->no_acs_rules = 1;
-                       break;
-               default:
-                       if (!silent)
-                               ntfs_err(
-                                       sb,
-                                       "Unrecognized mount option \"%s\" or missing value",
-                                       p);
-                       //return -EINVAL;
-               }
-       }
-
-out:
-       if (!strcmp(nls_name[0] ? nls_name : CONFIG_NLS_DEFAULT, "utf8")) {
-               /*
-                * For UTF-8 use utf16s_to_utf8s()/utf8s_to_utf16s()
-                * instead of NLS.
-                */
-               nls = NULL;
-       } else if (nls_name[0]) {
-               nls = load_nls(nls_name);
-               if (!nls) {
-                       ntfs_err(sb, "failed to load \"%s\"", nls_name);
-                       return -EINVAL;
-               }
-       } else {
-               nls = load_nls_default();
-               if (!nls) {
-                       ntfs_err(sb, "failed to load default nls");
-                       return -EINVAL;
-               }
+               else
+                       fc->sb_flags &= ~SB_POSIXACL;
+               break;
+       case Opt_showmeta:
+               opts->showmeta = result.negated ? 0 : 1;
+               break;
+       case Opt_nls:
+               kfree(opts->nls_name);
+               opts->nls_name = param->string;
+               param->string = NULL;
+               break;
+       case Opt_prealloc:
+               opts->prealloc = result.negated ? 0 : 1;
+               break;
+       case Opt_no_acs_rules:
+               opts->no_acs_rules = 1;
+               break;
+       default:
+               /* Should not be here unless we forget add case. */
+               return -EINVAL;
        }
-       opts->nls = nls;
-
        return 0;
 }
 
-static int ntfs_remount(struct super_block *sb, int *flags, char *data)
+static int ntfs_fs_reconfigure(struct fs_context *fc)
 {
-       int err, ro_rw;
+       struct super_block *sb = fc->root->d_sb;
        struct ntfs_sb_info *sbi = sb->s_fs_info;
-       struct ntfs_mount_options old_opts;
-       char *orig_data = kstrdup(data, GFP_KERNEL);
-
-       if (data && !orig_data)
-               return -ENOMEM;
-
-       /* Store  original options. */
-       memcpy(&old_opts, sbi->options, sizeof(old_opts));
-       clear_mount_options(sbi->options);
-       memset(sbi->options, 0, sizeof(old_opts));
-
-       err = ntfs_parse_options(sb, data, 0, sbi->options);
-       if (err)
-               goto restore_opts;
+       struct ntfs_mount_options *new_opts = fc->fs_private;
+       int ro_rw;
 
-       ro_rw = sb_rdonly(sb) && !(*flags & SB_RDONLY);
+       ro_rw = sb_rdonly(sb) && !(fc->sb_flags & SB_RDONLY);
        if (ro_rw && (sbi->flags & NTFS_FLAGS_NEED_REPLAY)) {
-               ntfs_warn(
-                       sb,
-                       "Couldn't remount rw because journal is not replayed. Please umount/remount instead\n");
-               err = -EINVAL;
-               goto restore_opts;
+               errorf(fc, "ntfs3: Couldn't remount rw because journal is not replayed. Please umount/remount instead\n");
+               return -EINVAL;
+       }
+
+       new_opts->nls = ntfs_load_nls(new_opts->nls_name);
+       if (IS_ERR(new_opts->nls)) {
+               new_opts->nls = NULL;
+               errorf(fc, "ntfs3: Cannot load nls %s", new_opts->nls_name);
+               return -EINVAL;
        }
+       if (new_opts->nls != sbi->options->nls)
+               return invalf(fc, "ntfs3: Cannot use different nls when remounting!");
 
        sync_filesystem(sb);
 
        if (ro_rw && (sbi->volume.flags & VOLUME_FLAG_DIRTY) &&
-           !sbi->options->force) {
-               ntfs_warn(sb, "volume is dirty and \"force\" flag is not set!");
-               err = -EINVAL;
-               goto restore_opts;
+           !new_opts->force) {
+               errorf(fc, "ntfs3: Volume is dirty and \"force\" flag is not set!");
+               return -EINVAL;
        }
 
-       clear_mount_options(&old_opts);
-
-       ntfs_info(sb, "re-mounted. Opts: %s", orig_data);
-       err = 0;
-       goto out;
-
-restore_opts:
-       clear_mount_options(sbi->options);
-       memcpy(sbi->options, &old_opts, sizeof(old_opts));
+       memcpy(sbi->options, new_opts, sizeof(*new_opts));
 
-out:
-       kfree(orig_data);
-       return err;
+       return 0;
 }
 
 static struct kmem_cache *ntfs_inode_cachep;
        xpress_free_decompressor(sbi->compress.xpress);
        lzx_free_decompressor(sbi->compress.lzx);
 #endif
-       clear_mount_options(sbi->options);
-       kfree(sbi->options);
-
        kfree(sbi);
 }
 
        /* Mark rw ntfs as clear, if possible. */
        ntfs_set_state(sbi, NTFS_DIRTY_CLEAR);
 
+       put_mount_options(sbi->options);
        put_ntfs(sbi);
+       sb->s_fs_info = NULL;
 
        sync_blockdev(sb->s_bdev);
 }
        .statfs = ntfs_statfs,
        .show_options = ntfs_show_options,
        .sync_fs = ntfs_sync_fs,
-       .remount_fs = ntfs_remount,
        .write_inode = ntfs3_write_inode,
 };
 
 /*
  * ntfs_fill_super - Try to mount.
  */
-static int ntfs_fill_super(struct super_block *sb, void *data, int silent)
+static int ntfs_fill_super(struct super_block *sb, struct fs_context *fc)
 {
        int err;
-       struct ntfs_sb_info *sbi;
+       struct ntfs_sb_info *sbi = sb->s_fs_info;
        struct block_device *bdev = sb->s_bdev;
        struct inode *bd_inode = bdev->bd_inode;
        struct request_queue *rq = bdev_get_queue(bdev);
 
        ref.high = 0;
 
-       sbi = kzalloc(sizeof(struct ntfs_sb_info), GFP_NOFS);
-       if (!sbi)
-               return -ENOMEM;
-
-       sbi->options = kzalloc(sizeof(struct ntfs_mount_options), GFP_NOFS);
-       if (!sbi->options) {
-               kfree(sbi);
-               return -ENOMEM;
-       }
-
-       sb->s_fs_info = sbi;
        sbi->sb = sb;
        sb->s_flags |= SB_NODIRATIME;
        sb->s_magic = 0x7366746e; // "ntfs"
        ratelimit_state_init(&sbi->msg_ratelimit, DEFAULT_RATELIMIT_INTERVAL,
                             DEFAULT_RATELIMIT_BURST);
 
-       err = ntfs_parse_options(sb, data, silent, sbi->options);
-       if (err)
-               goto out;
+       sbi->options->nls = ntfs_load_nls(sbi->options->nls_name);
+       if (IS_ERR(sbi->options->nls)) {
+               sbi->options->nls = NULL;
+               errorf(fc, "Cannot load nls %s", sbi->options->nls_name);
+               return -EINVAL;
+       }
 
        if (!rq || !blk_queue_discard(rq) || !rq->limits.discard_granularity) {
                ;
                goto out;
        }
 
+       fc->fs_private = NULL;
+       fc->s_fs_info = NULL;
+
        return 0;
 
 out:
                sb->s_root = NULL;
        }
 
-       put_ntfs(sbi);
-
-       sb->s_fs_info = NULL;
        return err;
 }
 
        return err;
 }
 
-static struct dentry *ntfs_mount(struct file_system_type *fs_type, int flags,
-                                const char *dev_name, void *data)
+static int ntfs_fs_get_tree(struct fs_context *fc)
+{
+       return get_tree_bdev(fc, ntfs_fill_super);
+}
+
+/*
+ * ntfs_fs_free - Free fs_context.
+ *
+ * Note that this will be called after fill_super and reconfigure
+ * even when they pass. So they have to take pointers if they pass.
+ */
+static void ntfs_fs_free(struct fs_context *fc)
+{
+       struct ntfs_mount_options *opts = fc->fs_private;
+       struct ntfs_sb_info *sbi = fc->s_fs_info;
+
+       if (sbi)
+               put_ntfs(sbi);
+
+       if (opts)
+               put_mount_options(opts);
+}
+
+static const struct fs_context_operations ntfs_context_ops = {
+       .parse_param    = ntfs_fs_parse_param,
+       .get_tree       = ntfs_fs_get_tree,
+       .reconfigure    = ntfs_fs_reconfigure,
+       .free           = ntfs_fs_free,
+};
+
+/*
+ * ntfs_init_fs_context - Initialize spi and opts
+ *
+ * This will called when mount/remount. We will first initiliaze
+ * options so that if remount we can use just that.
+ */
+static int ntfs_init_fs_context(struct fs_context *fc)
 {
-       return mount_bdev(fs_type, flags, dev_name, data, ntfs_fill_super);
+       struct ntfs_mount_options *opts;
+       struct ntfs_sb_info *sbi;
+
+       opts = kzalloc(sizeof(struct ntfs_mount_options), GFP_NOFS);
+       if (!opts)
+               return -ENOMEM;
+
+       /* Default options. */
+       opts->fs_uid = current_uid();
+       opts->fs_gid = current_gid();
+       opts->fs_fmask_inv = ~current_umask();
+       opts->fs_dmask_inv = ~current_umask();
+
+       if (fc->purpose == FS_CONTEXT_FOR_RECONFIGURE)
+               goto ok;
+
+       sbi = kzalloc(sizeof(struct ntfs_sb_info), GFP_NOFS);
+       if (!sbi) {
+               kfree(opts);
+               return -ENOMEM;
+       }
+
+       sbi->options = opts;
+       fc->s_fs_info = sbi;
+ok:
+       fc->fs_private = opts;
+       fc->ops = &ntfs_context_ops;
+
+       return 0;
 }
 
 // clang-format off
 static struct file_system_type ntfs_fs_type = {
-       .owner          = THIS_MODULE,
-       .name           = "ntfs3",
-       .mount          = ntfs_mount,
-       .kill_sb        = kill_block_super,
-       .fs_flags       = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
+       .owner                  = THIS_MODULE,
+       .name                   = "ntfs3",
+       .init_fs_context        = ntfs_init_fs_context,
+       .parameters             = ntfs_fs_parameters,
+       .kill_sb                = kill_block_super,
+       .fs_flags               = FS_REQUIRES_DEV | FS_ALLOW_IDMAP,
 };
 // clang-format on