close_ctree(fs_info);
 }
 
+/* Store the mount options related information. */
+struct btrfs_fs_context {
+       char *subvol_name;
+       u64 subvol_objectid;
+       u64 max_inline;
+       u32 commit_interval;
+       u32 metadata_ratio;
+       u32 thread_pool_size;
+       unsigned long mount_opt;
+       unsigned long compress_type:4;
+       unsigned int compress_level;
+};
+
 enum {
        Opt_acl, Opt_noacl,
        Opt_clear_cache,
        {}
 };
 
+static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
+{
+       struct btrfs_fs_context *ctx = fc->fs_private;
+       struct fs_parse_result result;
+       int opt;
+
+       opt = fs_parse(fc, btrfs_fs_parameters, param, &result);
+       if (opt < 0)
+               return opt;
+
+       switch (opt) {
+       case Opt_degraded:
+               btrfs_set_opt(ctx->mount_opt, DEGRADED);
+               break;
+       case Opt_subvol_empty:
+               /*
+                * This exists because we used to allow it on accident, so we're
+                * keeping it to maintain ABI.  See 37becec95ac3 ("Btrfs: allow
+                * empty subvol= again").
+                */
+               break;
+       case Opt_subvol:
+               kfree(ctx->subvol_name);
+               ctx->subvol_name = kstrdup(param->string, GFP_KERNEL);
+               if (!ctx->subvol_name)
+                       return -ENOMEM;
+               break;
+       case Opt_subvolid:
+               ctx->subvol_objectid = result.uint_64;
+
+               /* subvolid=0 means give me the original fs_tree. */
+               if (!ctx->subvol_objectid)
+                       ctx->subvol_objectid = BTRFS_FS_TREE_OBJECTID;
+               break;
+       case Opt_device: {
+               struct btrfs_device *device;
+               blk_mode_t mode = sb_open_mode(fc->sb_flags);
+
+               mutex_lock(&uuid_mutex);
+               device = btrfs_scan_one_device(param->string, mode, false);
+               mutex_unlock(&uuid_mutex);
+               if (IS_ERR(device))
+                       return PTR_ERR(device);
+               break;
+       }
+       case Opt_datasum:
+               if (result.negated) {
+                       btrfs_set_opt(ctx->mount_opt, NODATASUM);
+               } else {
+                       btrfs_clear_opt(ctx->mount_opt, NODATACOW);
+                       btrfs_clear_opt(ctx->mount_opt, NODATASUM);
+               }
+               break;
+       case Opt_datacow:
+               if (result.negated) {
+                       btrfs_clear_opt(ctx->mount_opt, COMPRESS);
+                       btrfs_clear_opt(ctx->mount_opt, FORCE_COMPRESS);
+                       btrfs_set_opt(ctx->mount_opt, NODATACOW);
+                       btrfs_set_opt(ctx->mount_opt, NODATASUM);
+               } else {
+                       btrfs_clear_opt(ctx->mount_opt, NODATACOW);
+               }
+               break;
+       case Opt_compress_force:
+       case Opt_compress_force_type:
+               btrfs_set_opt(ctx->mount_opt, FORCE_COMPRESS);
+               fallthrough;
+       case Opt_compress:
+       case Opt_compress_type:
+               if (opt == Opt_compress || opt == Opt_compress_force) {
+                       ctx->compress_type = BTRFS_COMPRESS_ZLIB;
+                       ctx->compress_level = BTRFS_ZLIB_DEFAULT_LEVEL;
+                       btrfs_set_opt(ctx->mount_opt, COMPRESS);
+                       btrfs_clear_opt(ctx->mount_opt, NODATACOW);
+                       btrfs_clear_opt(ctx->mount_opt, NODATASUM);
+               } else if (strncmp(param->string, "zlib", 4) == 0) {
+                       ctx->compress_type = BTRFS_COMPRESS_ZLIB;
+                       ctx->compress_level =
+                               btrfs_compress_str2level(BTRFS_COMPRESS_ZLIB,
+                                                        param->string + 4);
+                       btrfs_set_opt(ctx->mount_opt, COMPRESS);
+                       btrfs_clear_opt(ctx->mount_opt, NODATACOW);
+                       btrfs_clear_opt(ctx->mount_opt, NODATASUM);
+               } else if (strncmp(param->string, "lzo", 3) == 0) {
+                       ctx->compress_type = BTRFS_COMPRESS_LZO;
+                       ctx->compress_level = 0;
+                       btrfs_set_opt(ctx->mount_opt, COMPRESS);
+                       btrfs_clear_opt(ctx->mount_opt, NODATACOW);
+                       btrfs_clear_opt(ctx->mount_opt, NODATASUM);
+               } else if (strncmp(param->string, "zstd", 4) == 0) {
+                       ctx->compress_type = BTRFS_COMPRESS_ZSTD;
+                       ctx->compress_level =
+                               btrfs_compress_str2level(BTRFS_COMPRESS_ZSTD,
+                                                        param->string + 4);
+                       btrfs_set_opt(ctx->mount_opt, COMPRESS);
+                       btrfs_clear_opt(ctx->mount_opt, NODATACOW);
+                       btrfs_clear_opt(ctx->mount_opt, NODATASUM);
+               } else if (strncmp(param->string, "no", 2) == 0) {
+                       ctx->compress_level = 0;
+                       ctx->compress_type = 0;
+                       btrfs_clear_opt(ctx->mount_opt, COMPRESS);
+                       btrfs_clear_opt(ctx->mount_opt, FORCE_COMPRESS);
+               } else {
+                       btrfs_err(NULL, "unrecognized compression value %s",
+                                 param->string);
+                       return -EINVAL;
+               }
+               break;
+       case Opt_ssd:
+               if (result.negated) {
+                       btrfs_set_opt(ctx->mount_opt, NOSSD);
+                       btrfs_clear_opt(ctx->mount_opt, SSD);
+                       btrfs_clear_opt(ctx->mount_opt, SSD_SPREAD);
+               } else {
+                       btrfs_set_opt(ctx->mount_opt, SSD);
+                       btrfs_clear_opt(ctx->mount_opt, NOSSD);
+               }
+               break;
+       case Opt_ssd_spread:
+               if (result.negated) {
+                       btrfs_clear_opt(ctx->mount_opt, SSD_SPREAD);
+               } else {
+                       btrfs_set_opt(ctx->mount_opt, SSD);
+                       btrfs_set_opt(ctx->mount_opt, SSD_SPREAD);
+                       btrfs_clear_opt(ctx->mount_opt, NOSSD);
+               }
+               break;
+       case Opt_barrier:
+               if (result.negated)
+                       btrfs_set_opt(ctx->mount_opt, NOBARRIER);
+               else
+                       btrfs_clear_opt(ctx->mount_opt, NOBARRIER);
+               break;
+       case Opt_thread_pool:
+               if (result.uint_32 == 0) {
+                       btrfs_err(NULL, "invalid value 0 for thread_pool");
+                       return -EINVAL;
+               }
+               ctx->thread_pool_size = result.uint_32;
+               break;
+       case Opt_max_inline:
+               ctx->max_inline = memparse(param->string, NULL);
+               break;
+       case Opt_acl:
+               if (result.negated) {
+                       fc->sb_flags &= ~SB_POSIXACL;
+               } else {
+#ifdef CONFIG_BTRFS_FS_POSIX_ACL
+                       fc->sb_flags |= SB_POSIXACL;
+#else
+                       btrfs_err(NULL, "support for ACL not compiled in");
+                       return -EINVAL;
+#endif
+               }
+               /*
+                * VFS limits the ability to toggle ACL on and off via remount,
+                * despite every file system allowing this.  This seems to be
+                * an oversight since we all do, but it'll fail if we're
+                * remounting.  So don't set the mask here, we'll check it in
+                * btrfs_reconfigure and do the toggling ourselves.
+                */
+               if (fc->purpose != FS_CONTEXT_FOR_RECONFIGURE)
+                       fc->sb_flags_mask |= SB_POSIXACL;
+               break;
+       case Opt_treelog:
+               if (result.negated)
+                       btrfs_set_opt(ctx->mount_opt, NOTREELOG);
+               else
+                       btrfs_clear_opt(ctx->mount_opt, NOTREELOG);
+               break;
+       case Opt_recovery:
+               /*
+                * -o recovery used to be an alias for usebackuproot, and then
+                * norecovery was an alias for nologreplay, hence the different
+                * behaviors for negated and not.
+                */
+               if (result.negated) {
+                       btrfs_warn(NULL,
+               "'norecovery' is deprecated, use 'rescue=nologreplay' instead");
+                       btrfs_set_opt(ctx->mount_opt, NOLOGREPLAY);
+               } else {
+                       btrfs_warn(NULL,
+               "'recovery' is deprecated, use 'rescue=usebackuproot' instead");
+                       btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT);
+               }
+               break;
+       case Opt_nologreplay:
+               btrfs_warn(NULL,
+               "'nologreplay' is deprecated, use 'rescue=nologreplay' instead");
+               btrfs_set_opt(ctx->mount_opt, NOLOGREPLAY);
+               break;
+       case Opt_flushoncommit:
+               if (result.negated)
+                       btrfs_clear_opt(ctx->mount_opt, FLUSHONCOMMIT);
+               else
+                       btrfs_set_opt(ctx->mount_opt, FLUSHONCOMMIT);
+               break;
+       case Opt_ratio:
+               ctx->metadata_ratio = result.uint_32;
+               break;
+       case Opt_discard:
+               if (result.negated) {
+                       btrfs_clear_opt(ctx->mount_opt, DISCARD_SYNC);
+                       btrfs_clear_opt(ctx->mount_opt, DISCARD_ASYNC);
+                       btrfs_set_opt(ctx->mount_opt, NODISCARD);
+               } else {
+                       btrfs_set_opt(ctx->mount_opt, DISCARD_SYNC);
+                       btrfs_clear_opt(ctx->mount_opt, DISCARD_ASYNC);
+               }
+               break;
+       case Opt_discard_mode:
+               switch (result.uint_32) {
+               case Opt_discard_sync:
+                       btrfs_clear_opt(ctx->mount_opt, DISCARD_ASYNC);
+                       btrfs_set_opt(ctx->mount_opt, DISCARD_SYNC);
+                       break;
+               case Opt_discard_async:
+                       btrfs_clear_opt(ctx->mount_opt, DISCARD_SYNC);
+                       btrfs_set_opt(ctx->mount_opt, DISCARD_ASYNC);
+                       break;
+               default:
+                       btrfs_err(NULL, "unrecognized discard mode value %s",
+                                 param->key);
+                       return -EINVAL;
+               }
+               btrfs_clear_opt(ctx->mount_opt, NODISCARD);
+               break;
+       case Opt_space_cache:
+               if (result.negated) {
+                       btrfs_set_opt(ctx->mount_opt, NOSPACECACHE);
+                       btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE);
+                       btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE);
+               } else {
+                       btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE);
+                       btrfs_set_opt(ctx->mount_opt, SPACE_CACHE);
+               }
+               break;
+       case Opt_space_cache_version:
+               switch (result.uint_32) {
+               case Opt_space_cache_v1:
+                       btrfs_set_opt(ctx->mount_opt, SPACE_CACHE);
+                       btrfs_clear_opt(ctx->mount_opt, FREE_SPACE_TREE);
+                       break;
+               case Opt_space_cache_v2:
+                       btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE);
+                       btrfs_set_opt(ctx->mount_opt, FREE_SPACE_TREE);
+                       break;
+               default:
+                       btrfs_err(NULL, "unrecognized space_cache value %s",
+                                 param->key);
+                       return -EINVAL;
+               }
+               break;
+       case Opt_rescan_uuid_tree:
+               btrfs_set_opt(ctx->mount_opt, RESCAN_UUID_TREE);
+               break;
+       case Opt_inode_cache:
+               btrfs_warn(NULL,
+                          "the 'inode_cache' option is deprecated and has no effect since 5.11");
+               break;
+       case Opt_clear_cache:
+               btrfs_set_opt(ctx->mount_opt, CLEAR_CACHE);
+               break;
+       case Opt_user_subvol_rm_allowed:
+               btrfs_set_opt(ctx->mount_opt, USER_SUBVOL_RM_ALLOWED);
+               break;
+       case Opt_enospc_debug:
+               if (result.negated)
+                       btrfs_clear_opt(ctx->mount_opt, ENOSPC_DEBUG);
+               else
+                       btrfs_set_opt(ctx->mount_opt, ENOSPC_DEBUG);
+               break;
+       case Opt_defrag:
+               if (result.negated)
+                       btrfs_clear_opt(ctx->mount_opt, AUTO_DEFRAG);
+               else
+                       btrfs_set_opt(ctx->mount_opt, AUTO_DEFRAG);
+               break;
+       case Opt_usebackuproot:
+               btrfs_warn(NULL,
+                          "'usebackuproot' is deprecated, use 'rescue=usebackuproot' instead");
+               btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT);
+               break;
+       case Opt_skip_balance:
+               btrfs_set_opt(ctx->mount_opt, SKIP_BALANCE);
+               break;
+       case Opt_fatal_errors:
+               switch (result.uint_32) {
+               case Opt_fatal_errors_panic:
+                       btrfs_set_opt(ctx->mount_opt, PANIC_ON_FATAL_ERROR);
+                       break;
+               case Opt_fatal_errors_bug:
+                       btrfs_clear_opt(ctx->mount_opt, PANIC_ON_FATAL_ERROR);
+                       break;
+               default:
+                       btrfs_err(NULL, "unrecognized fatal_errors value %s",
+                                 param->key);
+                       return -EINVAL;
+               }
+               break;
+       case Opt_commit_interval:
+               ctx->commit_interval = result.uint_32;
+               if (ctx->commit_interval == 0)
+                       ctx->commit_interval = BTRFS_DEFAULT_COMMIT_INTERVAL;
+               break;
+       case Opt_rescue:
+               switch (result.uint_32) {
+               case Opt_rescue_usebackuproot:
+                       btrfs_set_opt(ctx->mount_opt, USEBACKUPROOT);
+                       break;
+               case Opt_rescue_nologreplay:
+                       btrfs_set_opt(ctx->mount_opt, NOLOGREPLAY);
+                       break;
+               case Opt_rescue_ignorebadroots:
+                       btrfs_set_opt(ctx->mount_opt, IGNOREBADROOTS);
+                       break;
+               case Opt_rescue_ignoredatacsums:
+                       btrfs_set_opt(ctx->mount_opt, IGNOREDATACSUMS);
+                       break;
+               case Opt_rescue_parameter_all:
+                       btrfs_set_opt(ctx->mount_opt, IGNOREDATACSUMS);
+                       btrfs_set_opt(ctx->mount_opt, IGNOREBADROOTS);
+                       btrfs_set_opt(ctx->mount_opt, NOLOGREPLAY);
+                       break;
+               default:
+                       btrfs_info(NULL, "unrecognized rescue option '%s'",
+                                  param->key);
+                       return -EINVAL;
+               }
+               break;
+#ifdef CONFIG_BTRFS_DEBUG
+       case Opt_fragment:
+               switch (result.uint_32) {
+               case Opt_fragment_parameter_all:
+                       btrfs_set_opt(ctx->mount_opt, FRAGMENT_DATA);
+                       btrfs_set_opt(ctx->mount_opt, FRAGMENT_METADATA);
+                       break;
+               case Opt_fragment_parameter_metadata:
+                       btrfs_set_opt(ctx->mount_opt, FRAGMENT_METADATA);
+                       break;
+               case Opt_fragment_parameter_data:
+                       btrfs_set_opt(ctx->mount_opt, FRAGMENT_DATA);
+                       break;
+               default:
+                       btrfs_info(NULL, "unrecognized fragment option '%s'",
+                                  param->key);
+                       return -EINVAL;
+               }
+               break;
+#endif
+#ifdef CONFIG_BTRFS_FS_REF_VERIFY
+       case Opt_ref_verify:
+               btrfs_set_opt(ctx->mount_opt, REF_VERIFY);
+               break;
+#endif
+       default:
+               btrfs_err(NULL, "unrecognized mount option '%s'", param->key);
+               return -EINVAL;
+       }
+
+       return 0;
+}
+
 static bool check_ro_option(struct btrfs_fs_info *fs_info, unsigned long opt,
                            const char *opt_name)
 {
        btrfs_free_fs_info(fs_info);
 }
 
+static const struct fs_context_operations btrfs_fs_context_ops __maybe_unused = {
+       .parse_param    = btrfs_parse_param,
+};
+
 static struct file_system_type btrfs_fs_type = {
        .owner          = THIS_MODULE,
        .name           = "btrfs",