]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
Merge tag 'for-6.8-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux
authorLinus Torvalds <torvalds@linux-foundation.org>
Wed, 10 Jan 2024 17:27:40 +0000 (09:27 -0800)
committerLinus Torvalds <torvalds@linux-foundation.org>
Wed, 10 Jan 2024 17:27:40 +0000 (09:27 -0800)
Pull btrfs updates from David Sterba:
 "There are no exciting changes for users, it's been mostly API
  conversions and some fixes or refactoring.

  The mount API conversion is a base for future improvements that would
  come with VFS. Metadata processing has been converted to folios, not
  yet enabling the large folios but it's one patch away once everything
  gets tested enough.

  Core changes:

   - convert extent buffers to folios:
      - direct API conversion where possible
      - performance can drop by a few percent on metadata heavy
        workloads, the folio sizes are not constant and the calculations
        add up in the item helpers
      - both regular and subpage modes
      - data cannot be converted yet, we need to port that to iomap and
        there are some other generic changes required

   - convert mount to the new API, should not be user visible:
      - options deprecated long time ago have been removed: inode_cache,
        recovery
      - the new logic that splits mount to two phases slightly changes
        timing of device scanning for multi-device filesystems
      - LSM options will now work (like for selinux)

   - convert delayed nodes radix tree to xarray, preserving the
     preload-like logic that still allows to allocate with GFP_NOFS

   - more validation of sysfs value of scrub_speed_max

   - refactor chunk map structure, reduce size and improve performance

   - extent map refactoring, smaller data structures, improved
     performance

   - reduce size of struct extent_io_tree, embedded in several
     structures

   - temporary pages used for compression are cached and attached to a
     shrinker, this may slightly improve performance

   - in zoned mode, remove redirty extent buffer tracking, zeros are
     written in case an out-of-order is detected and proper data are
     written to the actual write pointer

   - cleanups, refactoring, error message improvements, updated tests

   - verify and update branch name or tag

   - remove unwanted text"

* tag 'for-6.8-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux: (89 commits)
  btrfs: pass btrfs_io_geometry into btrfs_max_io_len
  btrfs: pass struct btrfs_io_geometry to set_io_stripe
  btrfs: open code set_io_stripe for RAID56
  btrfs: change block mapping to switch/case in btrfs_map_block
  btrfs: factor out block mapping for single profiles
  btrfs: factor out block mapping for RAID5/6
  btrfs: reduce scope of data_stripes in btrfs_map_block
  btrfs: factor out block mapping for RAID10
  btrfs: factor out block mapping for DUP profiles
  btrfs: factor out RAID1 block mapping
  btrfs: factor out block-mapping for RAID0
  btrfs: re-introduce struct btrfs_io_geometry
  btrfs: factor out helper for single device IO check
  btrfs: migrate btrfs_repair_io_failure() to folio interfaces
  btrfs: migrate eb_bitmap_offset() to folio interfaces
  btrfs: migrate various end io functions to folios
  btrfs: migrate subpage code to folio interfaces
  btrfs: migrate get_eb_page_index() and get_eb_offset_in_page() to folios
  btrfs: don't double put our subpage reference in alloc_extent_buffer
  btrfs: cleanup metadata page pointer usage
  ...

1  2 
fs/btrfs/extent_io.c
fs/btrfs/inode.c
fs/btrfs/subpage.c
fs/btrfs/super.c
fs/namespace.c

index b6ff6f320198c50853a791953b1247f28073c0e2,a0ffd41c5cc19fc8101fe21bfed31affa15955fc..cfd2967f04a293cf3d38956e9e21ce9e6656b498
@@@ -874,14 -908,14 +908,14 @@@ static int attach_extent_buffer_folio(s
         * For cloned or dummy extent buffers, their pages are not mapped and
         * will not race with any other ebs.
         */
-       if (page->mapping)
-               lockdep_assert_held(&page->mapping->i_private_lock);
+       if (folio->mapping)
 -              lockdep_assert_held(&folio->mapping->private_lock);
++              lockdep_assert_held(&folio->mapping->i_private_lock);
  
        if (fs_info->nodesize >= PAGE_SIZE) {
-               if (!PagePrivate(page))
-                       attach_page_private(page, eb);
+               if (!folio_test_private(folio))
+                       folio_attach_private(folio, eb);
                else
-                       WARN_ON(page->private != (unsigned long)eb);
+                       WARN_ON(folio_get_private(folio) != eb);
                return 0;
        }
  
@@@ -1741,9 -1784,9 +1784,9 @@@ static int submit_eb_subpage(struct pag
                 * Take private lock to ensure the subpage won't be detached
                 * in the meantime.
                 */
 -              spin_lock(&page->mapping->private_lock);
 +              spin_lock(&page->mapping->i_private_lock);
-               if (!PagePrivate(page)) {
+               if (!folio_test_private(folio)) {
 -                      spin_unlock(&page->mapping->private_lock);
 +                      spin_unlock(&page->mapping->i_private_lock);
                        break;
                }
                spin_lock_irqsave(&subpage->lock, flags);
@@@ -1816,9 -1860,9 +1860,9 @@@ static int submit_eb_page(struct page *
        if (btrfs_sb(page->mapping->host->i_sb)->nodesize < PAGE_SIZE)
                return submit_eb_subpage(page, wbc);
  
 -      spin_lock(&mapping->private_lock);
 +      spin_lock(&mapping->i_private_lock);
-       if (!PagePrivate(page)) {
+       if (!folio_test_private(folio)) {
 -              spin_unlock(&mapping->private_lock);
 +              spin_unlock(&mapping->i_private_lock);
                return 0;
        }
  
@@@ -3062,10 -3106,10 +3106,10 @@@ static bool folio_range_has_eb(struct b
  {
        struct btrfs_subpage *subpage;
  
-       lockdep_assert_held(&page->mapping->i_private_lock);
 -      lockdep_assert_held(&folio->mapping->private_lock);
++      lockdep_assert_held(&folio->mapping->i_private_lock);
  
-       if (PagePrivate(page)) {
-               subpage = (struct btrfs_subpage *)page->private;
+       if (folio_test_private(folio)) {
+               subpage = folio_get_private(folio);
                if (atomic_read(&subpage->eb_refs))
                        return true;
                /*
@@@ -3084,15 -3128,15 +3128,15 @@@ static void detach_extent_buffer_folio(
        const bool mapped = !test_bit(EXTENT_BUFFER_UNMAPPED, &eb->bflags);
  
        /*
-        * For mapped eb, we're going to change the page private, which should
+        * For mapped eb, we're going to change the folio private, which should
 -       * be done under the private_lock.
 +       * be done under the i_private_lock.
         */
        if (mapped)
-               spin_lock(&page->mapping->i_private_lock);
 -              spin_lock(&folio->mapping->private_lock);
++              spin_lock(&folio->mapping->i_private_lock);
  
-       if (!PagePrivate(page)) {
+       if (!folio_test_private(folio)) {
                if (mapped)
-                       spin_unlock(&page->mapping->i_private_lock);
 -                      spin_unlock(&folio->mapping->private_lock);
++                      spin_unlock(&folio->mapping->i_private_lock);
                return;
        }
  
                 * We do this since we'll remove the pages after we've
                 * removed the eb from the radix tree, so we could race
                 * and have this page now attached to the new eb.  So
-                * only clear page_private if it's still connected to
+                * only clear folio if it's still connected to
                 * this eb.
                 */
-               if (PagePrivate(page) &&
-                   page->private == (unsigned long)eb) {
+               if (folio_test_private(folio) && folio_get_private(folio) == eb) {
                        BUG_ON(test_bit(EXTENT_BUFFER_DIRTY, &eb->bflags));
-                       BUG_ON(PageDirty(page));
-                       BUG_ON(PageWriteback(page));
-                       /*
-                        * We need to make sure we haven't be attached
-                        * to a new eb.
-                        */
-                       detach_page_private(page);
+                       BUG_ON(folio_test_dirty(folio));
+                       BUG_ON(folio_test_writeback(folio));
+                       /* We need to make sure we haven't be attached to a new eb. */
+                       folio_detach_private(folio);
                }
                if (mapped)
-                       spin_unlock(&page->mapping->i_private_lock);
 -                      spin_unlock(&folio->mapping->private_lock);
++                      spin_unlock(&folio->mapping->i_private_lock);
                return;
        }
  
                return;
        }
  
-       btrfs_page_dec_eb_refs(fs_info, page);
+       btrfs_folio_dec_eb_refs(fs_info, folio);
  
        /*
-        * We can only detach the page private if there are no other ebs in the
+        * We can only detach the folio private if there are no other ebs in the
         * page range and no unfinished IO.
         */
-       if (!page_range_has_eb(fs_info, page))
-               btrfs_detach_subpage(fs_info, page);
+       if (!folio_range_has_eb(fs_info, folio))
+               btrfs_detach_subpage(fs_info, folio);
  
-       spin_unlock(&page->mapping->i_private_lock);
 -      spin_unlock(&folio->mapping->private_lock);
++      spin_unlock(&folio->mapping->i_private_lock);
  }
  
  /* Release all pages attached to the extent buffer */
@@@ -3516,11 -3612,9 +3612,9 @@@ struct extent_buffer *alloc_extent_buff
  
        btrfs_set_buffer_lockdep_class(lockdep_owner, eb, level);
  
-       num_pages = num_extent_pages(eb);
        /*
-        * Preallocate page->private for subpage case, so that we won't
+        * Preallocate folio private for subpage case, so that we won't
 -       * allocate memory with private_lock nor page lock hold.
 +       * allocate memory with i_private_lock nor page lock hold.
         *
         * The memory will be freed by attach_extent_buffer_page() or freed
         * manually if we exit earlier.
                }
        }
  
-       for (i = 0; i < num_pages; i++, index++) {
-               p = find_or_create_page(mapping, index, GFP_NOFS|__GFP_NOFAIL);
-               if (!p) {
-                       exists = ERR_PTR(-ENOMEM);
-                       btrfs_free_subpage(prealloc);
-                       goto free_eb;
+ reallocate:
+       /* Allocate all pages first. */
+       ret = alloc_eb_folio_array(eb, __GFP_NOFAIL);
+       if (ret < 0) {
+               btrfs_free_subpage(prealloc);
+               goto out;
+       }
+       num_folios = num_extent_folios(eb);
+       /* Attach all pages to the filemap. */
+       for (int i = 0; i < num_folios; i++) {
+               struct folio *folio;
+               ret = attach_eb_folio_to_filemap(eb, i, &existing_eb);
+               if (ret > 0) {
+                       ASSERT(existing_eb);
+                       goto out;
                }
  
-               spin_lock(&mapping->i_private_lock);
-               exists = grab_extent_buffer(fs_info, p);
-               if (exists) {
-                       spin_unlock(&mapping->i_private_lock);
-                       unlock_page(p);
-                       put_page(p);
-                       mark_extent_buffer_accessed(exists, p);
-                       btrfs_free_subpage(prealloc);
-                       goto free_eb;
+               /*
+                * TODO: Special handling for a corner case where the order of
+                * folios mismatch between the new eb and filemap.
+                *
+                * This happens when:
+                *
+                * - the new eb is using higher order folio
+                *
+                * - the filemap is still using 0-order folios for the range
+                *   This can happen at the previous eb allocation, and we don't
+                *   have higher order folio for the call.
+                *
+                * - the existing eb has already been freed
+                *
+                * In this case, we have to free the existing folios first, and
+                * re-allocate using the same order.
+                * Thankfully this is not going to happen yet, as we're still
+                * using 0-order folios.
+                */
+               if (unlikely(ret == -EAGAIN)) {
+                       ASSERT(0);
+                       goto reallocate;
                }
 -              spin_lock(&mapping->private_lock);
+               attached++;
+               /*
+                * Only after attach_eb_folio_to_filemap(), eb->folios[] is
+                * reliable, as we may choose to reuse the existing page cache
+                * and free the allocated page.
+                */
+               folio = eb->folios[i];
++              spin_lock(&mapping->i_private_lock);
                /* Should not fail, as we have preallocated the memory */
-               ret = attach_extent_buffer_page(eb, p, prealloc);
+               ret = attach_extent_buffer_folio(eb, folio, prealloc);
                ASSERT(!ret);
                /*
                 * To inform we have extra eb under allocation, so that
                 * detach_extent_buffer_page().
                 * Thus needs no special handling in error path.
                 */
-               btrfs_page_inc_eb_refs(fs_info, p);
+               btrfs_folio_inc_eb_refs(fs_info, folio);
 -              spin_unlock(&mapping->private_lock);
 +              spin_unlock(&mapping->i_private_lock);
  
-               WARN_ON(btrfs_page_test_dirty(fs_info, p, eb->start, eb->len));
-               eb->pages[i] = p;
-               if (!btrfs_page_test_uptodate(fs_info, p, eb->start, eb->len))
+               WARN_ON(btrfs_folio_test_dirty(fs_info, folio, eb->start, eb->len));
+               /*
+                * Check if the current page is physically contiguous with previous eb
+                * page.
+                * At this stage, either we allocated a large folio, thus @i
+                * would only be 0, or we fall back to per-page allocation.
+                */
+               if (i && folio_page(eb->folios[i - 1], 0) + 1 != folio_page(folio, 0))
+                       page_contig = false;
+               if (!btrfs_folio_test_uptodate(fs_info, folio, eb->start, eb->len))
                        uptodate = 0;
  
                /*
@@@ -4566,11 -4780,11 +4780,11 @@@ static int try_release_subpage_extent_b
                release_extent_buffer(eb);
        }
        /*
-        * Finally to check if we have cleared page private, as if we have
-        * released all ebs in the page, the page private should be cleared now.
+        * Finally to check if we have cleared folio private, as if we have
+        * released all ebs in the page, the folio private should be cleared now.
         */
 -      spin_lock(&page->mapping->private_lock);
 +      spin_lock(&page->mapping->i_private_lock);
-       if (!PagePrivate(page))
+       if (!folio_test_private(page_folio(page)))
                ret = 1;
        else
                ret = 0;
@@@ -4587,12 -4802,12 +4802,12 @@@ int try_release_extent_buffer(struct pa
                return try_release_subpage_extent_buffer(page);
  
        /*
-        * We need to make sure nobody is changing page->private, as we rely on
-        * page->private as the pointer to extent buffer.
+        * We need to make sure nobody is changing folio private, as we rely on
+        * folio private as the pointer to extent buffer.
         */
 -      spin_lock(&page->mapping->private_lock);
 +      spin_lock(&page->mapping->i_private_lock);
-       if (!PagePrivate(page)) {
+       if (!folio_test_private(folio)) {
 -              spin_unlock(&page->mapping->private_lock);
 +              spin_unlock(&page->mapping->i_private_lock);
                return 1;
        }
  
Simple merge
index 2347cf15278be8e3790e6cb757a5a9ea18455c80,d9a30b93d543befb3e5e8b877873abab1ef80e00..93511d54abf8280bc6778a17b5fa75a28d3585c1
@@@ -196,13 -193,13 +193,13 @@@ void btrfs_folio_inc_eb_refs(const stru
  {
        struct btrfs_subpage *subpage;
  
-       if (!btrfs_is_subpage(fs_info, page))
+       if (!btrfs_is_subpage(fs_info, folio->mapping))
                return;
  
-       ASSERT(PagePrivate(page) && page->mapping);
-       lockdep_assert_held(&page->mapping->i_private_lock);
+       ASSERT(folio_test_private(folio) && folio->mapping);
 -      lockdep_assert_held(&folio->mapping->private_lock);
++      lockdep_assert_held(&folio->mapping->i_private_lock);
  
-       subpage = (struct btrfs_subpage *)page->private;
+       subpage = folio_get_private(folio);
        atomic_inc(&subpage->eb_refs);
  }
  
@@@ -211,13 -207,13 +207,13 @@@ void btrfs_folio_dec_eb_refs(const stru
  {
        struct btrfs_subpage *subpage;
  
-       if (!btrfs_is_subpage(fs_info, page))
+       if (!btrfs_is_subpage(fs_info, folio->mapping))
                return;
  
-       ASSERT(PagePrivate(page) && page->mapping);
-       lockdep_assert_held(&page->mapping->i_private_lock);
+       ASSERT(folio_test_private(folio) && folio->mapping);
 -      lockdep_assert_held(&folio->mapping->private_lock);
++      lockdep_assert_held(&folio->mapping->i_private_lock);
  
-       subpage = (struct btrfs_subpage *)page->private;
+       subpage = folio_get_private(folio);
        ASSERT(atomic_read(&subpage->eb_refs));
        atomic_dec(&subpage->eb_refs);
  }
index 2a3a5bf102dc693ac7142c0da5c36e90291add4e,3a677b808f0f75365fead71b438f1783a8ab9f13..896acfda17895150ff501960dd72f084c542301e
@@@ -143,670 -141,468 +141,474 @@@ enum 
        Opt_err,
  };
  
- static const match_table_t tokens = {
-       {Opt_acl, "acl"},
-       {Opt_noacl, "noacl"},
-       {Opt_clear_cache, "clear_cache"},
-       {Opt_commit_interval, "commit=%u"},
-       {Opt_compress, "compress"},
-       {Opt_compress_type, "compress=%s"},
-       {Opt_compress_force, "compress-force"},
-       {Opt_compress_force_type, "compress-force=%s"},
-       {Opt_degraded, "degraded"},
-       {Opt_device, "device=%s"},
-       {Opt_fatal_errors, "fatal_errors=%s"},
-       {Opt_flushoncommit, "flushoncommit"},
-       {Opt_noflushoncommit, "noflushoncommit"},
-       {Opt_inode_cache, "inode_cache"},
-       {Opt_noinode_cache, "noinode_cache"},
-       {Opt_max_inline, "max_inline=%s"},
-       {Opt_barrier, "barrier"},
-       {Opt_nobarrier, "nobarrier"},
-       {Opt_datacow, "datacow"},
-       {Opt_nodatacow, "nodatacow"},
-       {Opt_datasum, "datasum"},
-       {Opt_nodatasum, "nodatasum"},
-       {Opt_defrag, "autodefrag"},
-       {Opt_nodefrag, "noautodefrag"},
-       {Opt_discard, "discard"},
-       {Opt_discard_mode, "discard=%s"},
-       {Opt_nodiscard, "nodiscard"},
-       {Opt_norecovery, "norecovery"},
-       {Opt_ratio, "metadata_ratio=%u"},
-       {Opt_rescan_uuid_tree, "rescan_uuid_tree"},
-       {Opt_skip_balance, "skip_balance"},
-       {Opt_space_cache, "space_cache"},
-       {Opt_no_space_cache, "nospace_cache"},
-       {Opt_space_cache_version, "space_cache=%s"},
-       {Opt_ssd, "ssd"},
-       {Opt_nossd, "nossd"},
-       {Opt_ssd_spread, "ssd_spread"},
-       {Opt_nossd_spread, "nossd_spread"},
-       {Opt_subvol, "subvol=%s"},
-       {Opt_subvol_empty, "subvol="},
-       {Opt_subvolid, "subvolid=%s"},
-       {Opt_thread_pool, "thread_pool=%u"},
-       {Opt_treelog, "treelog"},
-       {Opt_notreelog, "notreelog"},
-       {Opt_user_subvol_rm_allowed, "user_subvol_rm_allowed"},
+ enum {
+       Opt_fatal_errors_panic,
+       Opt_fatal_errors_bug,
+ };
  
-       /* Rescue options */
-       {Opt_rescue, "rescue=%s"},
+ static const struct constant_table btrfs_parameter_fatal_errors[] = {
+       { "panic", Opt_fatal_errors_panic },
+       { "bug", Opt_fatal_errors_bug },
+       {}
+ };
+ enum {
+       Opt_discard_sync,
+       Opt_discard_async,
+ };
+ static const struct constant_table btrfs_parameter_discard[] = {
+       { "sync", Opt_discard_sync },
+       { "async", Opt_discard_async },
+       {}
+ };
+ enum {
+       Opt_space_cache_v1,
+       Opt_space_cache_v2,
+ };
+ static const struct constant_table btrfs_parameter_space_cache[] = {
+       { "v1", Opt_space_cache_v1 },
+       { "v2", Opt_space_cache_v2 },
+       {}
+ };
+ enum {
+       Opt_rescue_usebackuproot,
+       Opt_rescue_nologreplay,
+       Opt_rescue_ignorebadroots,
+       Opt_rescue_ignoredatacsums,
+       Opt_rescue_parameter_all,
+ };
+ static const struct constant_table btrfs_parameter_rescue[] = {
+       { "usebackuproot", Opt_rescue_usebackuproot },
+       { "nologreplay", Opt_rescue_nologreplay },
+       { "ignorebadroots", Opt_rescue_ignorebadroots },
+       { "ibadroots", Opt_rescue_ignorebadroots },
+       { "ignoredatacsums", Opt_rescue_ignoredatacsums },
+       { "idatacsums", Opt_rescue_ignoredatacsums },
+       { "all", Opt_rescue_parameter_all },
+       {}
+ };
+ #ifdef CONFIG_BTRFS_DEBUG
+ enum {
+       Opt_fragment_parameter_data,
+       Opt_fragment_parameter_metadata,
+       Opt_fragment_parameter_all,
+ };
+ static const struct constant_table btrfs_parameter_fragment[] = {
+       { "data", Opt_fragment_parameter_data },
+       { "metadata", Opt_fragment_parameter_metadata },
+       { "all", Opt_fragment_parameter_all },
+       {}
+ };
+ #endif
+ static const struct fs_parameter_spec btrfs_fs_parameters[] = {
+       fsparam_flag_no("acl", Opt_acl),
+       fsparam_flag_no("autodefrag", Opt_defrag),
+       fsparam_flag_no("barrier", Opt_barrier),
+       fsparam_flag("clear_cache", Opt_clear_cache),
+       fsparam_u32("commit", Opt_commit_interval),
+       fsparam_flag("compress", Opt_compress),
+       fsparam_string("compress", Opt_compress_type),
+       fsparam_flag("compress-force", Opt_compress_force),
+       fsparam_string("compress-force", Opt_compress_force_type),
+       fsparam_flag_no("datacow", Opt_datacow),
+       fsparam_flag_no("datasum", Opt_datasum),
+       fsparam_flag("degraded", Opt_degraded),
+       fsparam_string("device", Opt_device),
+       fsparam_flag_no("discard", Opt_discard),
+       fsparam_enum("discard", Opt_discard_mode, btrfs_parameter_discard),
+       fsparam_enum("fatal_errors", Opt_fatal_errors, btrfs_parameter_fatal_errors),
+       fsparam_flag_no("flushoncommit", Opt_flushoncommit),
+       fsparam_string("max_inline", Opt_max_inline),
+       fsparam_u32("metadata_ratio", Opt_ratio),
+       fsparam_flag("rescan_uuid_tree", Opt_rescan_uuid_tree),
+       fsparam_flag("skip_balance", Opt_skip_balance),
+       fsparam_flag_no("space_cache", Opt_space_cache),
+       fsparam_enum("space_cache", Opt_space_cache_version, btrfs_parameter_space_cache),
+       fsparam_flag_no("ssd", Opt_ssd),
+       fsparam_flag_no("ssd_spread", Opt_ssd_spread),
+       fsparam_string("subvol", Opt_subvol),
+       fsparam_flag("subvol=", Opt_subvol_empty),
+       fsparam_u64("subvolid", Opt_subvolid),
+       fsparam_u32("thread_pool", Opt_thread_pool),
+       fsparam_flag_no("treelog", Opt_treelog),
+       fsparam_flag("user_subvol_rm_allowed", Opt_user_subvol_rm_allowed),
+       /* Rescue options. */
+       fsparam_enum("rescue", Opt_rescue, btrfs_parameter_rescue),
        /* Deprecated, with alias rescue=nologreplay */
-       {Opt_nologreplay, "nologreplay"},
+       __fsparam(NULL, "nologreplay", Opt_nologreplay, fs_param_deprecated, NULL),
        /* Deprecated, with alias rescue=usebackuproot */
-       {Opt_usebackuproot, "usebackuproot"},
+       __fsparam(NULL, "usebackuproot", Opt_usebackuproot, fs_param_deprecated, NULL),
  
-       /* Deprecated options */
-       {Opt_recovery, "recovery"},
-       /* Debugging options */
-       {Opt_enospc_debug, "enospc_debug"},
-       {Opt_noenospc_debug, "noenospc_debug"},
+       /* Debugging options. */
+       fsparam_flag_no("enospc_debug", Opt_enospc_debug),
  #ifdef CONFIG_BTRFS_DEBUG
-       {Opt_fragment_data, "fragment=data"},
-       {Opt_fragment_metadata, "fragment=metadata"},
-       {Opt_fragment_all, "fragment=all"},
+       fsparam_enum("fragment", Opt_fragment, btrfs_parameter_fragment),
  #endif
  #ifdef CONFIG_BTRFS_FS_REF_VERIFY
-       {Opt_ref_verify, "ref_verify"},
+       fsparam_flag("ref_verify", Opt_ref_verify),
  #endif
-       {Opt_err, NULL},
+       {}
  };
  
- static const match_table_t rescue_tokens = {
-       {Opt_usebackuproot, "usebackuproot"},
-       {Opt_nologreplay, "nologreplay"},
-       {Opt_ignorebadroots, "ignorebadroots"},
-       {Opt_ignorebadroots, "ibadroots"},
-       {Opt_ignoredatacsums, "ignoredatacsums"},
-       {Opt_ignoredatacsums, "idatacsums"},
-       {Opt_rescue_all, "all"},
-       {Opt_err, NULL},
- };
- static bool check_ro_option(struct btrfs_fs_info *fs_info, unsigned long opt,
-                           const char *opt_name)
++/* No support for restricting writes to btrfs devices yet... */
++static inline blk_mode_t btrfs_open_mode(struct fs_context *fc)
 +{
-       if (fs_info->mount_opt & opt) {
-               btrfs_err(fs_info, "%s must be used with ro mount option",
-                         opt_name);
-               return true;
-       }
-       return false;
++      return sb_open_mode(fc->sb_flags) & ~BLK_OPEN_RESTRICT_WRITES;
 +}
 +
- static int parse_rescue_options(struct btrfs_fs_info *info, const char *options)
+ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
  {
-       char *opts;
-       char *orig;
-       char *p;
-       substring_t args[MAX_OPT_ARGS];
-       int ret = 0;
+       struct btrfs_fs_context *ctx = fc->fs_private;
+       struct fs_parse_result result;
+       int opt;
  
-       opts = kstrdup(options, GFP_KERNEL);
-       if (!opts)
-               return -ENOMEM;
-       orig = opts;
+       opt = fs_parse(fc, btrfs_fs_parameters, param, &result);
+       if (opt < 0)
+               return opt;
  
-       while ((p = strsep(&opts, ":")) != NULL) {
-               int token;
+       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;
  
-               if (!*p)
-                       continue;
-               token = match_token(p, rescue_tokens, args);
-               switch (token){
-               case Opt_usebackuproot:
-                       btrfs_info(info,
-                                  "trying to use backup root at mount time");
-                       btrfs_set_opt(info->mount_opt, USEBACKUPROOT);
-                       break;
-               case Opt_nologreplay:
-                       btrfs_set_and_info(info, NOLOGREPLAY,
-                                          "disabling log replay at mount time");
-                       break;
-               case Opt_ignorebadroots:
-                       btrfs_set_and_info(info, IGNOREBADROOTS,
-                                          "ignoring bad roots");
-                       break;
-               case Opt_ignoredatacsums:
-                       btrfs_set_and_info(info, IGNOREDATACSUMS,
-                                          "ignoring data csums");
-                       break;
-               case Opt_rescue_all:
-                       btrfs_info(info, "enabling all of the rescue options");
-                       btrfs_set_and_info(info, IGNOREDATACSUMS,
-                                          "ignoring data csums");
-                       btrfs_set_and_info(info, IGNOREBADROOTS,
-                                          "ignoring bad roots");
-                       btrfs_set_and_info(info, NOLOGREPLAY,
-                                          "disabling log replay at mount time");
-                       break;
-               case Opt_err:
-                       btrfs_info(info, "unrecognized rescue option '%s'", p);
-                       ret = -EINVAL;
-                       goto out;
-               default:
-                       break;
-               }
+               /* 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);
++              blk_mode_t mode = btrfs_open_mode(fc);
  
+               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;
        }
- out:
-       kfree(orig);
-       return ret;
- }
- /*
-  * Regular mount options parser.  Everything that is needed only when
-  * reading in a new superblock is parsed here.
-  * XXX JDM: This needs to be cleaned up for remount.
-  */
- int btrfs_parse_options(struct btrfs_fs_info *info, char *options,
-                       unsigned long new_flags)
- {
-       substring_t args[MAX_OPT_ARGS];
-       char *p, *num;
-       int intarg;
-       int ret = 0;
-       char *compress_type;
-       bool compress_force = false;
-       enum btrfs_compression_type saved_compress_type;
-       int saved_compress_level;
-       bool saved_compress_force;
-       int no_compress = 0;
-       const bool remounting = test_bit(BTRFS_FS_STATE_REMOUNTING, &info->fs_state);
-       if (btrfs_fs_compat_ro(info, FREE_SPACE_TREE))
-               btrfs_set_opt(info->mount_opt, FREE_SPACE_TREE);
-       else if (btrfs_free_space_cache_v1_active(info)) {
-               if (btrfs_is_zoned(info)) {
-                       btrfs_info(info,
-                       "zoned: clearing existing space cache");
-                       btrfs_set_super_cache_generation(info->super_copy, 0);
+       case Opt_datasum:
+               if (result.negated) {
+                       btrfs_set_opt(ctx->mount_opt, NODATASUM);
                } else {
-                       btrfs_set_opt(info->mount_opt, SPACE_CACHE);
+                       btrfs_clear_opt(ctx->mount_opt, NODATACOW);
+                       btrfs_clear_opt(ctx->mount_opt, NODATASUM);
                }
-       }
-       /*
-        * Even the options are empty, we still need to do extra check
-        * against new flags
-        */
-       if (!options)
-               goto check;
-       while ((p = strsep(&options, ",")) != NULL) {
-               int token;
-               if (!*p)
-                       continue;
-               token = match_token(p, tokens, args);
-               switch (token) {
-               case Opt_degraded:
-                       btrfs_info(info, "allowing degraded mounts");
-                       btrfs_set_opt(info->mount_opt, DEGRADED);
-                       break;
-               case Opt_subvol:
-               case Opt_subvol_empty:
-               case Opt_subvolid:
-               case Opt_device:
-                       /*
-                        * These are parsed by btrfs_parse_subvol_options or
-                        * btrfs_parse_device_options and can be ignored here.
-                        */
-                       break;
-               case Opt_nodatasum:
-                       btrfs_set_and_info(info, NODATASUM,
-                                          "setting nodatasum");
-                       break;
-               case Opt_datasum:
-                       if (btrfs_test_opt(info, NODATASUM)) {
-                               if (btrfs_test_opt(info, NODATACOW))
-                                       btrfs_info(info,
-                                                  "setting datasum, datacow enabled");
-                               else
-                                       btrfs_info(info, "setting datasum");
-                       }
-                       btrfs_clear_opt(info->mount_opt, NODATACOW);
-                       btrfs_clear_opt(info->mount_opt, NODATASUM);
-                       break;
-               case Opt_nodatacow:
-                       if (!btrfs_test_opt(info, NODATACOW)) {
-                               if (!btrfs_test_opt(info, COMPRESS) ||
-                                   !btrfs_test_opt(info, FORCE_COMPRESS)) {
-                                       btrfs_info(info,
-                                                  "setting nodatacow, compression disabled");
-                               } else {
-                                       btrfs_info(info, "setting nodatacow");
-                               }
-                       }
-                       btrfs_clear_opt(info->mount_opt, COMPRESS);
-                       btrfs_clear_opt(info->mount_opt, FORCE_COMPRESS);
-                       btrfs_set_opt(info->mount_opt, NODATACOW);
-                       btrfs_set_opt(info->mount_opt, NODATASUM);
-                       break;
-               case Opt_datacow:
-                       btrfs_clear_and_info(info, NODATACOW,
-                                            "setting datacow");
-                       break;
-               case Opt_compress_force:
-               case Opt_compress_force_type:
-                       compress_force = true;
-                       fallthrough;
-               case Opt_compress:
-               case Opt_compress_type:
-                       saved_compress_type = btrfs_test_opt(info,
-                                                            COMPRESS) ?
-                               info->compress_type : BTRFS_COMPRESS_NONE;
-                       saved_compress_force =
-                               btrfs_test_opt(info, FORCE_COMPRESS);
-                       saved_compress_level = info->compress_level;
-                       if (token == Opt_compress ||
-                           token == Opt_compress_force ||
-                           strncmp(args[0].from, "zlib", 4) == 0) {
-                               compress_type = "zlib";
-                               info->compress_type = BTRFS_COMPRESS_ZLIB;
-                               info->compress_level = BTRFS_ZLIB_DEFAULT_LEVEL;
-                               /*
-                                * args[0] contains uninitialized data since
-                                * for these tokens we don't expect any
-                                * parameter.
-                                */
-                               if (token != Opt_compress &&
-                                   token != Opt_compress_force)
-                                       info->compress_level =
-                                         btrfs_compress_str2level(
-                                                       BTRFS_COMPRESS_ZLIB,
-                                                       args[0].from + 4);
-                               btrfs_set_opt(info->mount_opt, COMPRESS);
-                               btrfs_clear_opt(info->mount_opt, NODATACOW);
-                               btrfs_clear_opt(info->mount_opt, NODATASUM);
-                               no_compress = 0;
-                       } else if (strncmp(args[0].from, "lzo", 3) == 0) {
-                               compress_type = "lzo";
-                               info->compress_type = BTRFS_COMPRESS_LZO;
-                               info->compress_level = 0;
-                               btrfs_set_opt(info->mount_opt, COMPRESS);
-                               btrfs_clear_opt(info->mount_opt, NODATACOW);
-                               btrfs_clear_opt(info->mount_opt, NODATASUM);
-                               btrfs_set_fs_incompat(info, COMPRESS_LZO);
-                               no_compress = 0;
-                       } else if (strncmp(args[0].from, "zstd", 4) == 0) {
-                               compress_type = "zstd";
-                               info->compress_type = BTRFS_COMPRESS_ZSTD;
-                               info->compress_level =
-                                       btrfs_compress_str2level(
-                                                        BTRFS_COMPRESS_ZSTD,
-                                                        args[0].from + 4);
-                               btrfs_set_opt(info->mount_opt, COMPRESS);
-                               btrfs_clear_opt(info->mount_opt, NODATACOW);
-                               btrfs_clear_opt(info->mount_opt, NODATASUM);
-                               btrfs_set_fs_incompat(info, COMPRESS_ZSTD);
-                               no_compress = 0;
-                       } else if (strncmp(args[0].from, "no", 2) == 0) {
-                               compress_type = "no";
-                               info->compress_level = 0;
-                               info->compress_type = 0;
-                               btrfs_clear_opt(info->mount_opt, COMPRESS);
-                               btrfs_clear_opt(info->mount_opt, FORCE_COMPRESS);
-                               compress_force = false;
-                               no_compress++;
-                       } else {
-                               btrfs_err(info, "unrecognized compression value %s",
-                                         args[0].from);
-                               ret = -EINVAL;
-                               goto out;
-                       }
-                       if (compress_force) {
-                               btrfs_set_opt(info->mount_opt, FORCE_COMPRESS);
-                       } else {
-                               /*
-                                * If we remount from compress-force=xxx to
-                                * compress=xxx, we need clear FORCE_COMPRESS
-                                * flag, otherwise, there is no way for users
-                                * to disable forcible compression separately.
-                                */
-                               btrfs_clear_opt(info->mount_opt, FORCE_COMPRESS);
-                       }
-                       if (no_compress == 1) {
-                               btrfs_info(info, "use no compression");
-                       } else if ((info->compress_type != saved_compress_type) ||
-                                  (compress_force != saved_compress_force) ||
-                                  (info->compress_level != saved_compress_level)) {
-                               btrfs_info(info, "%s %s compression, level %d",
-                                          (compress_force) ? "force" : "use",
-                                          compress_type, info->compress_level);
-                       }
-                       compress_force = false;
-                       break;
-               case Opt_ssd:
-                       btrfs_set_and_info(info, SSD,
-                                          "enabling ssd optimizations");
-                       btrfs_clear_opt(info->mount_opt, NOSSD);
-                       break;
-               case Opt_ssd_spread:
-                       btrfs_set_and_info(info, SSD,
-                                          "enabling ssd optimizations");
-                       btrfs_set_and_info(info, SSD_SPREAD,
-                                          "using spread ssd allocation scheme");
-                       btrfs_clear_opt(info->mount_opt, NOSSD);
-                       break;
-               case Opt_nossd:
-                       btrfs_set_opt(info->mount_opt, NOSSD);
-                       btrfs_clear_and_info(info, SSD,
-                                            "not using ssd optimizations");
-                       fallthrough;
-               case Opt_nossd_spread:
-                       btrfs_clear_and_info(info, SSD_SPREAD,
-                                            "not using spread ssd allocation scheme");
-                       break;
-               case Opt_barrier:
-                       btrfs_clear_and_info(info, NOBARRIER,
-                                            "turning on barriers");
-                       break;
-               case Opt_nobarrier:
-                       btrfs_set_and_info(info, NOBARRIER,
-                                          "turning off barriers");
-                       break;
-               case Opt_thread_pool:
-                       ret = match_int(&args[0], &intarg);
-                       if (ret) {
-                               btrfs_err(info, "unrecognized thread_pool value %s",
-                                         args[0].from);
-                               goto out;
-                       } else if (intarg == 0) {
-                               btrfs_err(info, "invalid value 0 for thread_pool");
-                               ret = -EINVAL;
-                               goto out;
-                       }
-                       info->thread_pool_size = intarg;
-                       break;
-               case Opt_max_inline:
-                       num = match_strdup(&args[0]);
-                       if (num) {
-                               info->max_inline = memparse(num, NULL);
-                               kfree(num);
-                               if (info->max_inline) {
-                                       info->max_inline = min_t(u64,
-                                               info->max_inline,
-                                               info->sectorsize);
-                               }
-                               btrfs_info(info, "max_inline at %llu",
-                                          info->max_inline);
-                       } else {
-                               ret = -ENOMEM;
-                               goto out;
-                       }
-                       break;
-               case Opt_acl:
+               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
-                       info->sb->s_flags |= SB_POSIXACL;
-                       break;
+                       fc->sb_flags |= SB_POSIXACL;
  #else
-                       btrfs_err(info, "support for ACL not compiled in!");
-                       ret = -EINVAL;
-                       goto out;
+                       btrfs_err(NULL, "support for ACL not compiled in");
+                       return -EINVAL;
  #endif
-               case Opt_noacl:
-                       info->sb->s_flags &= ~SB_POSIXACL;
-                       break;
-               case Opt_notreelog:
-                       btrfs_set_and_info(info, NOTREELOG,
-                                          "disabling tree log");
-                       break;
-               case Opt_treelog:
-                       btrfs_clear_and_info(info, NOTREELOG,
-                                            "enabling tree log");
-                       break;
-               case Opt_norecovery:
-               case Opt_nologreplay:
-                       btrfs_warn(info,
+               }
+               /*
+                * 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_nologreplay:
+               btrfs_warn(NULL,
                "'nologreplay' is deprecated, use 'rescue=nologreplay' instead");
-                       btrfs_set_and_info(info, NOLOGREPLAY,
-                                          "disabling log replay at mount time");
-                       break;
-               case Opt_flushoncommit:
-                       btrfs_set_and_info(info, FLUSHONCOMMIT,
-                                          "turning on flush-on-commit");
-                       break;
-               case Opt_noflushoncommit:
-                       btrfs_clear_and_info(info, FLUSHONCOMMIT,
-                                            "turning off flush-on-commit");
-                       break;
-               case Opt_ratio:
-                       ret = match_int(&args[0], &intarg);
-                       if (ret) {
-                               btrfs_err(info, "unrecognized metadata_ratio value %s",
-                                         args[0].from);
-                               goto out;
-                       }
-                       info->metadata_ratio = intarg;
-                       btrfs_info(info, "metadata ratio %u",
-                                  info->metadata_ratio);
-                       break;
-               case Opt_discard:
-               case Opt_discard_mode:
-                       if (token == Opt_discard ||
-                           strcmp(args[0].from, "sync") == 0) {
-                               btrfs_clear_opt(info->mount_opt, DISCARD_ASYNC);
-                               btrfs_set_and_info(info, DISCARD_SYNC,
-                                                  "turning on sync discard");
-                       } else if (strcmp(args[0].from, "async") == 0) {
-                               btrfs_clear_opt(info->mount_opt, DISCARD_SYNC);
-                               btrfs_set_and_info(info, DISCARD_ASYNC,
-                                                  "turning on async discard");
-                       } else {
-                               btrfs_err(info, "unrecognized discard mode value %s",
-                                         args[0].from);
-                               ret = -EINVAL;
-                               goto out;
-                       }
-                       btrfs_clear_opt(info->mount_opt, NODISCARD);
-                       break;
-               case Opt_nodiscard:
-                       btrfs_clear_and_info(info, DISCARD_SYNC,
-                                            "turning off discard");
-                       btrfs_clear_and_info(info, DISCARD_ASYNC,
-                                            "turning off async discard");
-                       btrfs_set_opt(info->mount_opt, NODISCARD);
-                       break;
-               case Opt_space_cache:
-               case Opt_space_cache_version:
-                       /*
-                        * We already set FREE_SPACE_TREE above because we have
-                        * compat_ro(FREE_SPACE_TREE) set, and we aren't going
-                        * to allow v1 to be set for extent tree v2, simply
-                        * ignore this setting if we're extent tree v2.
-                        */
-                       if (btrfs_fs_incompat(info, EXTENT_TREE_V2))
-                               break;
-                       if (token == Opt_space_cache ||
-                           strcmp(args[0].from, "v1") == 0) {
-                               btrfs_clear_opt(info->mount_opt,
-                                               FREE_SPACE_TREE);
-                               btrfs_set_and_info(info, SPACE_CACHE,
-                                          "enabling disk space caching");
-                       } else if (strcmp(args[0].from, "v2") == 0) {
-                               btrfs_clear_opt(info->mount_opt,
-                                               SPACE_CACHE);
-                               btrfs_set_and_info(info, FREE_SPACE_TREE,
-                                                  "enabling free space tree");
-                       } else {
-                               btrfs_err(info, "unrecognized space_cache value %s",
-                                         args[0].from);
-                               ret = -EINVAL;
-                               goto out;
-                       }
-                       break;
-               case Opt_rescan_uuid_tree:
-                       btrfs_set_opt(info->mount_opt, RESCAN_UUID_TREE);
-                       break;
-               case Opt_no_space_cache:
-                       /*
-                        * We cannot operate without the free space tree with
-                        * extent tree v2, ignore this option.
-                        */
-                       if (btrfs_fs_incompat(info, EXTENT_TREE_V2))
-                               break;
-                       if (btrfs_test_opt(info, SPACE_CACHE)) {
-                               btrfs_clear_and_info(info, SPACE_CACHE,
-                                            "disabling disk space caching");
-                       }
-                       if (btrfs_test_opt(info, FREE_SPACE_TREE)) {
-                               btrfs_clear_and_info(info, FREE_SPACE_TREE,
-                                            "disabling free space tree");
-                       }
-                       break;
-               case Opt_inode_cache:
-               case Opt_noinode_cache:
-                       btrfs_warn(info,
-       "the 'inode_cache' option is deprecated and has no effect since 5.11");
-                       break;
-               case Opt_clear_cache:
-                       /*
-                        * We cannot clear the free space tree with extent tree
-                        * v2, ignore this option.
-                        */
-                       if (btrfs_fs_incompat(info, EXTENT_TREE_V2))
-                               break;
-                       btrfs_set_and_info(info, CLEAR_CACHE,
-                                          "force clearing of disk cache");
+               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_user_subvol_rm_allowed:
-                       btrfs_set_opt(info->mount_opt, USER_SUBVOL_RM_ALLOWED);
+               case Opt_discard_async:
+                       btrfs_clear_opt(ctx->mount_opt, DISCARD_SYNC);
+                       btrfs_set_opt(ctx->mount_opt, DISCARD_ASYNC);
                        break;
-               case Opt_enospc_debug:
-                       btrfs_set_opt(info->mount_opt, ENOSPC_DEBUG);
+               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_noenospc_debug:
-                       btrfs_clear_opt(info->mount_opt, ENOSPC_DEBUG);
+               case Opt_space_cache_v2:
+                       btrfs_clear_opt(ctx->mount_opt, SPACE_CACHE);
+                       btrfs_set_opt(ctx->mount_opt, FREE_SPACE_TREE);
                        break;
-               case Opt_defrag:
-                       btrfs_set_and_info(info, AUTO_DEFRAG,
-                                          "enabling auto defrag");
+               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_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);
+               /* If we're loading the backup roots we can't trust the space cache. */
+               btrfs_set_opt(ctx->mount_opt, CLEAR_CACHE);
+               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_nodefrag:
-                       btrfs_clear_and_info(info, AUTO_DEFRAG,
-                                            "disabling auto defrag");
+               case Opt_fatal_errors_bug:
+                       btrfs_clear_opt(ctx->mount_opt, PANIC_ON_FATAL_ERROR);
                        break;
-               case Opt_recovery:
-               case Opt_usebackuproot:
-                       btrfs_warn(info,
-                       "'%s' is deprecated, use 'rescue=usebackuproot' instead",
-                                  token == Opt_recovery ? "recovery" :
-                                  "usebackuproot");
-                       btrfs_info(info,
-                                  "trying to use backup root at mount time");
-                       btrfs_set_opt(info->mount_opt, USEBACKUPROOT);
+               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_skip_balance:
-                       btrfs_set_opt(info->mount_opt, SKIP_BALANCE);
+               case Opt_rescue_nologreplay:
+                       btrfs_set_opt(ctx->mount_opt, NOLOGREPLAY);
                        break;
-               case Opt_fatal_errors:
-                       if (strcmp(args[0].from, "panic") == 0) {
-                               btrfs_set_opt(info->mount_opt,
-                                             PANIC_ON_FATAL_ERROR);
-                       } else if (strcmp(args[0].from, "bug") == 0) {
-                               btrfs_clear_opt(info->mount_opt,
-                                             PANIC_ON_FATAL_ERROR);
-                       } else {
-                               btrfs_err(info, "unrecognized fatal_errors value %s",
-                                         args[0].from);
-                               ret = -EINVAL;
-                               goto out;
-                       }
+               case Opt_rescue_ignorebadroots:
+                       btrfs_set_opt(ctx->mount_opt, IGNOREBADROOTS);
                        break;
-               case Opt_commit_interval:
-                       intarg = 0;
-                       ret = match_int(&args[0], &intarg);
-                       if (ret) {
-                               btrfs_err(info, "unrecognized commit_interval value %s",
-                                         args[0].from);
-                               ret = -EINVAL;
-                               goto out;
-                       }
-                       if (intarg == 0) {
-                               btrfs_info(info,
-                                          "using default commit interval %us",
-                                          BTRFS_DEFAULT_COMMIT_INTERVAL);
-                               intarg = BTRFS_DEFAULT_COMMIT_INTERVAL;
-                       } else if (intarg > 300) {
-                               btrfs_warn(info, "excessive commit interval %d",
-                                          intarg);
-                       }
-                       info->commit_interval = intarg;
+               case Opt_rescue_ignoredatacsums:
+                       btrfs_set_opt(ctx->mount_opt, IGNOREDATACSUMS);
                        break;
-               case Opt_rescue:
-                       ret = parse_rescue_options(info, args[0].from);
-                       if (ret < 0) {
-                               btrfs_err(info, "unrecognized rescue value %s",
-                                         args[0].from);
-                               goto out;
-                       }
+               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_all:
-                       btrfs_info(info, "fragmenting all space");
-                       btrfs_set_opt(info->mount_opt, FRAGMENT_DATA);
-                       btrfs_set_opt(info->mount_opt, FRAGMENT_METADATA);
+       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_metadata:
-                       btrfs_info(info, "fragmenting metadata");
-                       btrfs_set_opt(info->mount_opt,
-                                     FRAGMENT_METADATA);
+               case Opt_fragment_parameter_metadata:
+                       btrfs_set_opt(ctx->mount_opt, FRAGMENT_METADATA);
                        break;
-               case Opt_fragment_data:
-                       btrfs_info(info, "fragmenting data");
-                       btrfs_set_opt(info->mount_opt, FRAGMENT_DATA);
+               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_info(info, "doing ref verification");
-                       btrfs_set_opt(info->mount_opt, REF_VERIFY);
-                       break;
+       case Opt_ref_verify:
+               btrfs_set_opt(ctx->mount_opt, REF_VERIFY);
+               break;
  #endif
-               case Opt_err:
-                       btrfs_err(info, "unrecognized mount option '%s'", p);
-                       ret = -EINVAL;
-                       goto out;
-               default:
-                       break;
-               }
+       default:
+               btrfs_err(NULL, "unrecognized mount option '%s'", param->key);
+               return -EINVAL;
        }
- check:
-       /* We're read-only, don't have to check. */
-       if (new_flags & SB_RDONLY)
-               goto out;
  
-       if (check_ro_option(info, BTRFS_MOUNT_NOLOGREPLAY, "nologreplay") ||
-           check_ro_option(info, BTRFS_MOUNT_IGNOREBADROOTS, "ignorebadroots") ||
-           check_ro_option(info, BTRFS_MOUNT_IGNOREDATACSUMS, "ignoredatacsums"))
-               ret = -EINVAL;
- out:
-       if (btrfs_fs_compat_ro(info, FREE_SPACE_TREE) &&
-           !btrfs_test_opt(info, FREE_SPACE_TREE) &&
-           !btrfs_test_opt(info, CLEAR_CACHE)) {
-               btrfs_err(info, "cannot disable free space tree");
-               ret = -EINVAL;
-       }
-       if (btrfs_fs_compat_ro(info, BLOCK_GROUP_TREE) &&
-            !btrfs_test_opt(info, FREE_SPACE_TREE)) {
-               btrfs_err(info, "cannot disable free space tree with block-group-tree feature");
-               ret = -EINVAL;
-       }
-       if (!ret)
-               ret = btrfs_check_mountopts_zoned(info);
-       if (!ret && !remounting) {
-               if (btrfs_test_opt(info, SPACE_CACHE))
-                       btrfs_info(info, "disk space caching is enabled");
-               if (btrfs_test_opt(info, FREE_SPACE_TREE))
-                       btrfs_info(info, "using free space tree");
-       }
-       return ret;
+       return 0;
  }
  
  /*
@@@ -2096,6 -1770,309 +1776,309 @@@ static int btrfs_statfs(struct dentry *
        return 0;
  }
  
 -      blk_mode_t mode = sb_open_mode(fc->sb_flags);
+ static int btrfs_fc_test_super(struct super_block *sb, struct fs_context *fc)
+ {
+       struct btrfs_fs_info *p = fc->s_fs_info;
+       struct btrfs_fs_info *fs_info = btrfs_sb(sb);
+       return fs_info->fs_devices == p->fs_devices;
+ }
+ static int btrfs_get_tree_super(struct fs_context *fc)
+ {
+       struct btrfs_fs_info *fs_info = fc->s_fs_info;
+       struct btrfs_fs_context *ctx = fc->fs_private;
+       struct btrfs_fs_devices *fs_devices = NULL;
+       struct block_device *bdev;
+       struct btrfs_device *device;
+       struct super_block *sb;
++      blk_mode_t mode = btrfs_open_mode(fc);
+       int ret;
+       btrfs_ctx_to_info(fs_info, ctx);
+       mutex_lock(&uuid_mutex);
+       /*
+        * With 'true' passed to btrfs_scan_one_device() (mount time) we expect
+        * either a valid device or an error.
+        */
+       device = btrfs_scan_one_device(fc->source, mode, true);
+       ASSERT(device != NULL);
+       if (IS_ERR(device)) {
+               mutex_unlock(&uuid_mutex);
+               return PTR_ERR(device);
+       }
+       fs_devices = device->fs_devices;
+       fs_info->fs_devices = fs_devices;
+       ret = btrfs_open_devices(fs_devices, mode, &btrfs_fs_type);
+       mutex_unlock(&uuid_mutex);
+       if (ret)
+               return ret;
+       if (!(fc->sb_flags & SB_RDONLY) && fs_devices->rw_devices == 0) {
+               ret = -EACCES;
+               goto error;
+       }
+       bdev = fs_devices->latest_dev->bdev;
+       /*
+        * From now on the error handling is not straightforward.
+        *
+        * If successful, this will transfer the fs_info into the super block,
+        * and fc->s_fs_info will be NULL.  However if there's an existing
+        * super, we'll still have fc->s_fs_info populated.  If we error
+        * completely out it'll be cleaned up when we drop the fs_context,
+        * otherwise it's tied to the lifetime of the super_block.
+        */
+       sb = sget_fc(fc, btrfs_fc_test_super, set_anon_super_fc);
+       if (IS_ERR(sb)) {
+               ret = PTR_ERR(sb);
+               goto error;
+       }
+       set_device_specific_options(fs_info);
+       if (sb->s_root) {
+               btrfs_close_devices(fs_devices);
+               if ((fc->sb_flags ^ sb->s_flags) & SB_RDONLY)
+                       ret = -EBUSY;
+       } else {
+               snprintf(sb->s_id, sizeof(sb->s_id), "%pg", bdev);
+               shrinker_debugfs_rename(sb->s_shrink, "sb-btrfs:%s", sb->s_id);
+               btrfs_sb(sb)->bdev_holder = &btrfs_fs_type;
+               ret = btrfs_fill_super(sb, fs_devices, NULL);
+       }
+       if (ret) {
+               deactivate_locked_super(sb);
+               return ret;
+       }
+       btrfs_clear_oneshot_options(fs_info);
+       fc->root = dget(sb->s_root);
+       return 0;
+ error:
+       btrfs_close_devices(fs_devices);
+       return ret;
+ }
+ /*
+  * Ever since commit 0723a0473fb4 ("btrfs: allow mounting btrfs subvolumes
+  * with different ro/rw options") the following works:
+  *
+  *        (i) mount /dev/sda3 -o subvol=foo,ro /mnt/foo
+  *       (ii) mount /dev/sda3 -o subvol=bar,rw /mnt/bar
+  *
+  * which looks nice and innocent but is actually pretty intricate and deserves
+  * a long comment.
+  *
+  * On another filesystem a subvolume mount is close to something like:
+  *
+  *    (iii) # create rw superblock + initial mount
+  *          mount -t xfs /dev/sdb /opt/
+  *
+  *          # create ro bind mount
+  *          mount --bind -o ro /opt/foo /mnt/foo
+  *
+  *          # unmount initial mount
+  *          umount /opt
+  *
+  * Of course, there's some special subvolume sauce and there's the fact that the
+  * sb->s_root dentry is really swapped after mount_subtree(). But conceptually
+  * it's very close and will help us understand the issue.
+  *
+  * The old mount API didn't cleanly distinguish between a mount being made ro
+  * and a superblock being made ro.  The only way to change the ro state of
+  * either object was by passing ms_rdonly. If a new mount was created via
+  * mount(2) such as:
+  *
+  *      mount("/dev/sdb", "/mnt", "xfs", ms_rdonly, null);
+  *
+  * the MS_RDONLY flag being specified had two effects:
+  *
+  * (1) MNT_READONLY was raised -> the resulting mount got
+  *     @mnt->mnt_flags |= MNT_READONLY raised.
+  *
+  * (2) MS_RDONLY was passed to the filesystem's mount method and the filesystems
+  *     made the superblock ro. Note, how SB_RDONLY has the same value as
+  *     ms_rdonly and is raised whenever MS_RDONLY is passed through mount(2).
+  *
+  * Creating a subtree mount via (iii) ends up leaving a rw superblock with a
+  * subtree mounted ro.
+  *
+  * But consider the effect on the old mount API on btrfs subvolume mounting
+  * which combines the distinct step in (iii) into a single step.
+  *
+  * By issuing (i) both the mount and the superblock are turned ro. Now when (ii)
+  * is issued the superblock is ro and thus even if the mount created for (ii) is
+  * rw it wouldn't help. Hence, btrfs needed to transition the superblock from ro
+  * to rw for (ii) which it did using an internal remount call.
+  *
+  * IOW, subvolume mounting was inherently complicated due to the ambiguity of
+  * MS_RDONLY in mount(2). Note, this ambiguity has mount(8) always translate
+  * "ro" to MS_RDONLY. IOW, in both (i) and (ii) "ro" becomes MS_RDONLY when
+  * passed by mount(8) to mount(2).
+  *
+  * Enter the new mount API. The new mount API disambiguates making a mount ro
+  * and making a superblock ro.
+  *
+  * (3) To turn a mount ro the MOUNT_ATTR_ONLY flag can be used with either
+  *     fsmount() or mount_setattr() this is a pure VFS level change for a
+  *     specific mount or mount tree that is never seen by the filesystem itself.
+  *
+  * (4) To turn a superblock ro the "ro" flag must be used with
+  *     fsconfig(FSCONFIG_SET_FLAG, "ro"). This option is seen by the filesystem
+  *     in fc->sb_flags.
+  *
+  * This disambiguation has rather positive consequences.  Mounting a subvolume
+  * ro will not also turn the superblock ro. Only the mount for the subvolume
+  * will become ro.
+  *
+  * So, if the superblock creation request comes from the new mount API the
+  * caller must have explicitly done:
+  *
+  *      fsconfig(FSCONFIG_SET_FLAG, "ro")
+  *      fsmount/mount_setattr(MOUNT_ATTR_RDONLY)
+  *
+  * IOW, at some point the caller must have explicitly turned the whole
+  * superblock ro and we shouldn't just undo it like we did for the old mount
+  * API. In any case, it lets us avoid the hack in the new mount API.
+  *
+  * Consequently, the remounting hack must only be used for requests originating
+  * from the old mount API and should be marked for full deprecation so it can be
+  * turned off in a couple of years.
+  *
+  * The new mount API has no reason to support this hack.
+  */
+ static struct vfsmount *btrfs_reconfigure_for_mount(struct fs_context *fc)
+ {
+       struct vfsmount *mnt;
+       int ret;
+       const bool ro2rw = !(fc->sb_flags & SB_RDONLY);
+       /*
+        * We got an EBUSY because our SB_RDONLY flag didn't match the existing
+        * super block, so invert our setting here and retry the mount so we
+        * can get our vfsmount.
+        */
+       if (ro2rw)
+               fc->sb_flags |= SB_RDONLY;
+       else
+               fc->sb_flags &= ~SB_RDONLY;
+       mnt = fc_mount(fc);
+       if (IS_ERR(mnt))
+               return mnt;
+       if (!fc->oldapi || !ro2rw)
+               return mnt;
+       /* We need to convert to rw, call reconfigure. */
+       fc->sb_flags &= ~SB_RDONLY;
+       down_write(&mnt->mnt_sb->s_umount);
+       ret = btrfs_reconfigure(fc);
+       up_write(&mnt->mnt_sb->s_umount);
+       if (ret) {
+               mntput(mnt);
+               return ERR_PTR(ret);
+       }
+       return mnt;
+ }
+ static int btrfs_get_tree_subvol(struct fs_context *fc)
+ {
+       struct btrfs_fs_info *fs_info = NULL;
+       struct btrfs_fs_context *ctx = fc->fs_private;
+       struct fs_context *dup_fc;
+       struct dentry *dentry;
+       struct vfsmount *mnt;
+       /*
+        * Setup a dummy root and fs_info for test/set super.  This is because
+        * we don't actually fill this stuff out until open_ctree, but we need
+        * then open_ctree will properly initialize the file system specific
+        * settings later.  btrfs_init_fs_info initializes the static elements
+        * of the fs_info (locks and such) to make cleanup easier if we find a
+        * superblock with our given fs_devices later on at sget() time.
+        */
+       fs_info = kvzalloc(sizeof(struct btrfs_fs_info), GFP_KERNEL);
+       if (!fs_info)
+               return -ENOMEM;
+       fs_info->super_copy = kzalloc(BTRFS_SUPER_INFO_SIZE, GFP_KERNEL);
+       fs_info->super_for_commit = kzalloc(BTRFS_SUPER_INFO_SIZE, GFP_KERNEL);
+       if (!fs_info->super_copy || !fs_info->super_for_commit) {
+               btrfs_free_fs_info(fs_info);
+               return -ENOMEM;
+       }
+       btrfs_init_fs_info(fs_info);
+       dup_fc = vfs_dup_fs_context(fc);
+       if (IS_ERR(dup_fc)) {
+               btrfs_free_fs_info(fs_info);
+               return PTR_ERR(dup_fc);
+       }
+       /*
+        * When we do the sget_fc this gets transferred to the sb, so we only
+        * need to set it on the dup_fc as this is what creates the super block.
+        */
+       dup_fc->s_fs_info = fs_info;
+       /*
+        * We'll do the security settings in our btrfs_get_tree_super() mount
+        * loop, they were duplicated into dup_fc, we can drop the originals
+        * here.
+        */
+       security_free_mnt_opts(&fc->security);
+       fc->security = NULL;
+       mnt = fc_mount(dup_fc);
+       if (PTR_ERR_OR_ZERO(mnt) == -EBUSY)
+               mnt = btrfs_reconfigure_for_mount(dup_fc);
+       put_fs_context(dup_fc);
+       if (IS_ERR(mnt))
+               return PTR_ERR(mnt);
+       /*
+        * This free's ->subvol_name, because if it isn't set we have to
+        * allocate a buffer to hold the subvol_name, so we just drop our
+        * reference to it here.
+        */
+       dentry = mount_subvol(ctx->subvol_name, ctx->subvol_objectid, mnt);
+       ctx->subvol_name = NULL;
+       if (IS_ERR(dentry))
+               return PTR_ERR(dentry);
+       fc->root = dentry;
+       return 0;
+ }
+ static int btrfs_get_tree(struct fs_context *fc)
+ {
+       /*
+        * Since we use mount_subtree to mount the default/specified subvol, we
+        * have to do mounts in two steps.
+        *
+        * First pass through we call btrfs_get_tree_subvol(), this is just a
+        * wrapper around fc_mount() to call back into here again, and this time
+        * we'll call btrfs_get_tree_super().  This will do the open_ctree() and
+        * everything to open the devices and file system.  Then we return back
+        * with a fully constructed vfsmount in btrfs_get_tree_subvol(), and
+        * from there we can do our mount_subvol() call, which will lookup
+        * whichever subvol we're mounting and setup this fc with the
+        * appropriate dentry for the subvol.
+        */
+       if (fc->s_fs_info)
+               return btrfs_get_tree_super(fc);
+       return btrfs_get_tree_subvol(fc);
+ }
  static void btrfs_kill_super(struct super_block *sb)
  {
        struct btrfs_fs_info *fs_info = btrfs_sb(sb);
diff --cc fs/namespace.c
Simple merge