From 29d1938258cc83423d42281d87d58ffe8cc63bc2 Mon Sep 17 00:00:00 2001 From: Johannes Obermayr Date: Wed, 28 Sep 2022 18:08:10 +0200 Subject: [PATCH] [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 | 106 +++++++++-------- 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, 319 insertions(+), 115 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,60 +5974,33 @@ 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(cpu_info, 1); + have_sse42 = cpu_info[2] & (1 << 20); + have_sse2 = cpu_info[3] & (1 << 26); - __cpuid(1, eax, ebx, ecx, edx); + __cpuidex(cpu_info, 7, 0); + have_avx2 = cpu_info[1] & (1 << 5); - if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) { - have_sse42 = ecx & bit_SSE4_2; - have_sse2 = edx & bit_SSE2; - } + if (have_avx2) { + // check Windows has enabled AVX2 - Windows 10 doesn't immediately - if (__get_cpuid_count(7, 0, &eax, &ebx, &ecx, &edx)) - have_avx2 = ebx & bit_AVX2; + if (__readcr4() & (1 << 18)) { + uint32_t xcr0; - if (have_avx2) { - // check Windows has enabled AVX2 - Windows 10 doesn't immediately - - if (__readcr4() & (1 << 18)) { - uint32_t xcr0; - - __asm__("xgetbv" : "=a" (xcr0) : "c" (0) : "edx" ); - - 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); - - if ((xcr0 & 6) != 6) - have_avx2 = false; - } else - have_avx2 = false; - } - } + __asm__("xgetbv" : "=a" (xcr0) : "c" (0) : "edx"); #endif + if ((xcr0 & 6) != 6) + have_avx2 = false; + } else + have_avx2 = false; + } + if (have_sse42) { TRACE("SSE4.2 is supported\n"); calc_crc32c = calc_crc32c_hw; 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; + } + + le2 = le2->Blink; + } + + 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; } - InsertHeadList(&br->items, &bi->list_entry); - - 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)