https://git.reactos.org/?p=reactos.git;a=commitdiff;h=29d1938258cc83423d422…
commit 29d1938258cc83423d42281d87d58ffe8cc63bc2
Author:     Johannes Obermayr <johannesobermayr(a)gmx.de>
AuthorDate: Wed Sep 28 18:08:10 2022 +0200
Commit:     GitHub <noreply(a)github.com>
CommitDate: Wed Sep 28 18:08:10 2022 +0200
    [BTRFS][UBTRFS][SHELLBTRFS] Upgrade to 1.8.1 (#4729)
    CORE-18322
    v1.8.1 (2022-08-23):
    - Fixed use-after-free when flushing
    - Fixed crash when opening volume when AppLocker installed
    - Compression now disabled for no-COW files, as on Linux
    - Flushing now scales better on very fast drives
    - Fixed small files getting padded to 4,096 bytes by lazy writer
    - Added NoDataCOW registry option
---
 dll/shellext/shellbtrfs/propsheet.cpp   |  15 ++++
 dll/shellext/shellbtrfs/shellbtrfs.rc   |   8 +-
 dll/win32/ubtrfs/ubtrfs.rc              |   8 +-
 drivers/filesystems/btrfs/btrfs.c       | 102 +++++++++++-----------
 drivers/filesystems/btrfs/btrfs.inf     |   2 +-
 drivers/filesystems/btrfs/btrfs.rc      |   8 +-
 drivers/filesystems/btrfs/btrfs_drv.h   |  16 +++-
 drivers/filesystems/btrfs/create.c      |  17 ++--
 drivers/filesystems/btrfs/fileinfo.c    |  35 ++++++--
 drivers/filesystems/btrfs/flushthread.c | 148 ++++++++++++++++++++++++++++----
 drivers/filesystems/btrfs/free-space.c  |   4 +-
 drivers/filesystems/btrfs/fsctl.c       |   4 +
 drivers/filesystems/btrfs/registry.c    |  10 ++-
 drivers/filesystems/btrfs/treefuncs.c   |  53 ++++++++----
 14 files changed, 317 insertions(+), 113 deletions(-)
diff --git a/dll/shellext/shellbtrfs/propsheet.cpp b/dll/shellext/shellbtrfs/propsheet.cpp
index f31ce2eb85f..3730038567f 100644
--- a/dll/shellext/shellbtrfs/propsheet.cpp
+++ b/dll/shellext/shellbtrfs/propsheet.cpp
@@ -597,6 +597,16 @@ void BtrfsPropSheet::change_inode_flag(HWND hDlg, uint64_t flag, UINT
state) {
         flags_set = ~flag;
     }
+    if (flags & BTRFS_INODE_NODATACOW && flags_set &
BTRFS_INODE_NODATACOW) {
+        EnableWindow(GetDlgItem(hDlg, IDC_COMPRESS), false);
+        EnableWindow(GetDlgItem(hDlg, IDC_COMPRESS_TYPE), false);
+    } else {
+        EnableWindow(GetDlgItem(hDlg, IDC_COMPRESS), true);
+        EnableWindow(GetDlgItem(hDlg, IDC_COMPRESS_TYPE), flags &
BTRFS_INODE_COMPRESS && flags_set & BTRFS_INODE_COMPRESS);
+    }
+
+    EnableWindow(GetDlgItem(hDlg, IDC_NODATACOW), !(flags & BTRFS_INODE_COMPRESS) ||
!(flags_set & BTRFS_INODE_COMPRESS));
+
     flags_changed = true;
     SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);
@@ -995,6 +1005,11 @@ void BtrfsPropSheet::init_propsheet(HWND hwndDlg) {
     set_check_box(hwndDlg, IDC_NODATACOW, min_flags & BTRFS_INODE_NODATACOW,
max_flags & BTRFS_INODE_NODATACOW);
     set_check_box(hwndDlg, IDC_COMPRESS, min_flags & BTRFS_INODE_COMPRESS, max_flags
& BTRFS_INODE_COMPRESS);
+    if (min_flags & BTRFS_INODE_NODATACOW || max_flags & BTRFS_INODE_NODATACOW)
+        EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS), false);
+    else if (min_flags & BTRFS_INODE_COMPRESS || max_flags &
BTRFS_INODE_COMPRESS)
+        EnableWindow(GetDlgItem(hwndDlg, IDC_NODATACOW), false);
+
     comptype = GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE);
     while (SendMessageW(comptype, CB_GETCOUNT, 0, 0) > 0) {
diff --git a/dll/shellext/shellbtrfs/shellbtrfs.rc b/dll/shellext/shellbtrfs/shellbtrfs.rc
index b83d7288147..5140d8d3498 100644
--- a/dll/shellext/shellbtrfs/shellbtrfs.rc
+++ b/dll/shellext/shellbtrfs/shellbtrfs.rc
@@ -61,8 +61,8 @@ IDI_ICON1               ICON                    "subvol.ico"
 //
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 1,8,0,0
- PRODUCTVERSION 1,8,0,0
+ FILEVERSION 1,8,1,0
+ PRODUCTVERSION 1,8,1,0
  FILEFLAGSMASK 0x17L
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -78,12 +78,12 @@ BEGIN
         BLOCK "080904b0"
         BEGIN
             VALUE "FileDescription", "WinBtrfs shell extension"
-            VALUE "FileVersion", "1.8.0"
+            VALUE "FileVersion", "1.8.1"
             VALUE "InternalName", "btrfs"
             VALUE "LegalCopyright", "Copyright (c) Mark Harmstone
2016-22"
             VALUE "OriginalFilename", "shellbtrfs.dll"
             VALUE "ProductName", "WinBtrfs"
-            VALUE "ProductVersion", "1.8.0"
+            VALUE "ProductVersion", "1.8.1"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/dll/win32/ubtrfs/ubtrfs.rc b/dll/win32/ubtrfs/ubtrfs.rc
index e723033ba8e..014cd9e6dba 100644
--- a/dll/win32/ubtrfs/ubtrfs.rc
+++ b/dll/win32/ubtrfs/ubtrfs.rc
@@ -51,8 +51,8 @@ END
 //
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 1,8,0,0
- PRODUCTVERSION 1,8,0,0
+ FILEVERSION 1,8,1,0
+ PRODUCTVERSION 1,8,1,0
  FILEFLAGSMASK 0x17L
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -68,12 +68,12 @@ BEGIN
         BLOCK "080904b0"
         BEGIN
             VALUE "FileDescription", "Btrfs utility DLL"
-            VALUE "FileVersion", "1.8.0"
+            VALUE "FileVersion", "1.8.1"
             VALUE "InternalName", "ubtrfs"
             VALUE "LegalCopyright", "Copyright (c) Mark Harmstone
2016-22"
             VALUE "OriginalFilename", "ubtrfs.dll"
             VALUE "ProductName", "WinBtrfs"
-            VALUE "ProductVersion", "1.8.0"
+            VALUE "ProductVersion", "1.8.1"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/drivers/filesystems/btrfs/btrfs.c b/drivers/filesystems/btrfs/btrfs.c
index 866f80a9205..f6d9ea0c196 100644
--- a/drivers/filesystems/btrfs/btrfs.c
+++ b/drivers/filesystems/btrfs/btrfs.c
@@ -83,6 +83,7 @@ uint32_t mount_clear_cache = 0;
 uint32_t mount_allow_degraded = 0;
 uint32_t mount_readonly = 0;
 uint32_t mount_no_root_dir = 0;
+uint32_t mount_nodatacow = 0;
 uint32_t no_pnp = 0;
 bool log_started = false;
 UNICODE_STRING log_device, log_file, registry_path;
@@ -645,6 +646,36 @@ static void calculate_total_space(_In_ device_extension* Vcb, _Out_
uint64_t* to
 }
 #ifndef __REACTOS__
+// simplified version of FsRtlAreNamesEqual, which can be a bottleneck!
+static bool compare_strings(const UNICODE_STRING* us1, const UNICODE_STRING* us2) {
+    if (us1->Length != us2->Length)
+        return false;
+
+    WCHAR* s1 = us1->Buffer;
+    WCHAR* s2 = us2->Buffer;
+
+    for (unsigned int i = 0; i < us1->Length; i++) {
+        WCHAR c1 = *s1;
+        WCHAR c2 = *s2;
+
+        if (c1 != c2) {
+            if (c1 >= 'a' && c1 <= 'z')
+                c1 = c1 - 'a' + 'A';
+
+            if (c2 >= 'a' && c2 <= 'z')
+                c2 = c2 - 'a' + 'A';
+
+            if (c1 != c2)
+                return false;
+        }
+
+        s1++;
+        s2++;
+    }
+
+    return true;
+}
+
 #define INIT_UNICODE_STRING(var, val) UNICODE_STRING us##var; us##var.Buffer =
(WCHAR*)val; us##var.Length = us##var.MaximumLength = sizeof(val) - sizeof(WCHAR);
 // This function exists because we have to lie about our FS type in certain situations.
@@ -707,7 +738,7 @@ static bool lie_about_fs_type() {
             name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length
- usmpr.Length) / sizeof(WCHAR)];
             name.Length = name.MaximumLength = usmpr.Length;
-            blacklist = FsRtlAreNamesEqual(&name, &usmpr, true, NULL);
+            blacklist = compare_strings(&name, &usmpr);
         }
         if (!blacklist && entry->FullDllName.Length >= uscmd.Length) {
@@ -716,7 +747,7 @@ static bool lie_about_fs_type() {
             name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length
- uscmd.Length) / sizeof(WCHAR)];
             name.Length = name.MaximumLength = uscmd.Length;
-            blacklist = FsRtlAreNamesEqual(&name, &uscmd, true, NULL);
+            blacklist = compare_strings(&name, &uscmd);
         }
         if (!blacklist && entry->FullDllName.Length >= usfsutil.Length) {
@@ -725,7 +756,7 @@ static bool lie_about_fs_type() {
             name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length
- usfsutil.Length) / sizeof(WCHAR)];
             name.Length = name.MaximumLength = usfsutil.Length;
-            blacklist = FsRtlAreNamesEqual(&name, &usfsutil, true, NULL);
+            blacklist = compare_strings(&name, &usfsutil);
         }
         if (!blacklist && entry->FullDllName.Length >= usstorsvc.Length) {
@@ -734,7 +765,7 @@ static bool lie_about_fs_type() {
             name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length
- usstorsvc.Length) / sizeof(WCHAR)];
             name.Length = name.MaximumLength = usstorsvc.Length;
-            blacklist = FsRtlAreNamesEqual(&name, &usstorsvc, true, NULL);
+            blacklist = compare_strings(&name, &usstorsvc);
         }
         if (!blacklist && entry->FullDllName.Length >= usifstest.Length) {
@@ -743,7 +774,7 @@ static bool lie_about_fs_type() {
             name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length
- usifstest.Length) / sizeof(WCHAR)];
             name.Length = name.MaximumLength = usifstest.Length;
-            blacklist = FsRtlAreNamesEqual(&name, &usifstest, true, NULL);
+            blacklist = compare_strings(&name, &usifstest);
         }
         if (blacklist) {
@@ -5943,59 +5974,32 @@ static void init_serial(bool first_time) {
 #if !defined(__REACTOS__) && (defined(_X86_) || defined(_AMD64_))
 static void check_cpu() {
     bool have_sse2 = false, have_sse42 = false, have_avx2 = false;
+    int cpu_info[4];
-#ifndef _MSC_VER
-    {
-        uint32_t eax, ebx, ecx, edx;
-
-        __cpuid(1, eax, ebx, ecx, edx);
-
-        if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
-            have_sse42 = ecx & bit_SSE4_2;
-            have_sse2 = edx & bit_SSE2;
-        }
-
-        if (__get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx))
-            have_avx2 = ebx & bit_AVX2;
+    __cpuid(cpu_info, 1);
+    have_sse42 = cpu_info[2] & (1 << 20);
+    have_sse2 = cpu_info[3] & (1 << 26);
-        if (have_avx2) {
-            // check Windows has enabled AVX2 - Windows 10 doesn't immediately
+    __cpuidex(cpu_info, 7, 0);
+    have_avx2 = cpu_info[1] & (1 << 5);
-            if (__readcr4() & (1 << 18)) {
-                uint32_t xcr0;
+    if (have_avx2) {
+        // check Windows has enabled AVX2 - Windows 10 doesn't immediately
-                __asm__("xgetbv" : "=a" (xcr0) : "c" (0) :
"edx" );
+        if (__readcr4() & (1 << 18)) {
+            uint32_t xcr0;
-                if ((xcr0 & 6) != 6)
-                    have_avx2 = false;
-            } else
-                have_avx2 = false;
-        }
-    }
+#ifdef _MSC_VER
+            xcr0 = (uint32_t)_xgetbv(0);
 #else
-    {
-        unsigned int cpu_info[4];
-
-        __cpuid(cpu_info, 1);
-        have_sse42 = cpu_info[2] & (1 << 20);
-        have_sse2 = cpu_info[3] & (1 << 26);
-
-        __cpuidex(cpu_info, 7, 0);
-        have_avx2 = cpu_info[1] & (1 << 5);
-
-        if (have_avx2) {
-            // check Windows has enabled AVX2 - Windows 10 doesn't immediately
-
-            if (__readcr4() & (1 << 18)) {
-                uint32_t xcr0 = (uint32_t)_xgetbv(0);
+            __asm__("xgetbv" : "=a" (xcr0) : "c" (0) :
"edx");
+#endif
-                if ((xcr0 & 6) != 6)
-                    have_avx2 = false;
-            } else
+            if ((xcr0 & 6) != 6)
                 have_avx2 = false;
-        }
+        } else
+            have_avx2 = false;
     }
-#endif
     if (have_sse42) {
         TRACE("SSE4.2 is supported\n");
diff --git a/drivers/filesystems/btrfs/btrfs.inf b/drivers/filesystems/btrfs/btrfs.inf
index 18dc5cd25e9..fd3512b4f2f 100644
--- a/drivers/filesystems/btrfs/btrfs.inf
+++ b/drivers/filesystems/btrfs/btrfs.inf
@@ -10,7 +10,7 @@ Signature   = "$Windows NT$"
 Class       = Volume
 ClassGuid   = {71a27cdd-812a-11d0-bec7-08002be2092f}
 Provider    = %Me%
-DriverVer   = 03/12/2022,1.8.0
+DriverVer   = 08/23/2022,1.8.1
 CatalogFile = btrfs.cat
 [DestinationDirs]
diff --git a/drivers/filesystems/btrfs/btrfs.rc b/drivers/filesystems/btrfs/btrfs.rc
index 59f9d2ccb1f..085aedd131b 100644
--- a/drivers/filesystems/btrfs/btrfs.rc
+++ b/drivers/filesystems/btrfs/btrfs.rc
@@ -51,8 +51,8 @@ END
 //
 VS_VERSION_INFO VERSIONINFO
- FILEVERSION 1,8,0,0
- PRODUCTVERSION 1,8,0,0
+ FILEVERSION 1,8,1,0
+ PRODUCTVERSION 1,8,1,0
  FILEFLAGSMASK 0x17L
 #ifdef _DEBUG
  FILEFLAGS 0x1L
@@ -68,12 +68,12 @@ BEGIN
         BLOCK "080904b0"
         BEGIN
             VALUE "FileDescription", "WinBtrfs"
-            VALUE "FileVersion", "1.8.0"
+            VALUE "FileVersion", "1.8.1"
             VALUE "InternalName", "btrfs"
             VALUE "LegalCopyright", "Copyright (c) Mark Harmstone
2016-22"
             VALUE "OriginalFilename", "btrfs.sys"
             VALUE "ProductName", "WinBtrfs"
-            VALUE "ProductVersion", "1.8.0"
+            VALUE "ProductVersion", "1.8.1"
         END
     END
     BLOCK "VarFileInfo"
diff --git a/drivers/filesystems/btrfs/btrfs_drv.h b/drivers/filesystems/btrfs/btrfs_drv.h
index e06770adfac..46e62e04d05 100644
--- a/drivers/filesystems/btrfs/btrfs_drv.h
+++ b/drivers/filesystems/btrfs/btrfs_drv.h
@@ -492,8 +492,15 @@ typedef struct {
 } batch_item;
 typedef struct {
-    root* r;
+    KEY key;
     LIST_ENTRY items;
+    unsigned int num_items;
+    LIST_ENTRY list_entry;
+} batch_item_ind;
+
+typedef struct {
+    root* r;
+    LIST_ENTRY items_ind;
     LIST_ENTRY list_entry;
 } batch_root;
@@ -674,6 +681,7 @@ typedef struct {
     bool clear_cache;
     bool allow_degraded;
     bool no_root_dir;
+    bool nodatacow;
 } mount_options;
 #define VCB_TYPE_FS         1
@@ -1182,6 +1190,7 @@ extern uint32_t mount_clear_cache;
 extern uint32_t mount_allow_degraded;
 extern uint32_t mount_readonly;
 extern uint32_t mount_no_root_dir;
+extern uint32_t mount_nodatacow;
 extern uint32_t no_pnp;
 #ifndef __GNUC__
@@ -1468,8 +1477,6 @@ NTSTATUS do_tree_writes(device_extension* Vcb, LIST_ENTRY*
tree_writes, bool no_
 void add_checksum_entry(device_extension* Vcb, uint64_t address, ULONG length, void*
csum, PIRP Irp);
 bool find_metadata_address_in_chunk(device_extension* Vcb, chunk* c, uint64_t* address);
 void add_trim_entry_avoid_sb(device_extension* Vcb, device* dev, uint64_t address,
uint64_t size);
-NTSTATUS insert_tree_item_batch(LIST_ENTRY* batchlist, device_extension* Vcb, root* r,
uint64_t objid, uint8_t objtype, uint64_t offset,
-                                _In_opt_ _When_(return >= 0, __drv_aliasesMem) void*
data, uint16_t datalen, enum batch_operation operation);
 NTSTATUS flush_partial_stripe(device_extension* Vcb, chunk* c, partial_stripe* ps);
 NTSTATUS update_dev_item(device_extension* Vcb, device* device, PIRP Irp);
 void calc_tree_checksum(device_extension* Vcb, tree_header* th);
@@ -1697,6 +1704,9 @@ static __inline void print_open_trees(device_extension* Vcb) {
 }
 static __inline bool write_fcb_compressed(fcb* fcb) {
+    if (fcb->inode_item.flags & BTRFS_INODE_NODATACOW)
+        return false;
+
     // make sure we don't accidentally write the cache inodes or pagefile compressed
     if (fcb->subvol->id == BTRFS_ROOT_ROOT || fcb->Header.Flags2 &
FSRTL_FLAG2_IS_PAGING_FILE)
         return false;
diff --git a/drivers/filesystems/btrfs/create.c b/drivers/filesystems/btrfs/create.c
index c5d27d1ce27..c87a85b5015 100644
--- a/drivers/filesystems/btrfs/create.c
+++ b/drivers/filesystems/btrfs/create.c
@@ -2319,19 +2319,24 @@ static NTSTATUS file_create2(_In_ PIRP Irp,
_Requires_exclusive_lock_held_(_Curr
         fcb->inode_item.flags = BTRFS_INODE_NODATACOW | BTRFS_INODE_NODATASUM |
BTRFS_INODE_NOCOMPRESS;
     } else {
         // inherit nodatacow flag from parent directory
-        if (parfileref->fcb->inode_item.flags & BTRFS_INODE_NODATACOW) {
+        if (parfileref->fcb->inode_item.flags & BTRFS_INODE_NODATACOW ||
Vcb->options.nodatacow) {
             fcb->inode_item.flags |= BTRFS_INODE_NODATACOW;
             if (type != BTRFS_TYPE_DIRECTORY)
                 fcb->inode_item.flags |= BTRFS_INODE_NODATASUM;
         }
-        if (parfileref->fcb->inode_item.flags & BTRFS_INODE_COMPRESS)
+        if (parfileref->fcb->inode_item.flags & BTRFS_INODE_COMPRESS &&
+            !(fcb->inode_item.flags & BTRFS_INODE_NODATACOW)) {
             fcb->inode_item.flags |= BTRFS_INODE_COMPRESS;
+        }
     }
-    fcb->prop_compression = parfileref->fcb->prop_compression;
-    fcb->prop_compression_changed = fcb->prop_compression != PropCompression_None;
+    if (!(fcb->inode_item.flags & BTRFS_INODE_NODATACOW)) {
+        fcb->prop_compression = parfileref->fcb->prop_compression;
+        fcb->prop_compression_changed = fcb->prop_compression !=
PropCompression_None;
+    } else
+        fcb->prop_compression = PropCompression_None;
     fcb->inode_item_changed = true;
@@ -2838,7 +2843,7 @@ static NTSTATUS
create_stream(_Requires_lock_held_(_Curr_->tree_lock) _Requires_
     fcb->adsmaxlen = Vcb->superblock.node_size - sizeof(tree_header) -
sizeof(leaf_node) - (sizeof(DIR_ITEM) - 1);
     if (utf8len + sizeof(xapref) - 1 + overhead > fcb->adsmaxlen) {
-        WARN("not enough room for new DIR_ITEM (%Iu + %lu > %lu)", utf8len +
sizeof(xapref) - 1, overhead, fcb->adsmaxlen);
+        WARN("not enough room for new DIR_ITEM (%Iu + %lu > %lu)\n", utf8len
+ sizeof(xapref) - 1, overhead, fcb->adsmaxlen);
         reap_fcb(fcb);
         free_fileref(parfileref);
         return STATUS_DISK_FULL;
@@ -4551,7 +4556,7 @@ static NTSTATUS open_file(PDEVICE_OBJECT DeviceObject,
_Requires_lock_held_(_Cur
             uint64_t inode;
             if (!related) {
-                WARN("cannot open by short file ID unless related fileref also
provided");
+                WARN("cannot open by short file ID unless related fileref also
provided"\n);
                 Status = STATUS_INVALID_PARAMETER;
                 goto exit;
             }
diff --git a/drivers/filesystems/btrfs/fileinfo.c b/drivers/filesystems/btrfs/fileinfo.c
index 16febd612d6..5b51b8ac56b 100644
--- a/drivers/filesystems/btrfs/fileinfo.c
+++ b/drivers/filesystems/btrfs/fileinfo.c
@@ -3248,6 +3248,7 @@ static NTSTATUS set_end_of_file_information(device_extension* Vcb,
PIRP Irp, PFI
     LIST_ENTRY rollback;
     bool set_size = false;
     ULONG filter;
+    uint64_t new_end_of_file;
     if (!fileref) {
         ERR("fileref is NULL\n");
@@ -3306,35 +3307,43 @@ static NTSTATUS set_end_of_file_information(device_extension* Vcb,
PIRP Irp, PFI
     TRACE("FileObject: AllocationSize = %I64x, FileSize = %I64x, ValidDataLength =
%I64x\n",
         fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart,
fcb->Header.ValidDataLength.QuadPart);
-    TRACE("setting new end to %I64x bytes (currently %I64x)\n",
feofi->EndOfFile.QuadPart, fcb->inode_item.st_size);
+    new_end_of_file = feofi->EndOfFile.QuadPart;
-    if ((uint64_t)feofi->EndOfFile.QuadPart < fcb->inode_item.st_size) {
+    /* The lazy writer sometimes tries to round files to the next page size through
CcSetValidData -
+     * ignore these. See fastfat!FatSetEndOfFileInfo, where Microsoft does the same as
we're
+     * doing below. */
+    if (advance_only && new_end_of_file >=
(uint64_t)fcb->Header.FileSize.QuadPart)
+        new_end_of_file = fcb->Header.FileSize.QuadPart;
+
+    TRACE("setting new end to %I64x bytes (currently %I64x)\n",
new_end_of_file, fcb->inode_item.st_size);
+
+    if (new_end_of_file < fcb->inode_item.st_size) {
         if (advance_only) {
             Status = STATUS_SUCCESS;
             goto end;
         }
-        TRACE("truncating file to %I64x bytes\n",
feofi->EndOfFile.QuadPart);
+        TRACE("truncating file to %I64x bytes\n", new_end_of_file);
         if (!MmCanFileBeTruncated(&fcb->nonpaged->segment_object,
&feofi->EndOfFile)) {
             Status = STATUS_USER_MAPPED_FILE;
             goto end;
         }
-        Status = truncate_file(fcb, feofi->EndOfFile.QuadPart, Irp, &rollback);
+        Status = truncate_file(fcb, new_end_of_file, Irp, &rollback);
         if (!NT_SUCCESS(Status)) {
             ERR("error - truncate_file failed\n");
             goto end;
         }
-    } else if ((uint64_t)feofi->EndOfFile.QuadPart > fcb->inode_item.st_size) {
-        TRACE("extending file to %I64x bytes\n", feofi->EndOfFile.QuadPart);
+    } else if (new_end_of_file > fcb->inode_item.st_size) {
+        TRACE("extending file to %I64x bytes\n", new_end_of_file);
-        Status = extend_file(fcb, fileref, feofi->EndOfFile.QuadPart, prealloc, NULL,
&rollback);
+        Status = extend_file(fcb, fileref, new_end_of_file, prealloc, NULL,
&rollback);
         if (!NT_SUCCESS(Status)) {
             ERR("error - extend_file failed\n");
             goto end;
         }
-    } else if ((uint64_t)feofi->EndOfFile.QuadPart == fcb->inode_item.st_size
&& advance_only) {
+    } else if (new_end_of_file == fcb->inode_item.st_size && advance_only) {
         Status = STATUS_SUCCESS;
         goto end;
     }
@@ -5502,6 +5511,11 @@ NTSTATUS __stdcall drv_query_ea(IN PDEVICE_OBJECT DeviceObject, IN
PIRP Irp) {
         goto end;
     }
+    if (fcb == fcb->Vcb->volume_fcb) {
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+
     ccb = FileObject->FsContext2;
     if (!ccb) {
@@ -5753,6 +5767,11 @@ NTSTATUS __stdcall drv_set_ea(IN PDEVICE_OBJECT DeviceObject, IN
PIRP Irp) {
         goto end;
     }
+    if (fcb == fcb->Vcb->volume_fcb) {
+        Status = STATUS_INVALID_PARAMETER;
+        goto end;
+    }
+
     ccb = FileObject->FsContext2;
     if (!ccb) {
diff --git a/drivers/filesystems/btrfs/flushthread.c
b/drivers/filesystems/btrfs/flushthread.c
index bdaf15bf69e..a39e174313f 100644
--- a/drivers/filesystems/btrfs/flushthread.c
+++ b/drivers/filesystems/btrfs/flushthread.c
@@ -29,6 +29,8 @@
 // #define DEBUG_WRITE_LOOPS
+#define BATCH_ITEM_LIMIT 1000
+
 typedef struct {
     KEVENT Event;
     IO_STATUS_BLOCK iosb;
@@ -49,6 +51,10 @@ typedef struct {
 static NTSTATUS create_chunk(device_extension* Vcb, chunk* c, PIRP Irp);
 static NTSTATUS update_tree_extents(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY*
rollback);
+static NTSTATUS insert_tree_item_batch(LIST_ENTRY* batchlist, device_extension* Vcb,
root* r, uint64_t objid,
+                                       uint8_t objtype, uint64_t offset, _In_opt_
_When_(return >= 0, __drv_aliasesMem) void* data,
+                                       uint16_t datalen, enum batch_operation operation);
+
 _Function_class_(IO_COMPLETION_ROUTINE)
 static NTSTATUS __stdcall write_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID
conptr) {
     write_context* context = conptr;
@@ -2923,7 +2929,7 @@ static NTSTATUS update_chunk_usage(device_extension* Vcb, PIRP Irp,
LIST_ENTRY*
 #ifdef DEBUG_PARANOID
             if (bgi->used & 0x8000000000000000) {
-                ERR("refusing to write BLOCK_GROUP_ITEM with negative usage value
(%I64x)", bgi->used);
+                ERR("refusing to write BLOCK_GROUP_ITEM with negative usage value
(%I64x)\n", bgi->used);
                 int3;
             }
 #endif
@@ -4429,12 +4435,88 @@ static NTSTATUS insert_sparse_extent(fcb* fcb, LIST_ENTRY*
batchlist, uint64_t s
     return STATUS_SUCCESS;
 }
+static NTSTATUS split_batch_item_list(batch_item_ind* bii) {
+    LIST_ENTRY* le;
+    unsigned int i = 0;
+    LIST_ENTRY* midpoint = NULL;
+    batch_item_ind* bii2;
+    batch_item* midpoint_item;
+    LIST_ENTRY* before_midpoint;
+
+    le = bii->items.Flink;
+    while (le != &bii->items) {
+        if (i >= bii->num_items / 2) {
+            midpoint = le;
+            break;
+        }
+
+        i++;
+
+        le = le->Flink;
+    }
+
+    if (!midpoint)
+        return STATUS_SUCCESS;
+
+    // make sure items on either side of split don't have same key
+
+    while (midpoint->Blink != &bii->items) {
+        batch_item* item = CONTAINING_RECORD(midpoint, batch_item, list_entry);
+        batch_item* prev = CONTAINING_RECORD(midpoint->Blink, batch_item, list_entry);
+
+        if (item->key.obj_id != prev->key.obj_id)
+            break;
+
+        if (item->key.obj_type != prev->key.obj_type)
+            break;
+
+        if (item->key.offset != prev->key.offset)
+            break;
+
+        midpoint = midpoint->Blink;
+        i--;
+    }
+
+    if (midpoint->Blink == &bii->items)
+        return STATUS_SUCCESS;
+
+    bii2 = ExAllocatePoolWithTag(PagedPool, sizeof(batch_item_ind), ALLOC_TAG);
+    if (!bii2) {
+        ERR("out of memory\n");
+        return STATUS_INSUFFICIENT_RESOURCES;
+    }
+
+    midpoint_item = CONTAINING_RECORD(midpoint, batch_item, list_entry);
+
+    bii2->key.obj_id = midpoint_item->key.obj_id;
+    bii2->key.obj_type = midpoint_item->key.obj_type;
+    bii2->key.offset = midpoint_item->key.offset;
+
+    bii2->num_items = bii->num_items - i;
+    bii->num_items = i;
+
+    before_midpoint = midpoint->Blink;
+
+    bii2->items.Flink = midpoint;
+    midpoint->Blink = &bii2->items;
+    bii2->items.Blink = bii->items.Blink;
+    bii->items.Blink->Flink = &bii2->items;
+
+    bii->items.Blink = before_midpoint;
+    before_midpoint->Flink = &bii->items;
+
+    InsertHeadList(&bii->list_entry, &bii2->list_entry);
+
+    return STATUS_SUCCESS;
+}
+
 #ifdef _MSC_VER
 #pragma warning(push)
 #pragma warning(suppress: 28194)
 #endif
-NTSTATUS insert_tree_item_batch(LIST_ENTRY* batchlist, device_extension* Vcb, root* r,
uint64_t objid, uint8_t objtype, uint64_t offset,
-                                _In_opt_ _When_(return >= 0, __drv_aliasesMem) void*
data, uint16_t datalen, enum batch_operation operation) {
+static NTSTATUS insert_tree_item_batch(LIST_ENTRY* batchlist, device_extension* Vcb,
root* r, uint64_t objid,
+                                       uint8_t objtype, uint64_t offset, _In_opt_
_When_(return >= 0, __drv_aliasesMem) void* data,
+                                       uint16_t datalen, enum batch_operation operation)
{
     LIST_ENTRY* le;
     batch_root* br = NULL;
     batch_item* bi;
@@ -4459,10 +4541,27 @@ NTSTATUS insert_tree_item_batch(LIST_ENTRY* batchlist,
device_extension* Vcb, ro
         }
         br->r = r;
-        InitializeListHead(&br->items);
+        InitializeListHead(&br->items_ind);
         InsertTailList(batchlist, &br->list_entry);
     }
+    if (IsListEmpty(&br->items_ind)) {
+        batch_item_ind* bii;
+
+        bii = ExAllocatePoolWithTag(PagedPool, sizeof(batch_item_ind), ALLOC_TAG);
+        if (!bii) {
+            ERR("out of memory\n");
+            return STATUS_INSUFFICIENT_RESOURCES;
+        }
+
+        bii->key.obj_id = 0;
+        bii->key.obj_type = 0;
+        bii->key.offset = 0;
+        InitializeListHead(&bii->items);
+        bii->num_items = 0;
+        InsertTailList(&br->items_ind, &bii->list_entry);
+    }
+
     bi = ExAllocateFromPagedLookasideList(&Vcb->batch_item_lookaside);
     if (!bi) {
         ERR("out of memory\n");
@@ -4476,22 +4575,41 @@ NTSTATUS insert_tree_item_batch(LIST_ENTRY* batchlist,
device_extension* Vcb, ro
     bi->datalen = datalen;
     bi->operation = operation;
-    le = br->items.Blink;
-    while (le != &br->items) {
-        batch_item* bi2 = CONTAINING_RECORD(le, batch_item, list_entry);
-        int cmp = keycmp(bi2->key, bi->key);
+    le = br->items_ind.Blink;
+    while (le != &br->items_ind) {
+        LIST_ENTRY* le2;
+        batch_item_ind* bii = CONTAINING_RECORD(le, batch_item_ind, list_entry);
-        if (cmp == -1 || (cmp == 0 && bi->operation >= bi2->operation))
{
-            InsertHeadList(&bi2->list_entry, &bi->list_entry);
-            return STATUS_SUCCESS;
+        if (keycmp(bii->key, bi->key) == 1) {
+            le = le->Blink;
+            continue;
         }
-        le = le->Blink;
-    }
+        le2 = bii->items.Blink;
+        while (le2 != &bii->items) {
+            batch_item* bi2 = CONTAINING_RECORD(le2, batch_item, list_entry);
+            int cmp = keycmp(bi2->key, bi->key);
+
+            if (cmp == -1 || (cmp == 0 && bi->operation >=
bi2->operation)) {
+                InsertHeadList(&bi2->list_entry, &bi->list_entry);
+                bii->num_items++;
+                goto end;
+            }
-    InsertHeadList(&br->items, &bi->list_entry);
+            le2 = le2->Blink;
+        }
-    return STATUS_SUCCESS;
+        InsertHeadList(&bii->items, &bi->list_entry);
+        bii->num_items++;
+
+end:
+        if (bii->num_items > BATCH_ITEM_LIMIT)
+            return split_batch_item_list(bii);
+
+        return STATUS_SUCCESS;
+    }
+
+    return STATUS_INTERNAL_ERROR;
 }
 #ifdef _MSC_VER
 #pragma warning(pop)
diff --git a/drivers/filesystems/btrfs/free-space.c
b/drivers/filesystems/btrfs/free-space.c
index 50b8141ee6f..5e6350af444 100644
--- a/drivers/filesystems/btrfs/free-space.c
+++ b/drivers/filesystems/btrfs/free-space.c
@@ -1892,16 +1892,16 @@ static NTSTATUS update_chunk_cache_tree(device_extension* Vcb,
chunk* c, PIRP Ir
                 fsi_count++;
-                ExFreePool(s);
                 RemoveHeadList(&space_list);
+                ExFreePool(s);
                 continue;
             } else if (s->address == tp.item->key.obj_id && s->size ==
tp.item->key.offset) {
                 // unchanged entry
                 fsi_count++;
-                ExFreePool(s);
                 RemoveHeadList(&space_list);
+                ExFreePool(s);
             } else {
                 // remove entry
diff --git a/drivers/filesystems/btrfs/fsctl.c b/drivers/filesystems/btrfs/fsctl.c
index 356031e395f..c1f12ecd57e 100644
--- a/drivers/filesystems/btrfs/fsctl.c
+++ b/drivers/filesystems/btrfs/fsctl.c
@@ -1363,6 +1363,10 @@ static NTSTATUS set_inode_info(PFILE_OBJECT FileObject, void* data,
ULONG length
         return STATUS_ACCESS_DENIED;
     }
+    // nocow and compression are mutually exclusive
+    if (bsii->flags_changed && bsii->flags & BTRFS_INODE_NODATACOW
&& bsii->flags & BTRFS_INODE_COMPRESS)
+        return STATUS_INVALID_PARAMETER;
+
     ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);
     if (bsii->flags_changed) {
diff --git a/drivers/filesystems/btrfs/registry.c b/drivers/filesystems/btrfs/registry.c
index 3f70cf83b14..63b51ea9ae4 100644
--- a/drivers/filesystems/btrfs/registry.c
+++ b/drivers/filesystems/btrfs/registry.c
@@ -38,7 +38,7 @@ NTSTATUS registry_load_volume_options(device_extension* Vcb) {
     mount_options* options = &Vcb->options;
     UNICODE_STRING path, ignoreus, compressus, compressforceus, compresstypeus,
readonlyus, zliblevelus, flushintervalus,
                    maxinlineus, subvolidus, skipbalanceus, nobarrierus, notrimus,
clearcacheus, allowdegradedus, zstdlevelus,
-                   norootdirus;
+                   norootdirus, nodatacowus;
     OBJECT_ATTRIBUTES oa;
     NTSTATUS Status;
     ULONG i, j, kvfilen, index, retlen;
@@ -59,6 +59,8 @@ NTSTATUS registry_load_volume_options(device_extension* Vcb) {
     options->clear_cache = mount_clear_cache;
     options->allow_degraded = mount_allow_degraded;
     options->subvol_id = 0;
+    options->no_root_dir = mount_no_root_dir;
+    options->nodatacow = mount_nodatacow;
     path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR));
     path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG);
@@ -123,6 +125,7 @@ NTSTATUS registry_load_volume_options(device_extension* Vcb) {
     RtlInitUnicodeString(&allowdegradedus, L"AllowDegraded");
     RtlInitUnicodeString(&zstdlevelus, L"ZstdLevel");
     RtlInitUnicodeString(&norootdirus, L"NoRootDir");
+    RtlInitUnicodeString(&nodatacowus, L"NoDataCOW");
     do {
         Status = ZwEnumerateValueKey(h, index, KeyValueFullInformation, kvfi, kvfilen,
&retlen);
@@ -199,6 +202,10 @@ NTSTATUS registry_load_volume_options(device_extension* Vcb) {
                 DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);
                 options->no_root_dir = *val;
+            } else if (FsRtlAreNamesEqual(&nodatacowus, &us, true, NULL)
&& kvfi->DataOffset > 0 && kvfi->DataLength > 0 &&
kvfi->Type == REG_DWORD) {
+                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);
+
+                options->nodatacow = *val;
             }
         } else if (Status != STATUS_NO_MORE_ENTRIES) {
             ERR("ZwEnumerateValueKey returned %08lx\n", Status);
@@ -813,6 +820,7 @@ void read_registry(PUNICODE_STRING regpath, bool refresh) {
     get_registry_value(h, L"Readonly", REG_DWORD, &mount_readonly,
sizeof(mount_readonly));
     get_registry_value(h, L"ZstdLevel", REG_DWORD, &mount_zstd_level,
sizeof(mount_zstd_level));
     get_registry_value(h, L"NoRootDir", REG_DWORD, &mount_no_root_dir,
sizeof(mount_no_root_dir));
+    get_registry_value(h, L"NoDataCOW", REG_DWORD, &mount_nodatacow,
sizeof(mount_nodatacow));
     if (!refresh)
         get_registry_value(h, L"NoPNP", REG_DWORD, &no_pnp,
sizeof(no_pnp));
diff --git a/drivers/filesystems/btrfs/treefuncs.c b/drivers/filesystems/btrfs/treefuncs.c
index 3b9483a7b08..157e453b29a 100644
--- a/drivers/filesystems/btrfs/treefuncs.c
+++ b/drivers/filesystems/btrfs/treefuncs.c
@@ -1254,11 +1254,16 @@ void clear_batch_list(device_extension* Vcb, LIST_ENTRY*
batchlist) {
         LIST_ENTRY* le = RemoveHeadList(batchlist);
         batch_root* br = CONTAINING_RECORD(le, batch_root, list_entry);
-        while (!IsListEmpty(&br->items)) {
-            LIST_ENTRY* le2 = RemoveHeadList(&br->items);
-            batch_item* bi = CONTAINING_RECORD(le2, batch_item, list_entry);
+        while (!IsListEmpty(&br->items_ind)) {
+            batch_item_ind* bii =
CONTAINING_RECORD(RemoveHeadList(&br->items_ind), batch_item_ind, list_entry);
-            ExFreeToPagedLookasideList(&Vcb->batch_item_lookaside, bi);
+            while (!IsListEmpty(&bii->items)) {
+                batch_item* bi = CONTAINING_RECORD(RemoveHeadList(&bii->items),
batch_item, list_entry);
+
+                ExFreeToPagedLookasideList(&Vcb->batch_item_lookaside, bi);
+            }
+
+            ExFreePool(bii);
         }
         ExFreePool(br);
@@ -1901,15 +1906,31 @@ static NTSTATUS handle_batch_collision(device_extension* Vcb,
batch_item* bi, tr
 __attribute__((nonnull(1,2)))
 static NTSTATUS
commit_batch_list_root(_Requires_exclusive_lock_held_(_Curr_->tree_lock)
device_extension* Vcb, batch_root* br, PIRP Irp) {
+    LIST_ENTRY items;
     LIST_ENTRY* le;
     NTSTATUS Status;
     TRACE("root: %I64x\n", br->r->id);
-    le = br->items.Flink;
-    while (le != &br->items) {
+    InitializeListHead(&items);
+
+    // move sub-lists into one big list
+
+    while (!IsListEmpty(&br->items_ind)) {
+        batch_item_ind* bii = CONTAINING_RECORD(RemoveHeadList(&br->items_ind),
batch_item_ind, list_entry);
+
+        items.Blink->Flink = bii->items.Flink;
+        bii->items.Flink->Blink = items.Blink;
+        items.Blink = bii->items.Blink;
+        bii->items.Blink->Flink = &items;
+
+        ExFreePool(bii);
+    }
+
+    le = items.Flink;
+    while (le != &items) {
         batch_item* bi = CONTAINING_RECORD(le, batch_item, list_entry);
-        LIST_ENTRY *le2;
+        LIST_ENTRY* le2;
         traverse_ptr tp;
         KEY tree_end;
         bool no_end;
@@ -2174,7 +2195,7 @@ static NTSTATUS
commit_batch_list_root(_Requires_exclusive_lock_held_(_Curr_->tr
                     if (td)
                         InsertHeadList(tp.item->list_entry.Blink,
&td->list_entry);
                 } else {
-                    Status = handle_batch_collision(Vcb, bi, tp.tree, tp.item, td,
&br->items, &ignore);
+                    Status = handle_batch_collision(Vcb, bi, tp.tree, tp.item, td,
&items, &ignore);
                     if (!NT_SUCCESS(Status)) {
                         ERR("handle_batch_collision returned %08lx\n", Status);
 #ifdef _DEBUG
@@ -2192,7 +2213,7 @@ static NTSTATUS
commit_batch_list_root(_Requires_exclusive_lock_held_(_Curr_->tr
             }
             if (bi->operation == Batch_DeleteInodeRef && cmp != 0 &&
Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {
-                add_delete_inode_extref(Vcb, bi, &br->items);
+                add_delete_inode_extref(Vcb, bi, &items);
             }
             if (!ignore && td) {
@@ -2214,7 +2235,7 @@ static NTSTATUS
commit_batch_list_root(_Requires_exclusive_lock_held_(_Curr_->tr
             }
             le2 = le->Flink;
-            while (le2 != &br->items) {
+            while (le2 != &items) {
                 batch_item* bi2 = CONTAINING_RECORD(le2, batch_item, list_entry);
                 if (bi2->operation == Batch_DeleteInode || bi2->operation ==
Batch_DeleteExtentData || bi2->operation == Batch_DeleteFreeSpace)
@@ -2255,10 +2276,10 @@ static NTSTATUS
commit_batch_list_root(_Requires_exclusive_lock_held_(_Curr_->tr
                                     InsertHeadList(le3->Blink,
&td->list_entry);
                                     inserted = true;
                                 } else if (bi2->operation == Batch_DeleteInodeRef
&& Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {
-                                    add_delete_inode_extref(Vcb, bi2, &br->items);
+                                    add_delete_inode_extref(Vcb, bi2, &items);
                                 }
                             } else {
-                                Status = handle_batch_collision(Vcb, bi2, tp.tree, td2,
td, &br->items, &ignore);
+                                Status = handle_batch_collision(Vcb, bi2, tp.tree, td2,
td, &items, &ignore);
                                 if (!NT_SUCCESS(Status)) {
                                     ERR("handle_batch_collision returned
%08lx\n", Status);
 #ifdef _DEBUG
@@ -2275,7 +2296,7 @@ static NTSTATUS
commit_batch_list_root(_Requires_exclusive_lock_held_(_Curr_->tr
                                 InsertHeadList(le3->Blink, &td->list_entry);
                                 inserted = true;
                             } else if (bi2->operation == Batch_DeleteInodeRef
&& Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {
-                                add_delete_inode_extref(Vcb, bi2, &br->items);
+                                add_delete_inode_extref(Vcb, bi2, &items);
                             }
                             break;
                         }
@@ -2294,7 +2315,7 @@ static NTSTATUS
commit_batch_list_root(_Requires_exclusive_lock_held_(_Curr_->tr
                             listhead = td;
                         }
                     } else if (!inserted && bi2->operation ==
Batch_DeleteInodeRef && Vcb->superblock.incompat_flags &
BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {
-                        add_delete_inode_extref(Vcb, bi2, &br->items);
+                        add_delete_inode_extref(Vcb, bi2, &items);
                     }
                     while (listhead->list_entry.Blink != &tp.tree->itemlist) {
@@ -2330,8 +2351,8 @@ static NTSTATUS
commit_batch_list_root(_Requires_exclusive_lock_held_(_Curr_->tr
     }
     // FIXME - remove as we are going along
-    while (!IsListEmpty(&br->items)) {
-        batch_item* bi = CONTAINING_RECORD(RemoveHeadList(&br->items), batch_item,
list_entry);
+    while (!IsListEmpty(&items)) {
+        batch_item* bi = CONTAINING_RECORD(RemoveHeadList(&items), batch_item,
list_entry);
         if ((bi->operation == Batch_DeleteDirItem || bi->operation ==
Batch_DeleteInodeRef ||
             bi->operation == Batch_DeleteInodeExtRef || bi->operation ==
Batch_DeleteXattr) && bi->data)