[
  {
    "path": ".github/workflows/build.yml",
    "content": "name: build\non: [push]\nenv:\n   PATH: /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/etc/eselect/wine/bin\njobs:\n  cmake:\n    runs-on: msvc-wine\n    container:\n      volumes:\n        - /var/run/pcscd/pcscd.comm:/var/run/pcscd/pcscd.comm\n    steps:\n      - run: echo \"SHORT_SHA=`echo ${{ github.sha }} | cut -c1-8`\" >> $GITHUB_ENV\n      - run: git clone --recurse-submodules -j`nproc` ${{ github.server_url }}/${{ github.repository }} ${SHORT_SHA}\n      - run: cd ${SHORT_SHA} && git checkout ${{ github.sha }}\n      - run: mkdir -p build/debug/{amd64,x86,aarch64,arm}\n      - run: mkdir -p build/release/{amd64,x86,aarch64,arm}\n      - run: mkdir -p build/pdb/{debug,release}/{amd64,x86,aarch64,arm}\n      - run: cmake -DCMAKE_TOOLCHAIN_FILE=msvc-amd64.cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_TEST=OFF -S ${SHORT_SHA} -B build/debug/amd64 && cmake --build build/debug/amd64 --parallel `nproc`\n      - run: cmake -DCMAKE_TOOLCHAIN_FILE=msvc-x86.cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_TEST=OFF -S ${SHORT_SHA} -B build/debug/x86 && cmake --build build/debug/x86 --parallel `nproc`\n      - run: cmake -DCMAKE_TOOLCHAIN_FILE=msvc-aarch64.cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_TEST=OFF -S ${SHORT_SHA} -B build/debug/aarch64 && cmake --build build/debug/aarch64 --parallel `nproc`\n      - run: cmake -DCMAKE_TOOLCHAIN_FILE=msvc-armv7.cmake -DCMAKE_BUILD_TYPE=Debug -DWITH_TEST=OFF -S ${SHORT_SHA} -B build/debug/arm && cmake --build build/debug/arm --parallel `nproc`\n      - run: cmake -DCMAKE_TOOLCHAIN_FILE=msvc-amd64.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_TEST=OFF -S ${SHORT_SHA} -B build/release/amd64 && cmake --build build/release/amd64 --parallel `nproc`\n      - run: cmake -DCMAKE_TOOLCHAIN_FILE=msvc-x86.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_TEST=OFF -S ${SHORT_SHA} -B build/release/x86 && cmake --build build/release/x86 --parallel `nproc`\n      - run: cmake -DCMAKE_TOOLCHAIN_FILE=msvc-aarch64.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_TEST=OFF -S ${SHORT_SHA} -B build/release/aarch64 && cmake --build build/release/aarch64 --parallel `nproc`\n      - run: cmake -DCMAKE_TOOLCHAIN_FILE=msvc-armv7.cmake -DCMAKE_BUILD_TYPE=RelWithDebInfo -DWITH_TEST=OFF -S ${SHORT_SHA} -B build/release/arm && cmake --build build/release/arm --parallel `nproc`\n      - run: mv build/debug/amd64/*.pdb build/pdb/debug/amd64/\n      - run: mv build/debug/x86/*.pdb build/pdb/debug/x86/\n      - run: mv build/debug/aarch64/*.pdb build/pdb/debug/aarch64/\n      - run: mv build/debug/arm/*.pdb build/pdb/debug/arm/\n      - run: mv build/release/amd64/*.pdb build/pdb/release/amd64/\n      - run: mv build/release/x86/*.pdb build/pdb/release/x86/\n      - run: mv build/release/aarch64/*.pdb build/pdb/release/aarch64/\n      - run: mv build/release/arm/*.pdb build/pdb/release/arm/\n      - run: cp ${SHORT_SHA}/src/{btrfs,btrfs-vol}.inf build/debug/\n      - run: cp ${SHORT_SHA}/src/{btrfs,btrfs-vol}.inf build/release/\n      - run: stampinf -f build/debug/btrfs.inf -d \\* -v \\*\n      - run: stampinf -f build/debug/btrfs-vol.inf -d \\* -v \\*\n      - run: stampinf -f build/release/btrfs.inf -d \\* -v \\*\n      - run: stampinf -f build/release/btrfs-vol.inf -d \\* -v \\*\n      - run: cd build/debug && makecat ../../${SHORT_SHA}/src/btrfs.cdf\n      - run: cd build/release && makecat ../../${SHORT_SHA}/src/btrfs.cdf\n      - env:\n          CERTIFICATE: ${{ secrets.CERTIFICATE }}\n        run: echo \"${CERTIFICATE}\" > codesigning.crt\n      - env:\n          PKCS11CERT: ${{ secrets.PKCS11CERT }}\n          PKCS11KEY: ${{ secrets.PKCS11KEY }}\n        run: for i in build/{debug,release}/btrfs.cat; do osslsigncode sign -pkcs11module /usr/lib64/libcrypto3PKCS.so -pkcs11cert \"${PKCS11CERT}\" -key \"${PKCS11KEY}\" -certs codesigning.crt -t http://timestamp.digicert.com -in $i -out tmp && mv tmp $i; done\n      - env:\n          PKCS11CERT: ${{ secrets.PKCS11CERT }}\n          PKCS11KEY: ${{ secrets.PKCS11KEY }}\n        run: for i in build/{debug,release}/{amd64,x86,aarch64,arm}/{btrfs.sys,mkbtrfs.exe,shellbtrfs.dll,ubtrfs.dll}; do osslsigncode sign -pkcs11module /usr/lib64/libcrypto3PKCS.so -pkcs11cert \"${PKCS11CERT}\" -key \"${PKCS11KEY}\" -certs codesigning.crt -t http://timestamp.digicert.com -ph -in $i -out tmp && mv tmp $i; done\n      - uses: actions/upload-artifact@v3\n        with:\n          name: ${{ github.sha }}\n          overwrite: true\n          path: |\n            build/**/btrfs.sys\n            build/**/mkbtrfs.exe\n            build/**/shellbtrfs.dll\n            build/**/ubtrfs.dll\n            build/**/*.inf\n            build/**/*.cat\n            build/pdb\n"
  },
  {
    "path": ".gitmodules",
    "content": "[submodule \"src/zstd\"]\n\tpath = src/zstd\n\turl = https://github.com/facebook/zstd\n[submodule \"src/zlib\"]\n\tpath = src/zlib\n\turl = https://github.com/madler/zlib\n"
  },
  {
    "path": "CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.15)\n\nproject(btrfs VERSION 1.9.0)\n\noption(WITH_TEST \"Compile test program\" ON)\n\nif(MSVC) # cmake bug 15170\n    if(MSVC_C_ARCHITECTURE_ID STREQUAL \"X86\")\n        set(CMAKE_SYSTEM_PROCESSOR \"x86\")\n    elseif(MSVC_C_ARCHITECTURE_ID STREQUAL \"x64\")\n        set(CMAKE_SYSTEM_PROCESSOR \"x86_64\")\n    elseif(MSVC_C_ARCHITECTURE_ID STREQUAL \"ARMV7\")\n        set(CMAKE_SYSTEM_PROCESSOR \"arm\")\n    elseif(MSVC_C_ARCHITECTURE_ID STREQUAL \"ARM64\")\n        set(CMAKE_SYSTEM_PROCESSOR \"aarch64\")\n    endif()\nendif()\n\n# zstd\n\nset(ZSTD_SRC_FILES src/zstd/lib/common/entropy_common.c\n    src/zstd/lib/common/error_private.c\n    src/zstd/lib/compress/fse_compress.c\n    src/zstd/lib/common/fse_decompress.c\n    src/zstd/lib/compress/hist.c\n    src/zstd/lib/compress/huf_compress.c\n    src/zstd/lib/decompress/huf_decompress.c\n    src/zstd/lib/common/zstd_common.c\n    src/zstd/lib/compress/zstd_compress.c\n    src/zstd/lib/compress/zstd_compress_literals.c\n    src/zstd/lib/compress/zstd_compress_sequences.c\n    src/zstd/lib/compress/zstd_compress_superblock.c\n    src/zstd/lib/decompress/zstd_ddict.c\n    src/zstd/lib/decompress/zstd_decompress.c\n    src/zstd/lib/decompress/zstd_decompress_block.c\n    src/zstd/lib/compress/zstd_double_fast.c\n    src/zstd/lib/compress/zstd_fast.c\n    src/zstd/lib/compress/zstd_lazy.c\n    src/zstd/lib/compress/zstd_ldm.c\n    src/zstd/lib/compress/zstd_opt.c\n    src/zstd/lib/common/xxhash.c)\n\nadd_library(zstd STATIC ${ZSTD_SRC_FILES})\ntarget_compile_definitions(zstd PRIVATE -DZSTD_DEPS_MALLOC -DXXH_NO_STDLIB)\n\nif(NOT MSVC)\n    target_compile_options(zstd PRIVATE -ffunction-sections -include ${CMAKE_SOURCE_DIR}/src/zstd-shim.h)\nelse()\n    target_compile_options(zstd PRIVATE /Gy /FI ${CMAKE_SOURCE_DIR}/src/zstd-shim.h)\n    set_property(TARGET zstd PROPERTY MSVC_RUNTIME_LIBRARY \"MultiThreaded$<$<CONFIG:Debug>:Debug>\")\nendif()\n\nif(MSVC AND CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86\")\n    target_compile_options(zstd PUBLIC /Gz) # stdcall\nendif()\n\n# zlib\n\nset(ZLIB_SRC_FILES src/zlib/adler32.c\n    src/zlib/deflate.c\n    src/zlib/inffast.c\n    src/zlib/inflate.c\n    src/zlib/inftrees.c\n    src/zlib/trees.c\n    src/zlib/zutil.c)\n\nadd_library(zlib STATIC ${ZLIB_SRC_FILES})\ntarget_compile_definitions(zlib PRIVATE -DNO_GZIP -DZ_SOLO)\n\nif(NOT MSVC)\n    target_compile_options(zlib PRIVATE -ffunction-sections)\nelse()\n    target_compile_options(zlib PRIVATE /Gy)\nendif()\n\nif(MSVC AND CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86\")\n    target_compile_options(zlib PUBLIC /Gz) # stdcall\nendif()\n\n# btrfs.sys\n\nset(SRC_FILES src/balance.c\n    src/blake2b-ref.c\n    src/boot.c\n    src/btrfs.c\n    src/cache.c\n    src/calcthread.c\n    src/compress.c\n    src/crc32c.c\n    src/create.c\n    src/devctrl.c\n    src/dirctrl.c\n    src/extent-tree.c\n    src/fastio.c\n    src/fileinfo.c\n    src/flushthread.c\n    src/free-space.c\n    src/fsctl.c\n    src/fsrtl.c\n    src/galois.c\n    src/pnp.c\n    src/read.c\n    src/registry.c\n    src/reparse.c\n    src/scrub.c\n    src/search.c\n    src/security.c\n    src/send.c\n    src/sha256.c\n    src/treefuncs.c\n    src/volume.c\n    src/worker-thread.c\n    src/write.c\n    ${CMAKE_CURRENT_BINARY_DIR}/btrfs.rc)\n\n# Work around bug in MSVC version of cmake - see https://gitlab.kitware.com/cmake/cmake/-/merge_requests/4257\nset(CMAKE_ASM_MASM_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreaded         \"\")\nset(CMAKE_ASM_MASM_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDLL      \"\")\nset(CMAKE_ASM_MASM_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDebug    \"\")\nset(CMAKE_ASM_MASM_COMPILE_OPTIONS_MSVC_RUNTIME_LIBRARY_MultiThreadedDebugDLL \"\")\n\nset(CMAKE_ASM_MASM_FLAGS \"/Zd\")\n\nif(CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86_64\" OR CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86\")\n    if(MSVC)\n        enable_language(ASM_MASM)\n        set(SRC_FILES ${SRC_FILES}\n            src/crc32c-masm.asm\n            src/xor-masm.asm)\n    else()\n        enable_language(ASM)\n        set(SRC_FILES ${SRC_FILES}\n            src/crc32c-gas.S\n            src/xor-gas.S)\n    endif()\nendif()\n\nif(MSVC AND (CMAKE_SYSTEM_PROCESSOR STREQUAL \"aarch64\"))\n    # see cmake bug 24317 if armasm64 fails (should be fixed in CMake 3.29)\n    enable_language(ASM_MARMASM)\n    set(SRC_FILES ${SRC_FILES}\n        src/crc32c-aarch64.asm)\nendif()\n\nconfigure_file(src/btrfs.rc.in btrfs.rc)\n\nif(CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86_64\")\n    add_definitions(-D_AMD64_)\n    set(MS_ARCH \"x64\")\nelseif(CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86\")\n    add_definitions(-D_X86_)\n    set(MS_ARCH \"x86\")\nelseif(CMAKE_SYSTEM_PROCESSOR STREQUAL \"arm\")\n    add_definitions(-D_ARM_)\n    set(MS_ARCH \"arm\")\nelseif(CMAKE_SYSTEM_PROCESSOR STREQUAL \"aarch64\")\n    add_definitions(-D_ARM64_)\n    set(MS_ARCH \"arm64\")\nendif()\n\nif(MSVC)\n    include_directories(\"$ENV{WindowsSdkDir}Include\\\\$ENV{WindowsSDKLibVersion}km\")\n    link_directories(\"$ENV{WindowsSdkDir}Lib\\\\$ENV{WindowsSDKLibVersion}km\\\\${MS_ARCH}\")\nelseif(CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\" AND WIN32)\n    include_directories(\"${CMAKE_FIND_ROOT_PATH}/usr/include/ddk\")\nendif()\n\nadd_library(btrfs SHARED ${SRC_FILES})\ntarget_link_libraries(btrfs zstd zlib)\n\nif(CMAKE_BUILD_TYPE STREQUAL \"Debug\")\n    add_definitions(-D_DEBUG)\nendif()\n\nif(NOT MSVC)\n    target_compile_options(btrfs PUBLIC -U__NO_INLINE__)\n    add_definitions(-D__USE_MINGW_ANSI_STDIO=0)\nendif()\n\ntarget_compile_definitions(btrfs PUBLIC _KERNEL_MODE WIN9X_COMPAT_SPINLOCK)\n\nif(MSVC)\n    if(CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86\")\n        target_compile_options(btrfs PUBLIC /Gz) # stdcall\n    endif()\n\n    target_link_libraries(btrfs ntoskrnl hal)\n\n    if(CMAKE_SYSTEM_PROCESSOR STREQUAL \"arm\" OR CMAKE_SYSTEM_PROCESSOR STREQUAL \"aarch64\")\n        target_link_libraries(btrfs bufferoverflowfastfailk)\n    else()\n        target_link_libraries(btrfs BufferOverflowK)\n    endif()\n\n    if(CMAKE_SYSTEM_PROCESSOR STREQUAL \"arm\")\n        target_link_libraries(btrfs armrt)\n    elseif(CMAKE_SYSTEM_PROCESSOR STREQUAL \"aarch64\")\n        target_link_libraries(btrfs arm64rt)\n    endif()\n\n    target_link_libraries(btrfs rtlver)\n    target_link_options(btrfs PUBLIC /SUBSYSTEM:NATIVE /NODEFAULTLIB /MANIFEST:NO /Driver /ENTRY:DriverEntry)\n\n    # strip out flags for MSVC's runtime checks\n    string(REGEX REPLACE \"/RTC(su|[1su])\" \"\" CMAKE_C_FLAGS \"${CMAKE_C_FLAGS}\")\n    string(REGEX REPLACE \"/RTC(su|[1su])\" \"\" CMAKE_C_FLAGS_DEBUG \"${CMAKE_C_FLAGS_DEBUG}\")\nelse()\n    target_compile_options(btrfs PUBLIC -Wall -Werror-implicit-function-declaration -Werror=incompatible-pointer-types -Wno-expansion-to-defined -Wunused-parameter -Wtype-limits -Wextra)\n\n    if (CMAKE_CXX_COMPILER_ID STREQUAL \"GNU\")\n        target_compile_options(btrfs PUBLIC -Werror=cast-function-type -Wold-style-declaration)\n    elseif (CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\")\n        target_compile_options(btrfs PUBLIC -Wno-pragma-pack) # ignore warning in mingw headers\n    endif()\n\n    target_link_libraries(btrfs ntoskrnl hal gcc)\n    target_link_options(btrfs PUBLIC -nostdlib -Wl,--subsystem,native -Wl,--file-alignment,0x1000 -Wl,--section-alignment,0x1000 -Wl,--exclude-all-symbols)\n\n    if(CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86\")\n        target_link_options(btrfs PUBLIC -Wl,--entry,_DriverEntry@8)\n    else()\n        target_link_options(btrfs PUBLIC -Wl,--entry,DriverEntry)\n    endif()\nendif()\n\nset_target_properties(btrfs PROPERTIES PREFIX \"\")\nset_target_properties(btrfs PROPERTIES SUFFIX \".sys\")\n\n# --------------------------------------\n\n# shellbtrfs.dll\n\nset(SHELLEXT_SRC_FILES src/shellext/balance.cpp\n    src/shellext/contextmenu.cpp\n    src/shellext/devices.cpp\n    src/shellext/factory.cpp\n    src/shellext/iconoverlay.cpp\n    src/shellext/main.cpp\n    src/shellext/mappings.cpp\n    src/shellext/mountmgr.cpp\n    src/shellext/propsheet.cpp\n    src/shellext/recv.cpp\n    src/shellext/scrub.cpp\n    src/shellext/send.cpp\n    src/shellext/volpropsheet.cpp\n    src/crc32c.c\n    src/shellext/shellbtrfs.def\n    ${CMAKE_CURRENT_BINARY_DIR}/shellbtrfs.rc)\n\nif(CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86_64\" OR CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86\")\n    if(MSVC)\n        enable_language(ASM_MASM)\n        set(SHELLEXT_SRC_FILES ${SHELLEXT_SRC_FILES} src/crc32c-masm.asm)\n    else()\n        enable_language(ASM)\n        set(SHELLEXT_SRC_FILES ${SHELLEXT_SRC_FILES} src/crc32c-gas.S)\n    endif()\nendif()\n\nset(CMAKE_CXX_STANDARD 23)\nset(CMAKE_CXX_STANDARD_REQUIRED ON)\nset(CMAKE_CXX_VISIBILITY_PRESET hidden)\n\nconfigure_file(src/shellext/shellbtrfs.rc.in shellbtrfs.rc)\n\nadd_library(shellbtrfs SHARED ${SHELLEXT_SRC_FILES})\n\nif(NOT MSVC)\n    target_link_options(shellbtrfs PUBLIC -static -static-libgcc)\n    target_link_libraries(shellbtrfs pthread)\n\n    target_compile_options(shellbtrfs PUBLIC -Wall -Wno-expansion-to-defined -Wunused-parameter -Wtype-limits -Wextra)\nelse()\n    target_compile_options(shellbtrfs PUBLIC /EHsc)\n    target_link_options(shellbtrfs PUBLIC /MANIFEST:NO)\nendif()\n\ntarget_link_libraries(shellbtrfs comctl32 ntdll setupapi uxtheme shlwapi windowscodecs gdi32 advapi32 shell32 ole32)\n\nif(MSVC AND CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86\")\n    target_compile_options(shellbtrfs PUBLIC /Gz) # stdcall\nendif()\n\nset_target_properties(shellbtrfs PROPERTIES PREFIX \"\")\n\nif(MSVC)\n    set_property(TARGET shellbtrfs PROPERTY MSVC_RUNTIME_LIBRARY \"MultiThreaded$<$<CONFIG:Debug>:Debug>\")\nendif()\n\n# --------------------------------------\n\n# ubtrfs.dll\n\nset(UBTRFS_SRC_FILES src/ubtrfs/ubtrfs.c\n    src/crc32c.c\n    src/sha256.c\n    src/blake2b-ref.c\n    src/ubtrfs/ubtrfs.def\n    ${CMAKE_CURRENT_BINARY_DIR}/ubtrfs.rc)\n\nif(CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86_64\" OR CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86\")\n    if(MSVC)\n        enable_language(ASM_MASM)\n        set(UBTRFS_SRC_FILES ${UBTRFS_SRC_FILES} src/crc32c-masm.asm)\n    else()\n        enable_language(ASM)\n        set(UBTRFS_SRC_FILES ${UBTRFS_SRC_FILES} src/crc32c-gas.S)\n    endif()\nendif()\n\nconfigure_file(src/ubtrfs/ubtrfs.rc.in ubtrfs.rc)\n\nadd_library(ubtrfs SHARED ${UBTRFS_SRC_FILES})\n\nif(MSVC)\n    set_property(TARGET ubtrfs PROPERTY MSVC_RUNTIME_LIBRARY \"MultiThreaded$<$<CONFIG:Debug>:Debug>\")\nendif()\n\ntarget_compile_definitions(ubtrfs PUBLIC _USRDLL)\ntarget_link_libraries(ubtrfs ntdll advapi32)\ntarget_link_libraries(ubtrfs zstd)\n\nif (CMAKE_CXX_COMPILER_ID STREQUAL \"Clang\")\n    target_compile_options(ubtrfs PUBLIC -Wno-pragma-pack) # ignore warning in mingw headers\nendif()\n\nif(MSVC AND CMAKE_SYSTEM_PROCESSOR STREQUAL \"x86\")\n    target_compile_options(ubtrfs PUBLIC /Gz) # stdcall\nendif()\n\nif(NOT MSVC)\n    target_compile_options(ubtrfs PUBLIC -Werror-implicit-function-declaration)\n    target_link_options(ubtrfs PUBLIC -static -static-libgcc -static-libstdc++)\nendif()\n\nset_target_properties(ubtrfs PROPERTIES PREFIX \"\")\n\n# --------------------------------------\n\n# mkbtrfs.exe\n\nset(MKBTRFS_SRC_FILES src/mkbtrfs/mkbtrfs.c\n    ${CMAKE_CURRENT_BINARY_DIR}/mkbtrfs.rc)\n\nconfigure_file(src/mkbtrfs/mkbtrfs.rc.in mkbtrfs.rc)\n\nadd_executable(mkbtrfs ${MKBTRFS_SRC_FILES})\n\nif(MSVC)\n    set_property(TARGET mkbtrfs PROPERTY MSVC_RUNTIME_LIBRARY \"MultiThreaded$<$<CONFIG:Debug>:Debug>\")\nelse()\n    target_link_options(mkbtrfs PUBLIC -static -static-libgcc)\nendif()\n\n# --------------------------------------\n\n# test.exe\n\nif(WITH_TEST)\n    set(CMAKE_CXX_STANDARD 20)\n    set(CMAKE_CXX_STANDARD_REQUIRED ON)\n\n    configure_file(src/tests/test.rc.in test.rc)\n\n    set(TEST_SRC_FILES src/tests/test.cpp\n        ${CMAKE_CURRENT_BINARY_DIR}/test.rc\n        src/tests/create.cpp\n        src/tests/supersede.cpp\n        src/tests/overwrite.cpp\n        src/tests/io.cpp\n        src/tests/mmap.cpp\n        src/tests/rename.cpp\n        src/tests/delete.cpp\n        src/tests/links.cpp\n        src/tests/oplock.cpp\n        src/tests/cs.cpp\n        src/tests/reparse.cpp\n        src/tests/streams.cpp\n        src/tests/ea.cpp\n        src/tests/fileinfo.cpp\n        src/tests/security.cpp)\n\n    add_executable(test ${TEST_SRC_FILES})\n\n    if(MSVC)\n        set_property(TARGET test PROPERTY MSVC_RUNTIME_LIBRARY \"MultiThreaded$<$<CONFIG:Debug>:Debug>\")\n    else()\n        target_link_options(test PUBLIC -static -static-libgcc -municode)\n        target_compile_options(test PUBLIC -Wall -Wno-expansion-to-defined -Wunused-parameter -Wtype-limits -Wextra)\n    endif()\n\n    target_link_libraries(test ntdll version advapi32)\nendif()\n\n# --------------------------------------\n\n# install\n\ninstall(TARGETS btrfs DESTINATION bin)\ninstall(TARGETS shellbtrfs DESTINATION bin)\ninstall(TARGETS ubtrfs DESTINATION bin)\ninstall(TARGETS mkbtrfs DESTINATION bin)\n"
  },
  {
    "path": "CMakeSettings.json",
    "content": "﻿{\n  \"configurations\": [\n    {\n      \"name\": \"x64-Debug\",\n      \"generator\": \"Ninja\",\n      \"configurationType\": \"Debug\",\n      \"buildRoot\": \"${projectDir}\\\\out\\\\build\\\\${name}\",\n      \"installRoot\": \"${projectDir}\\\\out\\\\install\\\\${name}\",\n      \"cmakeCommandArgs\": \"\",\n      \"buildCommandArgs\": \"-v\",\n      \"ctestCommandArgs\": \"\",\n      \"inheritEnvironments\": [ \"msvc_x64_x64\" ],\n      \"variables\": []\n    },\n    {\n      \"name\": \"x86-Debug\",\n      \"generator\": \"Ninja\",\n      \"configurationType\": \"Debug\",\n      \"buildRoot\": \"${projectDir}\\\\out\\\\build\\\\${name}\",\n      \"installRoot\": \"${projectDir}\\\\out\\\\install\\\\${name}\",\n      \"cmakeCommandArgs\": \"\",\n      \"buildCommandArgs\": \"-v\",\n      \"ctestCommandArgs\": \"\",\n      \"inheritEnvironments\": [ \"msvc_x86_x64\" ],\n      \"variables\": []\n    },\n    {\n      \"name\": \"arm-Debug\",\n      \"generator\": \"Ninja\",\n      \"configurationType\": \"Debug\",\n      \"buildRoot\": \"${projectDir}\\\\out\\\\build\\\\${name}\",\n      \"installRoot\": \"${projectDir}\\\\out\\\\install\\\\${name}\",\n      \"cmakeCommandArgs\": \"\",\n      \"buildCommandArgs\": \"-v\",\n      \"ctestCommandArgs\": \"\",\n      \"inheritEnvironments\": [ \"msvc_arm_x64\" ],\n      \"variables\": []\n    },\n    {\n      \"name\": \"arm64-Debug\",\n      \"generator\": \"Ninja\",\n      \"configurationType\": \"Debug\",\n      \"buildRoot\": \"${projectDir}\\\\out\\\\build\\\\${name}\",\n      \"installRoot\": \"${projectDir}\\\\out\\\\install\\\\${name}\",\n      \"cmakeCommandArgs\": \"\",\n      \"buildCommandArgs\": \"-v\",\n      \"ctestCommandArgs\": \"\",\n      \"inheritEnvironments\": [ \"msvc_arm64_x64\" ],\n      \"variables\": []\n    },\n    {\n      \"name\": \"x64-Release\",\n      \"generator\": \"Ninja\",\n      \"configurationType\": \"RelWithDebInfo\",\n      \"buildRoot\": \"${projectDir}\\\\out\\\\build\\\\${name}\",\n      \"installRoot\": \"${projectDir}\\\\out\\\\install\\\\${name}\",\n      \"cmakeCommandArgs\": \"\",\n      \"buildCommandArgs\": \"-v\",\n      \"ctestCommandArgs\": \"\",\n      \"inheritEnvironments\": [ \"msvc_x64_x64\" ],\n      \"variables\": []\n    },\n    {\n      \"name\": \"x86-Release\",\n      \"generator\": \"Ninja\",\n      \"configurationType\": \"RelWithDebInfo\",\n      \"buildRoot\": \"${projectDir}\\\\out\\\\build\\\\${name}\",\n      \"installRoot\": \"${projectDir}\\\\out\\\\install\\\\${name}\",\n      \"cmakeCommandArgs\": \"\",\n      \"buildCommandArgs\": \"-v\",\n      \"ctestCommandArgs\": \"\",\n      \"inheritEnvironments\": [ \"msvc_x86_x64\" ],\n      \"variables\": []\n    },\n    {\n      \"name\": \"arm-Release\",\n      \"generator\": \"Ninja\",\n      \"configurationType\": \"RelWithDebInfo\",\n      \"buildRoot\": \"${projectDir}\\\\out\\\\build\\\\${name}\",\n      \"installRoot\": \"${projectDir}\\\\out\\\\install\\\\${name}\",\n      \"cmakeCommandArgs\": \"\",\n      \"buildCommandArgs\": \"-v\",\n      \"ctestCommandArgs\": \"\",\n      \"inheritEnvironments\": [ \"msvc_arm_x64\" ],\n      \"variables\": []\n    },\n    {\n      \"name\": \"arm64-Release\",\n      \"generator\": \"Ninja\",\n      \"configurationType\": \"RelWithDebInfo\",\n      \"buildRoot\": \"${projectDir}\\\\out\\\\build\\\\${name}\",\n      \"installRoot\": \"${projectDir}\\\\out\\\\install\\\\${name}\",\n      \"cmakeCommandArgs\": \"\",\n      \"buildCommandArgs\": \"-v\",\n      \"ctestCommandArgs\": \"\",\n      \"inheritEnvironments\": [ \"msvc_arm64_x64\" ],\n      \"variables\": []\n    }\n  ]\n}\n"
  },
  {
    "path": "LICENCE",
    "content": "                   GNU LESSER GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n\n  This version of the GNU Lesser General Public License incorporates\nthe terms and conditions of version 3 of the GNU General Public\nLicense, supplemented by the additional permissions listed below.\n\n  0. Additional Definitions.\n\n  As used herein, \"this License\" refers to version 3 of the GNU Lesser\nGeneral Public License, and the \"GNU GPL\" refers to version 3 of the GNU\nGeneral Public License.\n\n  \"The Library\" refers to a covered work governed by this License,\nother than an Application or a Combined Work as defined below.\n\n  An \"Application\" is any work that makes use of an interface provided\nby the Library, but which is not otherwise based on the Library.\nDefining a subclass of a class defined by the Library is deemed a mode\nof using an interface provided by the Library.\n\n  A \"Combined Work\" is a work produced by combining or linking an\nApplication with the Library.  The particular version of the Library\nwith which the Combined Work was made is also called the \"Linked\nVersion\".\n\n  The \"Minimal Corresponding Source\" for a Combined Work means the\nCorresponding Source for the Combined Work, excluding any source code\nfor portions of the Combined Work that, considered in isolation, are\nbased on the Application, and not on the Linked Version.\n\n  The \"Corresponding Application Code\" for a Combined Work means the\nobject code and/or source code for the Application, including any data\nand utility programs needed for reproducing the Combined Work from the\nApplication, but excluding the System Libraries of the Combined Work.\n\n  1. Exception to Section 3 of the GNU GPL.\n\n  You may convey a covered work under sections 3 and 4 of this License\nwithout being bound by section 3 of the GNU GPL.\n\n  2. Conveying Modified Versions.\n\n  If you modify a copy of the Library, and, in your modifications, a\nfacility refers to a function or data to be supplied by an Application\nthat uses the facility (other than as an argument passed when the\nfacility is invoked), then you may convey a copy of the modified\nversion:\n\n   a) under this License, provided that you make a good faith effort to\n   ensure that, in the event an Application does not supply the\n   function or data, the facility still operates, and performs\n   whatever part of its purpose remains meaningful, or\n\n   b) under the GNU GPL, with none of the additional permissions of\n   this License applicable to that copy.\n\n  3. Object Code Incorporating Material from Library Header Files.\n\n  The object code form of an Application may incorporate material from\na header file that is part of the Library.  You may convey such object\ncode under terms of your choice, provided that, if the incorporated\nmaterial is not limited to numerical parameters, data structure\nlayouts and accessors, or small macros, inline functions and templates\n(ten or fewer lines in length), you do both of the following:\n\n   a) Give prominent notice with each copy of the object code that the\n   Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the object code with a copy of the GNU GPL and this license\n   document.\n\n  4. Combined Works.\n\n  You may convey a Combined Work under terms of your choice that,\ntaken together, effectively do not restrict modification of the\nportions of the Library contained in the Combined Work and reverse\nengineering for debugging such modifications, if you also do each of\nthe following:\n\n   a) Give prominent notice with each copy of the Combined Work that\n   the Library is used in it and that the Library and its use are\n   covered by this License.\n\n   b) Accompany the Combined Work with a copy of the GNU GPL and this license\n   document.\n\n   c) For a Combined Work that displays copyright notices during\n   execution, include the copyright notice for the Library among\n   these notices, as well as a reference directing the user to the\n   copies of the GNU GPL and this license document.\n\n   d) Do one of the following:\n\n       0) Convey the Minimal Corresponding Source under the terms of this\n       License, and the Corresponding Application Code in a form\n       suitable for, and under terms that permit, the user to\n       recombine or relink the Application with a modified version of\n       the Linked Version to produce a modified Combined Work, in the\n       manner specified by section 6 of the GNU GPL for conveying\n       Corresponding Source.\n\n       1) Use a suitable shared library mechanism for linking with the\n       Library.  A suitable mechanism is one that (a) uses at run time\n       a copy of the Library already present on the user's computer\n       system, and (b) will operate properly with a modified version\n       of the Library that is interface-compatible with the Linked\n       Version.\n\n   e) Provide Installation Information, but only if you would otherwise\n   be required to provide such information under section 6 of the\n   GNU GPL, and only to the extent that such information is\n   necessary to install and execute a modified version of the\n   Combined Work produced by recombining or relinking the\n   Application with a modified version of the Linked Version. (If\n   you use option 4d0, the Installation Information must accompany\n   the Minimal Corresponding Source and Corresponding Application\n   Code. If you use option 4d1, you must provide the Installation\n   Information in the manner specified by section 6 of the GNU GPL\n   for conveying Corresponding Source.)\n\n  5. Combined Libraries.\n\n  You may place library facilities that are a work based on the\nLibrary side by side in a single library together with other library\nfacilities that are not Applications and are not covered by this\nLicense, and convey such a combined library under terms of your\nchoice, if you do both of the following:\n\n   a) Accompany the combined library with a copy of the same work based\n   on the Library, uncombined with any other library facilities,\n   conveyed under the terms of this License.\n\n   b) Give prominent notice with the combined library that part of it\n   is a work based on the Library, and explaining where to find the\n   accompanying uncombined form of the same work.\n\n  6. Revised Versions of the GNU Lesser General Public License.\n\n  The Free Software Foundation may publish revised and/or new versions\nof the GNU Lesser General Public License from time to time. Such new\nversions will be similar in spirit to the present version, but may\ndiffer in detail to address new problems or concerns.\n\n  Each version is given a distinguishing version number. If the\nLibrary as you received it specifies that a certain numbered version\nof the GNU Lesser General Public License \"or any later version\"\napplies to it, you have the option of following the terms and\nconditions either of that published version or of any later version\npublished by the Free Software Foundation. If the Library as you\nreceived it does not specify a version number of the GNU Lesser\nGeneral Public License, you may choose any version of the GNU Lesser\nGeneral Public License ever published by the Free Software Foundation.\n\n  If the Library as you received it specifies that a proxy can decide\nwhether future versions of the GNU Lesser General Public License shall\napply, that proxy's public statement of acceptance of any version is\npermanent authorization for you to choose that version for the\nLibrary.\n"
  },
  {
    "path": "README.md",
    "content": "WinBtrfs v1.9\n-------------\n\nWinBtrfs is a Windows driver for the next-generation Linux filesystem Btrfs.\nA reimplementation from scratch, it contains no code from the Linux kernel,\nand should work on any version from Windows XP onwards. It is also included\nas part of the free operating system [ReactOS](https://www.reactos.org/).\n\nIf your Btrfs filesystem is on a MD software RAID device created by Linux, you\nwill also need [WinMD](https://github.com/maharmstone/winmd) to get this to appear\nunder Windows.\n\nSee also [Quibble](https://github.com/maharmstone/quibble), an experimental\nbootloader allowing Windows to boot from Btrfs, and [Ntfs2btrfs](https://github.com/maharmstone/ntfs2btrfs),\na tool which allows in-place conversion of NTFS filesystems.\n\nFirst, a disclaimer:\n\nYou use this software at your own risk. I take no responsibility for any damage\nit may do to your filesystem. It ought to be suitable for day-to-day use, but\nmake sure you take backups anyway.\n\nEverything here is released under the GNU Lesser General Public Licence (LGPL);\nsee the file LICENCE for more info. You are encouraged to play about with the\nsource code as you will, and I'd appreciate a note (mark@harmstone.com) if you\ncome up with anything nifty.\n\nSee at the end of this document for copyright details of third-party code that's\nincluded here.\n\nFeatures\n--------\n\n* Reading and writing of Btrfs filesystems\n* Basic RAID: RAID0, RAID1, and RAID10\n* Advanced RAID: RAID5 and RAID6\n* Caching\n* Discovery of Btrfs partitions, even if Windows would normally ignore them\n* Getting and setting of Access Control Lists (ACLs), using the xattr\n  security.NTACL\n* Alternate Data Streams (e.g. :Zone.Identifier is stored as the xattr\n  user.Zone.Identifier)\n* Mappings from Linux users to Windows ones (see below)\n* Symlinks and other reparse points\n* Shell extension to identify and create subvolumes, including snapshots\n* Hard links\n* Sparse files\n* Free-space cache\n* Preallocation\n* Asynchronous reading and writing\n* Partition-less Btrfs volumes\n* Per-volume registry mount options (see below)\n* zlib compression\n* LZO compression\n* LXSS (\"Ubuntu on Windows\") support\n* Balancing (including resuming balances started on Linux)\n* Device addition and removal\n* Creation of new filesystems with `mkbtrfs.exe` and `ubtrfs.dll`\n* Scrubbing\n* TRIM/DISCARD\n* Reflink copy\n* Subvol send and receive\n* Degraded mounts\n* Free space tree (compat_ro flag `free_space_cache`)\n* Shrinking and expanding\n* Passthrough of permissions etc. for LXSS\n* Zstd compression\n* Windows 10 case-sensitive directory flag\n* Oplocks\n* Metadata UUID incompat flag (Linux 5.0)\n* Three- and four-disk RAID1 (Linux 5.5)\n* New checksum types (xxhash, sha256, blake2) (Linux 5.5)\n* Block group tree (Linux 6.1)\n\nTodo\n----\n\n* Full fs-verity support (Linux 5.15)\n* Zoned support (Linux 5.11) (HM-SMR not supported on Windows?)\n* Defragmentation\n* Support for Btrfs quotas\n* Full transaction log support\n* Support for Windows transactions (TxF)\n\nInstallation\n------------\n\nTo install the driver, [download and extract the latest release](https://github.com/maharmstone/btrfs/releases),\nright-click btrfs.inf, and choose Install. The driver is signed, so should work out\nof the box on modern versions of Windows.\n\nIf you using Windows 10 or 11 and have Secure Boot enabled, you may have to make a Registry\nchange in order for the driver to be loaded - see [below](#secureboot). It's easier though\njust to turn off Secure Boot in your BIOS, unless you have a particular need for it. Bear in\nmind that Windows 11 soft-requires Secure Boot to be installed, but will work fine afterwords\nwith it turned off.\n\nWinBtrfs is also available on the following package managers:\n\n* [Chocolatey](https://chocolatey.org/packages/winbtrfs)\n```\nchoco install winbtrfs\n```\n* [Scoop](https://scoop.sh/#/apps?q=%22winbtrfs-np%22&s=0&d=1&o=true)\n```\nscoop bucket add nonportable\nscoop install winbtrfs-np -g\n```\n\nUninstalling\n------------\n\nIf you want to uninstall, from a command prompt run:\n\n```\nRUNDLL32.EXE SETUPAPI.DLL,InstallHinfSection DefaultUninstall 132 btrfs.inf\n```\n\nYou may need to give the full path to btrfs.inf.\n\nYou can also go to Device Manager, find \"Btrfs controller\" under\n\"Storage volumes\", right click and choose \"Uninstall\". Tick the checkbox to\nuninstall the driver as well, and let Windows reboot itself.\n\nIf you need to uninstall via the registry, open regedit and set the value of\nHKLM\\SYSTEM\\CurrentControlSet\\services\\btrfs\\Start to 4, to disable the service.\nAfter you reboot, you can then delete the btrfs key and remove\nC:\\Windows\\System32\\drivers\\btrfs.sys.\n\nCompilation\n-----------\n\nTo compile with Visual C++ 2019, open the directory and let CMake do its thing.\nIf you have the Windows DDK installed correctly, it should just work.\n\nTo compile with GCC on Linux, you will need a cross-compiler set up, for either\n`i686-w64-mingw32` or `x86_64-w64-mingw32`. Create a build directory, then use\neither `mingw-x86.cmake` or `mingw-amd64.cmake` as CMake toolchain files to\ngenerate your Makefile.\n\nMappings\n--------\n\nThe user mappings are stored in the registry key\nHKLM\\SYSTEM\\CurrentControlSet\\services\\btrfs\\Mappings. Create a DWORD with the\nname of your Windows SID (e.g. S-1-5-21-1379886684-2432464051-424789967-1001),\nand the value of your Linux uid (e.g. 1000). It will take effect next time the\ndriver is loaded.\n\nYou can find your current SID by running `wmic useraccount get name,sid`.\n\nSimilarly, the group mappings are stored in under GroupMappings. The default\nentry maps Windows' Users group to gid 100, which is usually \"users\" on Linux.\nYou can also specify user SIDs here to force files created by a user to belong\nto a certain group. The setgid flag also works as on Linux.\n\nNote that processes running under User Access Control tokens create files as\nthe BUILTIN\\Administrators SID (S-1-5-32-544), rather as a user account.\n\nLXSS (\"Ubuntu on Windows\" / \"Windows Subsystem for Linux\")\n----------------------------------------------------------\n\nThe driver will passthrough Linux metadata to recent versions of LXSS, but you\nwill have to let Windows know that you wish to do this. From a Bash prompt on\nWindows, edit `/etc/wsl.conf` to look like the following:\n\n```\n[automount]\nenabled = true\noptions = \"metadata\"\nmountFsTab = false\n```\n\nIt will then take effect next time you reboot. Yes, you should be able to chroot\ninto an actual Linux installation, if you wish.\n\nCommands\n--------\n\nThe DLL file shellbtrfs.dll provides the GUI interface, but it can also be used\nwith rundll32.exe to carry out some tasks from the command line, which may be\nuseful if you wish to schedule something to run periodically.\n\nBear in mind that rundll32 provides no mechanism to return any error codes, so\nany of these commands may fail silently.\n\n* `rundll32.exe shellbtrfs.dll,CreateSubvol <path>`\n\n* `rundll32.exe shellbtrfs.dll,CreateSnapshot <source> <destination>`\n\n* `rundll32.exe shellbtrfs.dll,ReflinkCopy <source> <destination>`\nThis also accepts wildcards, and any number of source files.\n\nThe following commands need various privileges, and so must be run as Administrator\nto work:\n\n* `rundll32.exe shellbtrfs.dll,SendSubvol <source> [-p <parent>] [-c <clone subvol>] <stream file>`\nThe -p and -c flags are as `btrfs send` on Linux. You can specify any number of\nclone subvolumes.\n\n* `rundll32.exe shellbtrfs.dll,RecvSubvol <stream file> <destination>`\n\n* `rundll32.exe shellbtrfs.dll,StartScrub <drive>`\n\n* `rundll32.exe shellbtrfs.dll,StopScrub <drive>`\n\nTroubleshooting\n---------------\n\n* How do I debug this?\n\nOn the releases page, there's zip files to download containing the PDBs. Or you\ncan try the symbols server http://symbols.burntcomma.com/ - in windbg, set your\nsymbol path to something like this:\n\n```symsrv*symsrv.dll*C:\\symbols*http://msdl.microsoft.com/download/symbols;symsrv*symsrv.dll*C:\\symbols*http://symbols.burntcomma.com```\n\n* The filenames are weird!\nor\n* I get strange errors on certain files or directories!\n\nThe driver assumes that all filenames are encoded in UTF-8. This should be the\ndefault on most setups nowadays - if you're not using UTF-8, it's probably worth\nlooking into converting your files.\n\n* <a name=\"secureboot\"></a>How do I get this working with Secure Boot turned on?\n\nFor the later versions of Windows 10, Microsoft introduced more onerous\nrequirements for signing, which seemingly aren't available for open-source drivers.\n\nTo work around this, go to `HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Control\\CI\\Policy` in Regedit,\ncreate a new DWORD value called `UpgradedSystem` and set to 1, and reboot.\n\nOr you could always just turn off Secure Boot in your BIOS settings.\n\n* The root of the drive isn't case-sensitive in LXSS\n\nThis is something Microsoft hardcoded into LXSS, presumably to stop people hosing\ntheir systems by running `mkdir /mnt/c/WiNdOwS`.\n\n* How do I change the drive letter?\n\nWith the shell extension installed, right-click the drive in Explorer, click Properties,\nand go to the Btrfs tab. There should be a button which allows you to change the drive\nletter.\n\n* I'm still having problems with drive letters\n\nIn Regedit, try deleting the relevant entries in `HKEY_LOCAL_MACHINE\\SYSTEM\\MountedDevices`,\nthen rebooting.\n\n* How do I format a partition as Btrfs?\n\nUse the included command line program mkbtrfs.exe. We can't add Btrfs to Windows' own\ndialog box, unfortunately, as its list of filesystems has been hardcoded. You can also\nrun `format /fs:btrfs`, if you don't need to set any Btrfs-specific options.\n\n* I can't reformat a mounted Btrfs filesystem\n\nIf Windows' Format dialog box refuses to appear, try running format.com with the /fs\nflag, e.g. `format /fs:ntfs D:`.\n\n* I can't mount a Synology NAS\n\nSynology seems to use LVM for its block devices. Until somebody writes an LVM driver\nfor Windows, you're out of luck.\n\n* I can't mount a Thecus NAS\n\nThecus uses Linux's MD raid for its block devices. You will need to install [WinMD](https://github.com/maharmstone/winmd)\nas well.\n\n* 64-bit Windows 7 won't load the driver\n\nMake sure that you have [KB3033929](https://www.microsoft.com/en-gb/download/details.aspx?id=46148) installed.\nOr consider installing from an \"escrow\" ISO which includes all updates.\n\n* The drive doesn't show up and Paragon software has been installed\n\nParagon's filesystem-reading software is known to disable automount. Disable or\nuninstall Paragon, then re-enable automount by running `diskpart` and typing\n`automount enable`.\n\n* The drive doesn't show up on very old versions of Windows\n\nOn very old versions of Windows (XP, Server 2003?), Windows ignores Linux partitions\nentirely. If this is the case for you, try running `fdisk` on Linux and changing your\npartition type from 83 to 7.\n\n* I can edit files on Windows that I shouldn't be able to\n\nThere's no mapping between Windows and POSIX permission models, they're too\ndifferent for this to be practical. If this bothers you, you can create a\nWindows ACL on files that you don't want to be able to edit.\n\nChangelog\n---------\n\nv1.9 (2024-03-15):\n* Added support for block group tree (Linux 6.1)\n* Fixed hang when system under heavy load\n* Added /blockgrouptree and /freespacetree options to mkbtrfs\n* Follow Linux in defaulting /noholes to on in mkbtrfs\n* Added support for CRC32C instructions on aarch64\n\nv1.8.2 (2023-01-10):\n* Fixed UAC not working\n* Fixed Smartlocker crash on Windows 11 22H2\n* Rejigged INF file to work better on Windows 11\n* Files now signed with SHA256 hash rather than SHA1\n\nv1.8.1 (2022-08-23):\n* Fixed use-after-free when flushing\n* Fixed crash when opening volume when AppLocker installed\n* Compression now disabled for no-COW files, as on Linux\n* Flushing now scales better on very fast drives\n* Fixed small files getting padded to 4,096 bytes by lazy writer\n* Added NoDataCOW registry option\n\nv1.8 (2022-03-12):\n* Added minimal support for fs-verity\n* Added test suite\n* Fixed incorrect disk usage statistics\n* Fixed potential crashes when renaming stream to file or file to stream\n* Fixed potential crashes when querying hard links on file\n* Fixed potential hang when opening oplocked file\n* Fixed minor issues also uncovered by test suite\n\nv1.7.9 (2021-10-02):\n* Fixed deadlock when mounting on Windows 11\n* Added support for BitLocker-encrypted volumes\n* Improved filename checks when renaming or creating hard links\n* Miscellaneous bug fixes\n\nv1.7.8.1 (2021-06-13):\n* Fixed bug preventing new directories from appearing in listings\n* Fixed Release version of driver still not working on XP\n\nv1.7.8 (2021-06-09):\n* Upgraded zstd to version 1.5.0\n* Fixed regression stopping driver from working under XP\n* Fixed compilation on clang\n* Fixed corruption issue when Linux mount option `inode_cache` had been used\n* Fixed recursion issue involving virtual directory \\\\$Root\n\nv1.7.7 (2021-04-12):\n* Fixed deadlock on high load\n* Fixed free space issue when installing Genshin Impact\n* Fixed issue when copying files with wildcards in command prompt\n* Increased speed of directory lookups\n\nv1.7.6 (2021-01-14):\n* Fixed race condition when booting with Quibble\n* No longer need to restart Windows after initial installation\n* Forced maximum file name to 255 UTF-8 characters, to match Linux driver\n* Fixed issue where directories could be created with trailing backslash\n* Fixed potential deadlock when Windows calls NtCreateSection during flush\n* Miscellaneous bug fixes\n\nv1.7.5 (2020-10-31):\n* Fixed text display issue in shell extension\n* Added support for mingw 8\n* Fixed LXSS permissions not working in new versions of Windows\n* Fixed issue where truncating an inline file wouldn't change its size\n* Fixed crash with Quibble where driver would try to use AVX2 before Windows had enabled it\n\nv1.7.4 (2020-08-23):\n* Fixed issue when running compressed EXEs\n* Changed build system to cmake\n* Upgraded zstd to version 1.4.5\n* Added support for FSCTL_GET_RETRIEVAL_POINTERS\n* Miscellaneous bug fixes\n\nv1.7.3 (2020-05-24):\n* Fixed crash when sending file change notifications\n* Improved symlink handling with LXSS\n* Added support for undocumented flag SL_IGNORE_READONLY_ATTRIBUTE\n* Fixed corruption caused by edge case, where address allocated and freed in same flush\n* Improved handling of free space tree\n* Improved handling of very full volumes\n* Fixed spurious warnings raised by GCC 10 static analyser\n* Replaced multiplications and divisions with bit shift operations where appropriate\n* Fixed combobox stylings in shell extension\n\nv1.7.2 (2020-04-10):\n* Added more fixes for booting from Btrfs on Windows 10\n* Fixed occasional deadlock when deleting or closing files on Windows 10 1909\n* Fixed crash when reading large ADSes\n* Fixed occasional crash when writing files on RAID5/6\n* Miscellaneous bug fixes\n\nv1.7.1 (2020-03-02):\n* Fixed crash when reading beyond end of file\n* Fixed spurious checksum errors when doing unaligned read\n\nv1.7 (2020-02-26):\n* Added support for metadata_uuid incompat flag (Linux 5.0)\n* Added support for three- and four-disk RAID1 (Linux 5.5)\n* Added support for new checksum types: xxhash, sha256, blake2 (Linux 5.5)\n* Greatly increased checksumming speed\n* Greatly increased compression and decompression speed\n* Fixed bug causing incorrect free-space reporting when data is DUP\n* Fixed issue creating directories on LXSS when `case=dir` option set\n\nv1.6 (2020-02-04):\n* Added experimental (i.e. untested) ARM support (thanks to [DjArt](https://github.com/DjArt) for this)\n* Added fixes for booting from Btrfs on Windows 10\n* Volumes will now get remounted if changed while Windows is asleep or hibernating\n* Fixed corruption when mounting volume that hasn't been unmounted cleanly by Linux\n* Fixed crash when deleting subvolume\n\nv1.5 (2019-11-10):\n* More fixes for booting from Btrfs\n* Added virtual $Root directory (see \"NoRootDir\" below)\n* Added support for Windows XP\n* Added support for renaming alternative data streams\n* Added oplock support\n* Fixed potential deadlock on boot\n* Fixed possible crash on shutdown\n* Fixed a bunch of memory leaks\n* Many other miscellaneous bug fixes\n\nv1.4 (2019-08-31):\n* Added fragmentation percentage to property sheet\n* Added support for Windows Server 2003 and Windows Vista\n* Added pagefile support\n* Improved support for file locking\n* Added support for booting from Btrfs on Windows Server 2003 (see https://www.youtube.com/watch?v=-5E2CHmHEUs)\n* Fixed issue where driver could open same inode twice\n* Other miscellaneous bug fixes\n\nv1.3 (2019-06-10):\n* Added support for new rename and delete functions introduced to Windows 10\n* Added support for Windows 10's flag for case-sensitive directories\n* Changed free-space calculation method to be more like that of the Linux driver\n* Added more support for 128-bit file IDs\n* Fixed bug causing outdated root items\n* Fixed bug preventing writing to VHDs\n\nv1.2.1 (2019-05-06):\n* Reverted commit affecting the creation of streams\n\nv1.2 (2019-05-05):\n* Dramatic speed increase when opening many small files, such as with a Git repository\n* Fixed crash on surprise removals of removable devices\n* Added ability to change drive letters easily\n* No longer creates free-space cache for very small chunks, so as not to confuse the Linux driver\n* Fixed corruption when very large file created and then immediately deleted\n* Minor bug fixes\n\nv1.1 (2018-12-15):\n* Support for Zstd compression\n* Passthrough of Linux metadata to LXSS\n* Refactored shell extension\n* Fixed memory leaks\n* Many other bug fixes\n\nv1.0.2 (2018-05-19):\n* Minor bug fixes\n\nv1.0.1 (2017-10-15):\n* Fixed deadlock\n* Binaries now signed\n* Minor bug fixes\n\nv1.0 (2017-09-04):\n* First non-beta release!\n* Degraded mounts\n* New free space cache (compat_ro flag `free_space_cache`)\n* Shrinking and expanding of volumes\n* Registry options now re-read when changed, rather than just on startup\n* Improved balancing on very full filesystems\n* Fixed problem preventing user profile directory being stored on btrfs on Windows 8 and above\n* Better Plug and Play support\n* Miscellaneous bug fixes\n\nv0.10 (2017-05-02):\n* Reflink copy\n* Sending and receiving subvolumes\n* Group mappings (see Mappings section above)\n* Added commands for scripting etc. (see Commands section above)\n* Fixed an issue preventing mounting on non-PNP devices, such as VeraCrypt\n* Fixed an issue preventing new versions of LXSS from working\n* Fixed problem with the ordering of extent refs, which caused problems on Linux but wasn't picked up by `btrfs check`\n* Added support for reading compressed inline extents\n* Many miscellaneous bug fixes\n\nv0.9 (2017-03-05):\n* Scrubbing\n* TRIM/DISCARD\n* Better handling of multi-device volumes\n* Performance increases when reading from RAID filesystems\n* No longer lies about being NTFS, except when it has to\n* Volumes will now go readonly if there is an unrecoverable error, rather than blue-screening\n* Filesystems can now be created with Windows' inbuilt format.com\n* Zlib upgraded to version 1.2.11\n* Miscellaneous performance increases\n* Miscellaneous bug fixes\n\nv0.8 (2016-12-30):\n* Volume property sheet, for:\n * Balances\n * Adding and removing devices\n * Showing disk usage, i.e. the equivalent to `btrfs fi usage`\n* Checksums now calculated in parallel where appropriate\n* Creation of new filesystems, with mkbtrfs.exe\n* Plug and play support for RAID devices\n* Disk usage now correctly allocated to processes in taskmgr\n* Performance increases\n* Miscellaneous bug fixes\n\nv0.7 (2016-10-24):\n* Support for RAID5/6 (incompat flag `raid56`)\n* Seeding support\n* LXSS (\"Ubuntu on Windows\") support\n* Support for Windows Extended Attributes\n* Improved removable device support\n* Better snapshot support\n* Recovery from RAID checksum errors\n* Fixed issue where creating a lot of new files was taking a long time\n* Miscellaneous speed increases and bug fixes\n\nv0.6 (2016-08-21):\n* Compression support (both zlib and lzo)\n* Mixed groups support\n* No-holes support\n* Added inode property sheet to shell extension\n* Many more mount options (see below)\n* Better support for removable devices\n* Page file support\n* Many miscellaneous bug fixes\n\nv0.5 (2016-07-24):\n* Massive speed increases (from \"sluggish\" to \"blistering\")\n* Massive stability improvements\n* RAID support: RAID0, RAID1, and RAID10\n* Asynchronous reading and writing\n* Partition-less Btrfs volumes\n* Windows sparse file support\n* Object ID support\n* Beginnings of per-volume mount options\n* Security improvements\n* Notification improvements\n* Miscellaneous bug fixes\n\nv0.4 (2016-05-02):\n* Subvolume creation and deletion\n* Snapshots\n* Preallocation\n* Reparse points\n* Hard links\n* Plug and play\n* Free-space cache\n* Fix problems preventing volume from being shared over the network\n* Miscellaneous bug fixes\n\nv0.3 (2016-03-25):\n* Bug fixes:\n * Fixed crashes when metadata blocks were SINGLE, such as on SSDs\n * Fixed crash when splitting an internal tree\n * Fixed tree traversal failing when first item in tree had been deleted\n * Fixed emptying out of whole tree (probably only relevant to checksum tree)\n * Fixed \"incorrect local backref count\" message appearing in `btrfs check`\n * Miscellaneous other fixes\n* Added beginnings of shell extension, which currently only changes the icon of subvolumes\n\nv0.2 (2016-03-13):\n* Bug fix release:\n * Check memory allocations succeed\n * Check tree items are the size we're expecting\n * Added rollbacks, so failed operations are completely undone\n * Fixed driver claiming all unrecognized partitions (thanks Pierre Schweitzer)\n * Fixed deadlock within `CcCopyRead`\n * Fixed changing properties of a JPEG within Explorer\n * Lie about FS type, so UAC works\n * Many, many miscellaneous bug fixes\n* Rudimentary security support\n* Debug log support (see below)\n\nv0.1 (2016-02-21):\n* Initial alpha release.\n\nDebug log\n---------\n\nWinBtrfs has three levels of debug messages: errors and FIXMEs, warnings, and traces.\nThe release version of the driver only displays the errors and FIXMEs, which it logs\nvia `DbgPrint`. You can view these messages via the Microsoft program DebugView, available\nat https://technet.microsoft.com/en-gb/sysinternals/debugview.\n\nIf you want to report a problem, it'd be of great help if you could also attach a full\ndebug log. To do this, you will need to use the debug versions of the drivers; copy the files\nin Debug\\x64 or Debug\\x86 into x64 or x86. You will also need to set the registry entries in\nHKLM\\SYSTEM\\CurrentControlSet\\Services\\btrfs:\n\n* `DebugLogLevel` (DWORD): 0 for no messages, 1 for errors and FIXMEs, 2 for warnings also,\nand 3 for absolutely everything, including traces.\n* `LogDevice` (string, optional): the serial device you want to output to, such as\n`\\Device\\Serial0`. This is probably only useful on virtual machines.\n* `LogFile` (string, optional): the file you wish to output to, if `LogDevice` isn't set.\nBear in mind this is a kernel filename, so you'll have to prefix it with \"\\\\??\\\\\" (e.g.,\n\"\\\\??\\\\C:\\\\btrfs.log\"). It probably goes without saying, but don't store this on a volume the\ndriver itself is using, or you'll cause an infinite loop.\n\nMount options\n-------------\n\nThe driver will create subkeys in the registry under HKLM\\SYSTEM\\CurrentControlSet\\Services\\btrfs\nfor each mounted filesystem, named after its UUID. If you're unsure which UUID refers to which\nvolume, you can check using `btrfs fi show` on Linux. You can add per-volume mount options to this\nsubkey, which will take effect on reboot. If a value is set in the key above this, it will use this\nby default.\n\n* `Ignore` (DWORD): set this to 1 to tell the driver not to attempt loading this filesystem. With the\n`Readonly` flag, this is probably redundant.\n\n* `Readonly` (DWORD): set this to 1 to tell the driver not to allow writing to this volume. This is\nthe equivalent of the `ro` flag on Linux.\n\n* `Compress` (DWORD): set this to 1 to tell the driver to write files as compressed by default. This is\nthe equivalent of the `compress` flag on Linux.\n\n* `CompressForce` (DWORD): set this to 1 to force compression, i.e. to ignore the `nocompress` inode\nflag and even attempt compression of incompressible files. This isn't a good idea, but is the equivalent\nof the `compress-force` flag on Linux.\n\n* `CompressType` (DWORD): set this to 1 to prefer zlib compression, 2 to prefer lzo compression, or 3\nto prefer zstd compression. The default is 0, which uses zstd or lzo compression if the incompat flags\nare set, and zlib otherwise.\n\n* `FlushInterval` (DWORD): the interval in seconds between metadata flushes. The default is 30, as on Linux -\nthe parameter is called `commit` there.\n\n* `ZlibLevel` (DWORD): a number between -1 and 9, which determines how much CPU time is spent trying to\ncompress files. You might want to fiddle with this if you have a fast CPU but a slow disk, or vice versa.\nThe default is 3, which is the hard-coded value on Linux.\n\n* `MaxInline` (DWORD): the maximum size that will be allowed for \"inline\" files, i.e. those stored in the\nmetadata. The default is 2048, which is also the default on modern versions of Linux - the parameter is\ncalled `max_inline` there. It will be clipped to the maximum value, which unless you've changed your node\nsize will be a shade under 16 KB.\n\n* `SubvolId` (QWORD): the ID of the subvolume that we will attempt to mount as the root. If it doesn't\nexist, this parameter will be silently ignored. The subvolume ID can be found on the inode property\nsheet; it's in hex there, as opposed to decimal on the Linux tools. The default is whatever has been set\nvia `btrfs subvolume set-default`; or, failing that, subvolume 5. The equivalent parameter on Linux is\ncalled `subvolid`.\n\n* `SkipBalance` (DWORD): set to 1 to tell the driver not to attempt resuming a balance which was running\nwhen the system last powered down. The default is 0. The equivalent parameter on Linux is `skip_balance`.\n\n* `NoPNP` (DWORD): useful for debugging only, this forces any volumes to appear rather than exposing them\nvia the usual Plug and Play method.\n\n* `ZstdLevel` (DWORD): Zstd compression level, default 3.\n\n* `NoTrim` (DWORD): set this to 1 to disable TRIM support.\n\n* `AllowDegraded` (DWORD): set this to 1 to allow mounting a degraded volume, i.e. one with a device\nmissing. You are strongly advised not to enable this unless you need to.\n\n* `NoRootDir` (DWORD): if you have changed your default subvolume, either natively or by a registry option,\nthere will be a hidden directory called $Root which points to where the root would normally be. Set this\nvalue to 1 to prevent this appearing.\n\n* `NoDataCOW` (DWORD): set this to 1 to disable copy-on-write for new files. This is the equivalent of the\n`nodatacow` flag on Linux.\n\nContact\n-------\n\nI'd appreciate any feedback you might have, positive or negative:\nmark@harmstone.com.\n\nCopyright\n---------\n\nThis code contains portions of the following software:\n\n### Zlib\n\n  Copyright (C) 1995-2017 Jean-loup Gailly and Mark Adler\n\n  This software is provided 'as-is', without any express or implied\n  warranty.  In no event will the authors be held liable for any damages\n  arising from the use of this software.\n\n  Permission is granted to anyone to use this software for any purpose,\n  including commercial applications, and to alter it and redistribute it\n  freely, subject to the following restrictions:\n\n  1. The origin of this software must not be misrepresented; you must not\n     claim that you wrote the original software. If you use this software\n     in a product, an acknowledgment in the product documentation would be\n     appreciated but is not required.\n  2. Altered source versions must be plainly marked as such, and must not be\n     misrepresented as being the original software.\n  3. This notice may not be removed or altered from any source distribution.\n\n### LZO\n\nWinBtrfs contains portions of an early version of lzo, which is copyright 1996\nMarkus Oberhumer. Modern versions are licensed under the GPL, but this was\nlicensed under the LGPL, so I believe it is okay to use.\n\n### Zstd\n\nCopyright (c) 2016-present, Facebook, Inc. All rights reserved.\n\nRedistribution and use in source and binary forms, with or without modification,\nare permitted provided that the following conditions are met:\n\n * Redistributions of source code must retain the above copyright notice, this\n   list of conditions and the following disclaimer.\n\n * Redistributions in binary form must reproduce the above copyright notice,\n   this list of conditions and the following disclaimer in the documentation\n   and/or other materials provided with the distribution.\n\n * Neither the name Facebook nor the names of its contributors may be used to\n   endorse or promote products derived from this software without specific\n   prior written permission.\n\nTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND\nANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED\nWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE\nDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR\nANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES\n(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;\nLOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON\nANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT\n(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS\nSOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n### BLAKE2\n\n[https://github.com/BLAKE2/BLAKE2](https://github.com/BLAKE2/BLAKE2) (public domain)\n\n### SHA256\n\n[https://github.com/amosnier/sha-2](https://github.com/amosnier/sha-2) (public domain)\n"
  },
  {
    "path": "btrfs-dump.pl",
    "content": "#!/usr/bin/perl\n\n# Quick and dirty btrfs tree dumper. Great for diff'ing, which btrfs-debug-tree isn't...\n# Do something like:\n#\n# qemu-nbd -r -n -c /dev/nbd0 ~/vms/win7/win7-32.img ; sleep 1 ; chmod 666 /dev/nbd0p3\n# ./btrfs-dump.pl /dev/nbd0p3 > dump2.txt\n# diff -u dump1.txt dump2.txt > diff2.txt\n\n# Like btrfs.h, I'm disclaiming any copyright on this file, but I'd appreciate\n# hearing about what you do with it: mark@harmstone.com.\n\nuse Data::Dumper;\nuse strict;\nuse integer;\n\nif (scalar(@ARGV) < 1) {\n    my @dp = split(/\\//, $0);\n\n    print \"Usage: \" . $dp[$#dp] . \" [BLOCKDEVICE]\\n\";\n    exit;\n}\n\nmy %devs = ();\nfor (my $i = 1; $i <= $#ARGV; $i++) {\n    my ($file, $sb);\n\n    open($file, $ARGV[$i]) || die \"Error opening \" . $ARGV[$i] . \": $!\";\n    binmode($file);\n\n    seek($file, 0x10000, 0);\n    read($file, $sb, 0x1000);\n    my @b = unpack(\"Vx28a16QQA8QQQQQQQQQVVVVVQQQQvCCCa98a256QQx240a2048a672\", $sb);\n\n    if ($b[4] ne \"_BHRfS_M\") {\n        die $ARGV[$i] . \": not Btrfs\";\n    }\n\n    my @di = unpack(\"QQQVVVQQQVCCa16a16\", $b[27]);\n    $devs{$di[0]} = $file;\n}\n\nmy ($f, $chunktree, $roottree, $logtree, $nodesize, $blocksize);\n\nopen($f, $ARGV[0]) || die \"Error opening \" . $ARGV[0] . \": $!\";\nbinmode($f);\n\nmy %roots = ();\nmy %logroots = ();\nmy @l2p = ();\nmy @l2p_bs = ();\nmy $csum_type;\n\nread_superblock($f);\n\nprint \"CHUNK:\\n\";\ndump_tree($chunktree, \"\", 1);\nprint \"\\n\";\n\nprint \"ROOT:\\n\";\ndump_tree($roottree, \"\", 0);\nprint \"\\n\";\n\nif ($logtree != 0) {\n    print \"LOG:\\n\";\n    dump_tree($logtree, \"\", 0);\n    print \"\\n\";\n}\n\nmy @rs = sort { $a <=> $b } (keys(%roots));\n\nforeach my $r (@rs) {\n    printf(\"Tree %x:\\n\", $r);\n    dump_tree($roots{$r}, \"\");\n    print \"\\n\";\n}\n\nmy @lrs = sort { $a <=> $b } (keys(%logroots));\n\nforeach my $lr (@lrs) {\n    printf(\"Tree %x (log):\\n\", $lr);\n    dump_tree($logroots{$lr}, \"\");\n    print \"\\n\";\n}\n\nclose($f);\n\nsub incompat_flags {\n    my ($f) = @_;\n    my @l;\n\n    if ($f & 0x1) {\n        push @l, \"mixed_backref\";\n        $f &= ~0x1;\n    }\n\n    if ($f & 0x2) {\n        push @l, \"default_subvol\";\n        $f &= ~0x2;\n    }\n\n    if ($f & 0x4) {\n        push @l, \"mixed_groups\";\n        $f &= ~0x4;\n    }\n\n    if ($f & 0x8) {\n        push @l, \"compress_lzo\";\n        $f &= ~0x8;\n    }\n\n    if ($f & 0x10) {\n        push @l, \"compress_zstd\";\n        $f &= ~0x10;\n    }\n\n    if ($f & 0x20) {\n        push @l, \"big_metadata\";\n        $f &= ~0x20;\n    }\n\n    if ($f & 0x40) {\n        push @l, \"extended_iref\";\n        $f &= ~0x40;\n    }\n\n    if ($f & 0x80) {\n        push @l, \"raid56\";\n        $f &= ~0x80;\n    }\n\n    if ($f & 0x100) {\n        push @l, \"skinny_metadata\";\n        $f &= ~0x100;\n    }\n\n    if ($f & 0x200) {\n        push @l, \"no_holes\";\n        $f &= ~0x200;\n    }\n\n    if ($f & 0x400) {\n        push @l, \"metadata_uuid\";\n        $f &= ~0x400;\n    }\n\n    if ($f & 0x800) {\n        push @l, \"raid1c34\";\n        $f &= ~0x800;\n    }\n\n    if ($f & 0x1000) {\n        push @l, \"zoned\";\n        $f &= ~0x1000;\n    }\n\n    if ($f & 0x2000) {\n        push @l, \"extent_tree_v2\";\n        $f &= ~0x2000;\n    }\n\n    if ($f & 0x4000) {\n        push @l, \"raid_stripe_tree\";\n        $f &= ~0x4000;\n    }\n\n    if ($f & 0x10000) {\n        push @l, \"simple_quota\";\n        $f &= ~0x10000;\n    }\n\n    if ($f != 0 || $#l == -1) {\n        push @l, sprintf(\"%x\", $f);\n    }\n\n    return join(',', @l);\n}\n\nsub compat_ro_flags {\n    my ($f) = @_;\n    my @l;\n\n    if ($f & 1) {\n        push @l, \"free_space_tree\";\n        $f &= ~1;\n    }\n\n    if ($f & 2) {\n        push @l, \"free_space_tree_valid\";\n        $f &= ~2;\n    }\n\n    if ($f & 4) {\n        push @l, \"verity\";\n        $f &= ~4;\n    }\n\n    if ($f & 8) {\n        push @l, \"block_group_tree\";\n        $f &= ~8;\n    }\n\n    if ($f != 0 || $#l == -1) {\n        push @l, sprintf(\"%x\", $f);\n    }\n\n    return join(',', @l);\n}\n\nsub format_super_flags {\n    my ($f) = @_;\n    my @l;\n\n    if ($f & 1) {\n        push @l, \"written\";\n        $f &= ~1;\n    }\n\n    if ($f & 2) {\n        push @l, \"reloc\";\n        $f &= ~2;\n    }\n\n    if ($f & 4) {\n        push @l, \"error\";\n        $f &= ~4;\n    }\n\n    if ($f & 0x100000000) {\n        push @l, \"seeding\";\n        $f &= ~0x100000000;\n    }\n\n    if ($f & 0x200000000) {\n        push @l, \"metadump\";\n        $f &= ~0x200000000;\n    }\n\n    if ($f & 0x400000000) {\n        push @l, \"metadump_v2\";\n        $f &= ~0x400000000;\n    }\n\n    if ($f & 0x800000000) {\n        push @l, \"changing_fsid\";\n        $f &= ~0x800000000;\n    }\n\n    if ($f & 0x1000000000) {\n        push @l, \"changing_fsid_v2\";\n        $f &= ~0x1000000000;\n    }\n\n    if ($f & 0x4000000000) {\n        push @l, \"changing_bg_tree\";\n        $f &= ~0x4000000000;\n    }\n\n    if ($f & 0x8000000000) {\n        push @l, \"changing_data_csum\";\n        $f &= ~0x8000000000;\n    }\n\n    if ($f & 0x10000000000) {\n        push @l, \"changin_meta_csum\";\n        $f &= ~0x10000000000;\n    }\n\n    if ($f != 0 || $#l == -1) {\n        push @l, sprintf(\"%x\", $f);\n    }\n\n    return join(',', @l);\n}\n\nsub csum_type {\n    my ($t) = @_;\n\n    if ($t == 0) {\n        return \"crc32\";\n    } elsif ($t == 1) {\n        return \"xxhash\";\n    } elsif ($t == 2) {\n        return \"sha256\";\n    } elsif ($t == 3) {\n        return \"blake2\";\n    } else {\n        return sprintf(\"%x\", $t);\n    }\n}\n\nsub read_superblock {\n    my ($f) = @_;\n    my ($sb, @b, @b2, @di, $csum);\n\n    seek($f, 0x10000, 0);\n    read($f, $sb, 0x1000);\n    ($roottree, $chunktree, $logtree) = unpack(\"x80QQQ\", $sb);\n    @b = unpack(\"a32a16QQa8QQQQQQQQQVVVVVQQQQvCCCa98A256QQa16x224a2048a672\", $sb);\n    @di = unpack(\"QQQVVVQQQVCCa16a16\", $b[27]);\n\n    $csum_type = $b[23];\n\n    if ($csum_type == 1) {\n        $csum = sprintf(\"%016x\", unpack(\"Q\", $b[0]));\n    } elsif ($csum_type == 2 || $csum_type == 3) {\n        $csum = sprintf(\"%016x%016x%016x%016x\", unpack(\"QQQQ\", $b[0]));\n    } else {\n        $csum = sprintf(\"%08x\", unpack(\"V\", $b[0]));\n    }\n\n    printf(\"superblock csum=%s fsid=%s bytenr=%x flags=%s magic=%s generation=%x root=%x chunk_root=%x log_root=%x log_root_transid=%x total_bytes=%x bytes_used=%x root_dir_objectid=%x num_devices=%x sectorsize=%x nodesize=%x leafsize=%x stripesize=%x sys_chunk_array_size=%x chunk_root_generation=%x compat_flags=%x compat_ro_flags=%s incompat_flags=%s csum_type=%s root_level=%x chunk_root_level=%x log_root_level=%x (dev_item devid=%x total_bytes=%x bytes_used=%x io_align=%x io_width=%x sector_size=%x type=%x generation=%x start_offset=%x dev_group=%x seek_speed=%x bandwidth=%x uuid=%s fsid=%s) label=%s cache_generation=%x uuid_tree_generation=%x metadata_uuid=%s\\n\", $csum, format_uuid($b[1]), $b[2], format_super_flags($b[3]), $b[4], $b[5], $b[6], $b[7], $b[8], $b[9], $b[10], $b[11], $b[12], $b[13], $b[14], $b[15], $b[16], $b[17], $b[18], $b[19], $b[20], compat_ro_flags($b[21]), incompat_flags($b[22]), csum_type($b[23]), $b[24], $b[25], $b[26], $di[0], $di[1], $di[2], $di[3], $di[4], $di[5], $di[6], $di[7], $di[8], $di[9], $di[10], $di[11], format_uuid($di[12]), format_uuid($di[13]), $b[28], $b[29], $b[30], format_uuid($b[31]));\n\n    my $devid = format_uuid($di[12]);\n\n    $blocksize = $b[14];\n    $nodesize = $b[15];\n\n    $devs{$di[0]} = $f;\n\n    my $bootstrap = substr($b[32], 0, $b[18]);\n\n    while (length($bootstrap) > 0) {\n        #print Dumper($bootstrap).\"\\n\";\n        @b2 = unpack(\"QCQ\", $bootstrap);\n        printf(\"bootstrap %x,%x,%x\\n\", @b2[0], @b2[1], @b2[2]);\n        $bootstrap = substr($bootstrap, 0x11);\n\n        my @c = unpack(\"QQQQVVVvv\", $bootstrap);\n        dump_item(0xe4, substr($bootstrap, 0, 0x30 + ($c[7] * 0x20)), \"\", 0);\n\n        $bootstrap = substr($bootstrap, 0x30);\n\n        my %obj;\n\n        $obj{'offset'} = $b2[2];\n        $obj{'size'} = $c[0];\n        $obj{'type'} = $c[3];\n        $obj{'num_stripes'} = $c[7];\n        $obj{'stripe_len'} = $c[2];\n        $obj{'sub_stripes'} = $c[8];\n\n        for (my $i = 0; $i < $c[7]; $i++) {\n            my @cis = unpack(\"QQa16\", $bootstrap);\n            $bootstrap = substr($bootstrap, 0x20);\n\n            $obj{'stripes'}[$i]{'physoffset'} = $cis[1];\n            $obj{'stripes'}[$i]{'devid'} = $cis[0];\n        }\n\n        push @l2p_bs, \\%obj;\n    }\n\n    my $backups = $b[33];\n\n    while (length($backups) > 0) {\n        my $backup = substr($backups, 0, 168);\n        $backups = substr($backups, 168);\n\n        my @b3 = unpack(\"QQQQQQQQQQQQQQQx32CCCCCCx10\", $backup);\n\n        printf(\"backup tree_root=%x tree_root_gen=%x chunk_root=%x chunk_root_gen=%x extent_root=%x extent_root_gen=%x fs_root=%x fs_root_gen=%x dev_root=%x dev_root_gen=%x csum_root=%x csum_root_gen=%x total_bytes=%x bytes_used=%x num_devices=%x tree_root_level=%x chunk_root_level=%x extent_root_level=%x fs_root_level=%x dev_root_level=%x csum_root_level=%x\\n\", @b3);\n    }\n\n    print \"\\n\";\n}\n\nsub format_uuid {\n    my ($s) = @_;\n    my @b = unpack(\"VVVV\", $s);\n\n    return sprintf(\"%08x%08x%08x%08x\", $b[3], $b[2], $b[1], $b[0]);\n}\n\nsub format_time {\n    my ($t, $ns) = @_;\n\n    my @tb = gmtime($t);\n\n    return sprintf(\"%04u-%02u-%02uT%02u:%02u:%02u\", $tb[5] + 1900, $tb[4] + 1, $tb[3], $tb[2], $tb[1], $tb[0]);\n}\n\nsub inode_flags {\n    my ($flags) = @_;\n    my @l = ();\n\n    if ($flags & 1) {\n        push @l, \"nodatasum\";\n        $flags &= ~1;\n    }\n\n    if ($flags & 2) {\n        push @l, \"nodatacow\";\n        $flags &= ~2;\n    }\n\n    if ($flags & 4) {\n        push @l, \"readonly\";\n        $flags &= ~4;\n    }\n\n    if ($flags & 8) {\n        push @l, \"nocompress\";\n        $flags &= ~8;\n    }\n\n    if ($flags & 16) {\n        push @l, \"prealloc\";\n        $flags &= ~16;\n    }\n\n    if ($flags & 32) {\n        push @l, \"sync\";\n        $flags &= ~32;\n    }\n\n    if ($flags & 64) {\n        push @l, \"immutable\";\n        $flags &= ~64;\n    }\n\n    if ($flags & 128) {\n        push @l, \"append\";\n        $flags &= ~128;\n    }\n\n    if ($flags & 256) {\n        push @l, \"nodump\";\n        $flags &= ~256;\n    }\n\n    if ($flags & 512) {\n        push @l, \"noatime\";\n        $flags &= ~512;\n    }\n\n    if ($flags & 1024) {\n        push @l, \"dirsync\";\n        $flags &= ~1024;\n    }\n\n    if ($flags & 2048) {\n        push @l, \"compress\";\n        $flags &= ~2048;\n    }\n\n    if ($flags & 0x80000000) {\n        push @l, \"root_item_init\";\n        $flags &= ~0x80000000;\n    }\n\n    if ($flags & 4294967296) {\n        push @l, \"ro_verity\";\n        $flags &= ~4294967296;\n    }\n\n    if ($flags != 0) {\n        push @l, sprintf(\"%x\", $flags);\n    }\n\n    if ($#l > -1) {\n        return join(',', @l);\n    } else {\n        return 0;\n    }\n}\n\nsub format_balance {\n    my ($s) = @_;\n    my (@b, $flags, @f, $fl, $t);\n\n    @b = unpack(\"QVVQQQQQQQVVVV\", $s);\n\n    $flags = $b[9];\n\n    $t = sprintf(\"profiles=%x\", $b[0]);\n\n    if ($flags & (1 << 10)) {\n        $t .= sprintf(\" usage=%x\", ($b[2] << 32) | $b[1]);\n    } elsif ($flags & (1 << 1)) {\n        $t .= sprintf(\" usage=%x..%x\", $b[1], $b[2]);\n    }\n\n    $t .= sprintf(\" devid=%x pstart=%x pend=%x vstart=%x vend=%x target=%x\", $b[3], $b[4], $b[5], $b[6], $b[7], $b[8]);\n\n    @f = ();\n    $fl = $flags;\n\n    if ($fl & (1 << 0)) {\n        push @f, \"profiles\";\n        $fl &= ~(1 << 0);\n    }\n\n    if ($fl & (1 << 1)) {\n        push @f, \"usage\";\n        $fl &= ~(1 << 1);\n    }\n\n    if ($fl & (1 << 2)) {\n        push @f, \"devid\";\n        $fl &= ~(1 << 2);\n    }\n\n    if ($fl & (1 << 3)) {\n        push @f, \"drange\";\n        $fl &= ~(1 << 3);\n    }\n\n    if ($fl & (1 << 4)) {\n        push @f, \"vrange\";\n        $fl &= ~(1 << 4);\n    }\n\n    if ($fl & (1 << 5)) {\n        push @f, \"limit\";\n        $fl &= ~(1 << 5);\n    }\n\n    if ($fl & (1 << 6)) {\n        push @f, \"limitrange\";\n        $fl &= ~(1 << 6);\n    }\n\n    if ($fl & (1 << 7)) {\n        push @f, \"stripesrange\";\n        $fl &= ~(1 << 7);\n    }\n\n    if ($fl & (1 << 8)) {\n        push @f, \"convert\";\n        $fl &= ~(1 << 8);\n    }\n\n    if ($fl & (1 << 9)) {\n        push @f, \"soft\";\n        $fl &= ~(1 << 9);\n    }\n\n    if ($fl & (1 << 10)) {\n        push @f, \"usagerange\";\n        $fl &= ~(1 << 10);\n    }\n\n    if ($fl != 0 || $#f == -1) {\n        push @f, $fl;\n    }\n\n    $t .= sprintf(\" flags=%s\", join(',', @f));\n\n    if ($flags & (1 << 5)) {\n        $t .= sprintf(\" limit=%x\", ($b[11] << 32) | $b[10]);\n    } elsif ($flags & (1 << 6)) {\n        $t .= sprintf(\" limit=%x..%x\", $b[11], $b[10]);\n    }\n\n    if ($flags & (1 << 7)) {\n        $t .= sprintf(\" stripes=%x..%x\", $b[12], $b[13]);\n    }\n\n    return $t;\n}\n\nsub qgroup_status_flags {\n    my ($f) = @_;\n    my (@l);\n\n    if ($f & 1) {\n        push @l, \"on\";\n        $f &= ~1;\n    }\n\n    if ($f & 2) {\n        push @l, \"rescan\";\n        $f &= ~2;\n    }\n\n    if ($f & 4) {\n        push @l, \"inconsistent\";\n        $f &= ~4;\n    }\n\n    if ($f & 8) {\n        push @l, \"simple_mode\";\n        $f &= ~8;\n    }\n\n    if ($f != 0) {\n        push @l, $f;\n    }\n\n    return join(',', @l);\n}\n\nsub free_space_bitmap {\n    my ($s, $off) = @_;\n\n    my $b = \"\";\n    while (length($s) != 0) {\n        $b .= reverse(sprintf(\"%08b\", ord($s)));\n        $s = substr($s, 1, length($s) - 1);\n    }\n\n    my @runs = ();\n\n    my $run_start = 0;\n    for (my $i = 0; $i < length($b); $i++) {\n        my $c = substr($b, $i, 1);\n\n        if ($c eq \"1\" && ($i == 0 || substr($b, $i - 1, 1) eq \"0\")) {\n            $run_start = $i;\n        } elsif ($c eq \"0\" && $i != 0 && substr($b, $i - 1, 1) eq \"1\") {\n            push @runs, sprintf(\"%x, %x\", $off + ($run_start * $blocksize), ($i - $run_start) * $blocksize);\n        }\n    }\n\n    if (substr($b, length($b) - 1, 1) eq \"1\") {\n        push @runs, sprintf(\"%x, %x\", $off + ($run_start * $blocksize),\n                            (length($b) - $run_start) * $blocksize);\n    }\n\n    return join('; ', @runs);\n}\n\nsub block_group_item_flags {\n    my ($f) = @_;\n    my (@l);\n\n    if ($f & 1) {\n        push @l, \"data\";\n        $f &= ~1;\n    }\n\n    if ($f & 2) {\n        push @l, \"system\";\n        $f &= ~2;\n    }\n\n    if ($f & 4) {\n        push @l, \"metadata\";\n        $f &= ~4;\n    }\n\n    if ($f & 8) {\n        push @l, \"raid0\";\n        $f &= ~8;\n    }\n\n    if ($f & 16) {\n        push @l, \"raid1\";\n        $f &= ~16;\n    }\n\n    if ($f & 32) {\n        push @l, \"dup\";\n        $f &= ~32;\n    }\n\n    if ($f & 64) {\n        push @l, \"raid10\";\n        $f &= ~64;\n    }\n\n    if ($f & 128) {\n        push @l, \"raid5\";\n        $f &= ~128;\n    }\n\n    if ($f & 256) {\n        push @l, \"raid6\";\n        $f &= ~256;\n    }\n\n    if ($f & 512) {\n        push @l, \"raid1c3\";\n        $f &= ~512;\n    }\n\n    if ($f & 1024) {\n        push @l, \"raid1c4\";\n        $f &= ~1024;\n    }\n\n    if ($f != 0) {\n        push @l, $f;\n    }\n\n    return join(',', @l);\n}\n\nsub extent_item_flags {\n    my ($f) = @_;\n    my (@l);\n\n    if ($f & 1) {\n        push @l, \"data\";\n        $f &= ~1;\n    }\n\n    if ($f & 2) {\n        push @l, \"tree_block\";\n        $f &= ~2;\n    }\n\n    if ($f & 256) {\n        push @l, \"full_backref\";\n        $f &= ~256;\n    }\n\n    if ($f != 0) {\n        push @l, $f;\n    }\n\n    return join(',', @l);\n}\n\nsub extent_data_type {\n    my ($d) = @_;\n\n    if ($d == 0) {\n        return \"inline\";\n    } elsif ($d == 1) {\n        return \"reg\";\n    } elsif ($d == 2) {\n        return \"prealloc\";\n    } else {\n        return sprintf(\"%x\", $d);\n    }\n}\n\nsub compression_type {\n    my ($d) = @_;\n\n    if ($d == 0) {\n        return \"none\";\n    } elsif ($d == 1) {\n        return \"zlib\";\n    } elsif ($d == 2) {\n        return \"lzo\";\n    } elsif ($d == 3) {\n        return \"zstd\";\n    } else {\n        return sprintf(\"%x\", $d);\n    }\n}\n\nsub dir_item_type {\n    my ($d) = @_;\n\n    if ($d == 0) {\n        return \"unknown\";\n    } elsif ($d == 1) {\n        return \"reg_file\";\n    } elsif ($d == 2) {\n        return \"dir\";\n    } elsif ($d == 3) {\n        return \"chrdev\";\n    } elsif ($d == 4) {\n        return \"blkdev\";\n    } elsif ($d == 5) {\n        return \"fifo\";\n    } elsif ($d == 6) {\n        return \"sock\";\n    } elsif ($d == 7) {\n        return \"symlink\";\n    } elsif ($d == 8) {\n        return \"xattr\";\n    } else {\n        return sprintf(\"%x\", $d);\n    }\n}\n\nsub free_space_info_flags {\n    my ($f) = @_;\n    my (@l);\n\n    if ($f & 1) {\n        push @l, \"using_bitmaps\";\n        $f &= ~1;\n    }\n\n    if ($f != 0 || $#l == -1) {\n        push @l, sprintf(\"%x\", $f);\n    }\n\n    return join(',', @l);\n}\n\nsub qgroup_limit_flags {\n    my ($f) = @_;\n    my (@l);\n\n    if ($f & 1) {\n        push @l, \"max_rfer\";\n        $f &= ~1;\n    }\n\n    if ($f & 2) {\n        push @l, \"max_excl\";\n        $f &= ~2;\n    }\n\n    if ($f & 4) {\n        push @l, \"rsv_rfer\";\n        $f &= ~4;\n    }\n\n    if ($f & 8) {\n        push @l, \"rsv_excl\";\n        $f &= ~8;\n    }\n\n    if ($f & 16) {\n        push @l, \"rfer_cmpr\";\n        $f &= ~8;\n    }\n\n    if ($f & 32) {\n        push @l, \"excl_cmpr\";\n        $f &= ~8;\n    }\n\n    if ($f != 0 || $#l == -1) {\n        push @l, $f;\n    }\n\n    return join(',', @l);\n}\n\nsub dump_item {\n    my ($type, $s, $pref, $id, $off) = @_;\n    my (@b);\n\n    my $unrecog = 0;\n\n    print $pref;\n    if ($type == 0x1 || $type == 0x84) { # INODE_ITEM or ROOT_ITEM\n        if (length($s) < 0xa0) {\n            $s .= chr(0) x (0xa0 - length($s));\n        }\n\n        @b = unpack(\"QQQQQVVVVQQQx32QVQVQVQV\", $s);\n        $s = substr($s, 0xa0);\n\n        if ($type == 0x84) {\n            print \"root_item\";\n        } else {\n            print \"inode_item\";\n        }\n\n        printf(\" generation=%x transid=%x size=%x nbytes=%x block_group=%x nlink=%x uid=%x gid=%x mode=%o rdev=%x flags=%s sequence=%x atime=%s ctime=%s mtime=%s otime=%s\", $b[0], $b[1], $b[2], $b[3], $b[4], $b[5], $b[6], $b[7], $b[8], $b[9], inode_flags($b[10]), $b[11], format_time($b[12], $b[13]), format_time($b[14], $b[15]), format_time($b[16], $b[17]), format_time($b[18], $b[19]));\n\n        if ($type != 0x1) {\n            @b = unpack(\"QQQQQQQVQCQCC\", $s);\n            $s = substr($s, 0x4f);\n\n            #print Dumper(@b).\"\\n\";\n            printf(\"; generation=%x root_dirid=%x bytenr=%x byte_limit=%x bytes_used=%x last_snapshot=%x flags=%x refs=%x drop_progress=%x,%x,%x drop_level=%x level=%x\", @b);\n\n            @b = unpack(\"Qa16a16a16QQQQQVQVQVQV\", $s);\n            $s = substr($s, 0xc8); # above + 64 blank bytes\n\n            printf(\" generation_v2=%x uuid=%s parent_uuid=%s received_uuid=%s ctransid=%x otransid=%x stransid=%x rtransid=%x ctime=%s otime=%s stime=%s rtime=%s\", $b[0], format_uuid($b[1]), format_uuid($b[2]), format_uuid($b[3]), $b[4], $b[5], $b[6], $b[7], format_time($b[8], $b[9]), format_time($b[10], $b[11]), format_time($b[12], $b[13]), format_time($b[14], $b[15]));\n        }\n    } elsif ($type == 0xc) { # INODE_REF\n        printf(\"inode_ref\");\n\n        do {\n            @b = unpack(\"Qv\", $s);\n            $s = substr($s, 0xa);\n            my $name = substr($s, 0, $b[1]);\n            $s = substr($s, $b[1]);\n\n            printf(\" index=%x name_len=%x name=%s\", $b[0], $b[1], $name);\n        } while (length($s) > 0);\n    } elsif ($type == 0xd) { # INODE_EXTREF\n        printf(\"inode_extref\");\n\n        do {\n            @b = unpack(\"QQv\", $s);\n            $s = substr($s, 0x12);\n            my $name = substr($s, 0, $b[2]);\n            $s = substr($s, $b[2]);\n\n            printf(\" parent_objectid=%x index=%x name_len=%x name=%s\", $b[0], $b[1], $b[2], $name);\n        } while (length($s) > 0);\n    } elsif ($type == 0x18 || $type == 0x54 || $type == 0x60) { # XATTR_ITEM, DIR_ITEM or DIR_INDEX\n        print $type == 0x54 ? \"dir_item\" : ($type == 0x18 ? \"xattr_item\" : \"dir_index\");\n\n        while (length($s) > 0) {\n            @b = unpack(\"QCQQvvC\", $s);\n            $s = substr($s, 0x1e);\n\n            my $name = substr($s, 0, $b[5]);\n            $s = substr($s, $b[5]);\n\n            my $name2 = substr($s, 0, $b[4]);\n            $s = substr($s, $b[4]);\n\n            printf(\" location=%x,%x,%x transid=%x data_len=%x name_len=%x type=%s name=%s%s\", $b[0], $b[1], $b[2], $b[3], $b[4], $b[5], dir_item_type($b[6]), $name, $name2 eq \"\" ? \"\" : (\" data=\" . $name2));\n        }\n    } elsif ($type == 0x24) { # VERITY_DESC_ITEM\n        printf(\"verity_desc_item\");\n\n        if ($off == 0) {\n            @b = unpack(\"Qx16C\", $s);\n            $s = substr($s, 25);\n\n            printf(\" size=%x encryption=%x\", $b[0], $b[1]);\n        } else {\n            while (length($s) > 0) {\n                @b = unpack(\"C\", $s);\n                printf(\" %02x\", $b[0]);\n                $s = substr($s, 1);\n            }\n        }\n    } elsif ($type == 0x25) { # VERITY_MERKLE_ITEM\n        printf(\"verity_merkle_item\");\n\n        while (length($s) > 0) {\n            @b = unpack(\"NNNNNNNN\", $s);\n            printf(\" %008x%008x%008x%008x%008x%008x%008x%008x\", $b[0], $b[1], $b[2], $b[3], $b[4], $b[5], $b[6], $b[7]);\n            $s = substr($s, 32);\n        }\n    } elsif ($type == 0x30) { # ORPHAN_ITEM\n        printf(\"orphan_item\");\n    } elsif ($type == 0x48) { # DIR_LOG_INDEX\n        @b = unpack(\"Q\", $s);\n        $s = substr($s, 8);\n\n        printf(\"dir_log_index end=%x\", $b[0]);\n    } elsif ($type == 0x6c) { # EXTENT_DATA\n        @b = unpack(\"QQCCvC\", $s);\n        $s = substr($s, 0x15);\n\n        printf(\"extent_data generation=%x ram_bytes=%x compression=%s encryption=%x other_encoding=%x type=%s\", $b[0], $b[1], compression_type($b[2]), $b[3], $b[4], extent_data_type($b[5]));\n\n        if ($b[5] != 0) {\n            @b = unpack(\"QQQQ\", $s);\n            $s = substr($s, 0x20);\n\n            printf(\" disk_bytenr=%x disk_num_bytes=%x offset=%x num_bytes=%x\", @b);\n        } else {\n            $s = substr($s, $b[1]);\n        }\n    } elsif ($type == 0x80) { # EXTENT_CSUM\n        print \"extent_csum\";\n\n        if ($csum_type == 1) { # xxhash\n            while (length($s) > 0) {\n                printf(\" %016x\", unpack(\"Q\", $s));\n                $s = substr($s, 8);\n            }\n        } elsif ($csum_type == 2 || $csum_type == 3) { # sha256 or blake2\n            while (length($s) > 0) {\n                printf(\" %016x%016x%016x%016x\", unpack(\"QQQQ\", $s));\n                $s = substr($s, 32);\n            }\n        } else {\n            while (length($s) > 0) {\n                printf(\" %08x\", unpack(\"V\", $s));\n                $s = substr($s, 4);\n            }\n        }\n    } elsif ($type == 0x90 || $type == 0x9c) { # ROOT_BACKREF or ROOT_REF\n        @b = unpack(\"QQv\", $s);\n        $s = substr($s, 18);\n\n        my $name = substr($s, 0, $b[2]);\n        $s = substr($s, $b[2]);\n\n        printf(\"%s dirid=%x sequence=%x name_len=%x name=%s\", $type == 0x90 ? \"root_backref\" : \"root_ref\", $b[0], $b[1], $b[2], $name);\n    } elsif ($type == 0xa8 || $type == 0xa9) { # EXTENT_ITEM_KEY or METADATA_ITEM_KEY\n        # FIXME - TREE_BLOCK is out by one byte (why?)\n        if (length($s) == 4) {\n            @b = unpack(\"L\", $s);\n            $s = substr($s, 4);\n            printf(\"extent_item_v0 refcount=%x\", $b[0]);\n        } else {\n            @b = unpack(\"QQQ\", $s);\n            printf(\"%s refs=%x generation=%x flags=%s\", $type == 0xa9 ? \"metadata_item\" : \"extent_item\",\n                   $b[0], $b[1], extent_item_flags($b[2]));\n\n            $s = substr($s, 24);\n\n            my $refcount = $b[0];\n            if ($b[2] & 2 && $type != 0xa9) {\n                @b = unpack(\"QCQC\", $s);\n                printf(\" key=%x,%x,%x level=%u\", $b[0], $b[1], $b[2], $b[3]);\n                $s = substr($s, 18);\n            }\n\n            while (length($s) > 0) {\n                my $irt = unpack(\"C\", $s);\n                $s = substr($s, 1);\n\n                if ($irt == 0xac) {\n                    @b = unpack(\"Q\", $s);\n                    $s = substr($s, 8);\n                    printf(\" extent_owner_ref root=%x\", $b[0]);\n                } elsif ($irt == 0xb0) {\n                    @b = unpack(\"Q\", $s);\n                    $s = substr($s, 8);\n                    printf(\" tree_block_ref root=%x\", $b[0]);\n                } elsif ($irt == 0xb2) {\n                    @b = unpack(\"QQQv\", $s);\n                    $s = substr($s, 28);\n                    printf(\" extent_data_ref root=%x objectid=%x offset=%x count=%x\", @b);\n                    $refcount -= $b[3] - 1;\n                } elsif ($irt == 0xb6) {\n                    @b = unpack(\"Q\", $s);\n                    $s = substr($s, 8);\n                    printf(\" shared_block_ref offset=%x\", $b[0]);\n                } elsif ($irt == 0xb8) {\n                    @b = unpack(\"Qv\", $s);\n                    $s = substr($s, 12);\n                    printf(\" shared_data_ref offset=%x count=%x\", @b);\n                    $refcount -= $b[1] - 1;\n                } else {\n                    printf(\" unknown %x (length %u)\", $irt, length($s));\n                }\n            }\n        }\n    } elsif ($type == 0xb0) { # TREE_BLOCK_REF\n        printf(\"tree_block_ref\");\n    } elsif ($type == 0xb2) { # EXTENT_DATA_REF\n        @b = unpack(\"QQQv\", $s);\n        $s = substr($s, 28);\n        printf(\"extent_data_ref root=%x objectid=%x offset=%x count=%x\", @b);\n    } elsif ($type == 0xb4) { # EXTENT_REF_V0\n        @b = unpack(\"QQQv\", $s);\n        $s = substr($s, 28);\n\n        printf(\"extent_ref_v0 root=%x gen=%x objid=%x count=%x\", @b);\n    } elsif ($type == 0xb6) { # SHARED_BLOCK_REF\n        printf(\"shared_block_ref\");\n    } elsif ($type == 0xb8) { # SHARED_DATA_REF\n        @b = unpack(\"v\", $s);\n        $s = substr($s, 4);\n\n        printf(\"shared_data_ref count=%x\", @b);\n    } elsif ($type == 0xc0) { # BLOCK_GROUP_ITEM\n        @b = unpack(\"QQQ\", $s);\n        $s = substr($s, 0x18);\n        printf(\"block_group_item used=%x chunk_objectid=%x flags=%s\", $b[0], $b[1], block_group_item_flags($b[2]));\n    } elsif ($type == 0xc6) { # FREE_SPACE_INFO\n        @b = unpack(\"VV\", $s);\n        $s = substr($s, 0x8);\n        printf(\"free_space_info extent_count=%x flags=%s\", $b[0], free_space_info_flags($b[1]));\n    } elsif ($type == 0xc7) { # FREE_SPACE_EXTENT\n        printf(\"free_space_extent\");\n    } elsif ($type == 0xc8) { # FREE_SPACE_BITMAP\n        printf(\"free_space_bitmap %s\", free_space_bitmap($s, $id));\n        $s = \"\";\n    } elsif ($type == 0xcc) { # DEV_EXTENT\n        @b = unpack(\"QQQQa16\", $s);\n        $s = substr($s, 0x30);\n        printf(\"dev_extent chunk_tree=%x chunk_objectid=%x chunk_offset=%x length=%x chunk_tree_uuid=%s\", $b[0], $b[1], $b[2], $b[3], format_uuid($b[4]));\n    } elsif ($type == 0xd8) { # DEV_ITEM\n        @b = unpack(\"QQQVVVQQQVCCa16a16\", $s);\n        printf(\"dev_item devid=%x total_bytes=%x bytes_used=%x io_align=%x io_width=%x sector_size=%x type=%x generation=%x start_offset=%x dev_group=%x seek_speed=%x bandwidth=%x uuid=%s fsid=%s\", $b[0], $b[1],  $b[2], $b[3], $b[4], $b[5], $b[6], $b[7], $b[8], $b[9], $b[10], $b[11], format_uuid($b[12]), format_uuid($b[13]));\n        $s = substr($s, 0x62);\n    } elsif ($type == 0xe4) { # CHUNK_ITEM\n        @b = unpack(\"QQQQVVVvv\", $s);\n        printf(\"chunk_item length=%x owner=%x stripe_len=%x type=%s io_align=%x io_width=%x sector_size=%x num_stripes=%x sub_stripes=%x\",\n               $b[0], $b[1], $b[2], block_group_item_flags($b[3]), $b[4], $b[5], $b[6], $b[7], $b[8]);\n        $s = substr($s, 0x30);\n\n        my $numstripes = $b[7];\n        for (my $i = 0; $i < $numstripes; $i++) {\n            @b = unpack(\"QQa16\", $s);\n            $s = substr($s, 0x20);\n\n            printf(\" stripe(%u) devid=%x offset=%x dev_uuid=%s\", $i, $b[0], $b[1], format_uuid($b[2]));\n        }\n    } elsif ($type == 0xf0) { # QGROUP_STATUS\n        @b = unpack(\"QQQQQ\", $s);\n        printf(\"qgroup_status version=%x generation=%x flags=%s rescan=%x enable_gen=%x\", $b[0], $b[1], qgroup_status_flags($b[2]), $b[3], $b[4]);\n        $s = substr($s, 0x28);\n    } elsif ($type == 0xf2) { # QGROUP_INFO\n        @b = unpack(\"QQQQQ\", $s);\n        printf(\"qgroup_info generation=%x rfer=%x rfer_cmpr=%x excl=%x excl_cmpr=%x\", $b[0], $b[1], $b[2], $b[3], $b[4]);\n        $s = substr($s, 0x28);\n    } elsif ($type == 0xf4) { # QGROUP_LIMIT\n        @b = unpack(\"QQQQQ\", $s);\n        printf(\"qgroup_limit flags=%s max_rfer=%x max_excl=%x rsv_rfer=%x rsv_excl=%x\", qgroup_limit_flags($b[0]), $b[1], $b[2], $b[3], $b[4]);\n        $s = substr($s, 0x28);\n    } elsif ($type == 0xf6) { # QGROUP_RELATION\n        printf(\"qgroup_relation\");\n    } elsif ($type == 0xf8 && $id == 0xfffffffffffffffc) { # balance\n        my ($fl, @f);\n\n        @b = unpack(\"Q\", $s);\n        $s = substr($s, 8);\n\n        $fl = $b[0];\n        @f = ();\n\n        if ($fl & (1 << 0)) {\n            push @f, \"data\";\n            $fl &= ~(1 << 0);\n        }\n\n        if ($fl & (1 << 1)) {\n            push @f, \"system\";\n            $fl &= ~(1 << 1);\n        }\n\n        if ($fl & (1 << 2)) {\n            push @f, \"metadata\";\n            $fl &= ~(1 << 2);\n        }\n\n        if ($fl != 0 || $#f == -1) {\n            push @f, $fl;\n        }\n\n        printf(\"balance flags=%s data=(%s) metadata=(%s) sys=(%s)\", join(',', @f), format_balance(substr($s, 0, 0x88)), format_balance(substr($s, 0x88, 0x88)), format_balance(substr($s, 0x110, 0x88)));\n\n        $s = substr($s, 0x1b8);\n    } elsif ($type == 0xf9) { # DEV_STATS\n        print \"dev_stats\";\n\n        while (length($s) > 0) {\n            printf(\" %x\", unpack(\"Q\", $s));\n            $s = substr($s, 8);\n        }\n    } elsif ($type == 0xfb) { # UUID_SUBVOL\n        print \"uuid_subvol\";\n\n        while (length($s) > 0) {\n            printf(\" %x\", unpack(\"Q\", $s));\n            $s = substr($s, 8);\n        }\n    } elsif ($type == 0xfc) { # UUID_REC_SUBVOL\n        print \"uuid_rec_subvol\";\n\n        while (length($s) > 0) {\n            printf(\" %x\", unpack(\"Q\", $s));\n            $s = substr($s, 8);\n        }\n    } elsif ($type == 0 && $id == 0xfffffffffffffff5) { # free space\n        @b = unpack(\"QCQQQQ\", $s);\n        $s = substr($s, 0x29);\n\n        printf(\"free_space location=(%x,%x,%x) generation=%x num_entries=%x num_bitmaps=%x\", @b);\n    } else {\n        printf STDERR(\"ERROR - unknown type %x (size=%x, tell=%x)\\n\", $type, length($s), tell($f));\n        printf(\"unknown (size=%x)\", length($s));\n        $unrecog = 1;\n    }\n\n    if ($unrecog == 0 && length($s) > 0) {\n        printf(\" (left=%x)\", length($s));\n    }\n\n    print \"\\n\";\n}\n\nsub read_data {\n    my ($addr, $size, $bs) = @_;\n    my (@arr, $f, $data, $stripeoff, $parity, $stripe, $stripe2, $physoff);\n\n    if ($bs == 1) {\n        @arr = @l2p_bs;\n    } else {\n        @arr = @l2p;\n    }\n\n    foreach my $obj (@arr) {\n        if ($obj->{'offset'} <= $addr && ($addr - $obj->{'offset'}) < $obj->{'size'}) {\n            if ($obj->{'type'} & 0x80) { # RAID5\n                my $data_stripes = $obj->{'num_stripes'} - 1;\n                $stripeoff = ($addr - $obj->{'offset'}) % ($data_stripes * $obj->{'stripe_len'});\n                $parity = (int(($addr - $obj->{'offset'}) / ($data_stripes * $obj->{'stripe_len'})) + $obj->{'num_stripes'} - 1) % $obj->{'num_stripes'};\n                $stripe2 = int($stripeoff / $obj->{'stripe_len'});\n                $stripe = ($parity + $stripe2 + 1) % $obj->{'num_stripes'};\n\n                $f = $devs{$obj->{'stripes'}[$stripe]{'devid'}};\n                $physoff = $obj->{'stripes'}[$stripe]{'physoffset'} + (int(($addr - $obj->{'offset'}) / ($data_stripes * $obj->{'stripe_len'})) * $obj->{'stripe_len'}) + ($stripeoff % $obj->{'stripe_len'});\n            } elsif ($obj->{'type'} & 0x100) { # RAID6\n                my $data_stripes = $obj->{'num_stripes'} - 2;\n                $stripeoff = ($addr - $obj->{'offset'}) % ($data_stripes * $obj->{'stripe_len'});\n                $parity = (int(($addr - $obj->{'offset'}) / ($data_stripes * $obj->{'stripe_len'})) + $obj->{'num_stripes'} - 1) % $obj->{'num_stripes'};\n                $stripe2 = int($stripeoff / $obj->{'stripe_len'});\n                $stripe = ($parity + $stripe2 + 1) % $obj->{'num_stripes'};\n\n                $f = $devs{$obj->{'stripes'}[$stripe]{'devid'}};\n                $physoff = $obj->{'stripes'}[$stripe]{'physoffset'} + (int(($addr - $obj->{'offset'}) / ($data_stripes * $obj->{'stripe_len'})) * $obj->{'stripe_len'}) + ($stripeoff % $obj->{'stripe_len'});\n            } elsif ($obj->{'type'} & 0x40) { # RAID10\n                my $stripe_num = ($addr - $obj->{'offset'}) / $obj->{'stripe_len'};\n                my $stripe_offset = ($addr - $obj->{'offset'}) % $obj->{'stripe_len'};\n                my $stripe = $stripe_num % ($obj->{'num_stripes'} / $obj->{'sub_stripes'}) * $obj->{'sub_stripes'};\n\n                $f = $devs{$obj->{'stripes'}[$stripe]{'devid'}};\n                $physoff = $obj->{'stripes'}[$stripe]{'physoffset'} + (($stripe_num / ($obj->{'num_stripes'} / $obj->{'sub_stripes'})) * $obj->{'stripe_len'}) + $stripe_offset;\n            } elsif ($obj->{'type'} & 0x8) { # RAID0\n                my $stripe_num = ($addr - $obj->{'offset'}) / $obj->{'stripe_len'};\n                my $stripe_offset = ($addr - $obj->{'offset'}) % $obj->{'stripe_len'};\n                my $stripe = $stripe_num % $obj->{'num_stripes'};\n\n                $f = $devs{$obj->{'stripes'}[$stripe]{'devid'}};\n                $physoff = $obj->{'stripes'}[$stripe]{'physoffset'} + (($stripe_num / $obj->{'num_stripes'}) * $obj->{'stripe_len'}) + $stripe_offset;\n            } else { # SINGLE, DUP, RAID1, RAID1C3, RAID1C4\n                $f = $devs{$obj->{'stripes'}[0]{'devid'}};\n                $physoff = $obj->{'stripes'}[0]{'physoffset'} + $addr - $obj->{'offset'};\n            }\n\n            seek($f, $physoff, 0);\n            read($f, $data, $size);\n\n            return $data;\n        }\n    }\n}\n\nsub header_flags {\n    my ($flags) = @_;\n    my @l = ();\n\n    if ($flags & 1) {\n        push @l, \"written\";\n        $flags &= ~1;\n    }\n\n    if ($flags & 2) {\n        push @l, \"reloc\";\n        $flags &= ~2;\n    }\n\n    if ($flags & 0x100000000000000) {\n        push @l, \"mixed_backref\";\n        $flags &= ~0x100000000000000;\n    }\n\n    if ($flags != 0) {\n        push @l, sprintf(\"%x\", $flags);\n    }\n\n    if ($#l > -1) {\n        return join(',', @l);\n    } else {\n        return 0;\n    }\n}\n\nsub dump_tree {\n    my ($addr, $pref, $bs) = @_;\n    my ($head, @headbits, $level, $treenum, $tree, $csum);\n\n    $tree = read_data($addr, $nodesize, $bs);\n\n    @headbits = unpack(\"a32a16QQa16QQVC\", $tree);\n    if ($headbits[2] != $addr) {\n        printf STDERR sprintf(\"Address mismatch: expected %llx, got %llx\\n\", $addr, $headbits[2]);\n        exit;\n    }\n\n    if ($csum_type == 1) {\n        $csum = sprintf(\"%016x\", unpack(\"Q\", $headbits[0]));\n    } elsif ($csum_type == 2 || $csum_type == 3) {\n        $csum = sprintf(\"%016x%016x%016x%016x\", unpack(\"QQQQ\", $headbits[0]));\n    } else {\n        $csum = sprintf(\"%08x\", unpack(\"V\", $headbits[0]));\n    }\n\n    print $pref;\n    printf(\"header csum=%s fsid=%s bytenr=%x flags=%s chunk_tree_uuid=%s generation=%x owner=%x nritems=%x level=%x\\n\", $csum, format_uuid($headbits[1]), $headbits[2], header_flags($headbits[3]), format_uuid($headbits[4]), $headbits[5], $headbits[6], $headbits[7], $headbits[8]);\n\n    $level = $headbits[8];\n    $treenum = $headbits[6];\n\n    my $numitems = $headbits[7];\n\n    if ($level == 0) {\n        my $headaddr = tell($f);\n        for (my $i = 0; $i < $numitems; $i++) {\n            #read($f, my $itemhead, 0x19);\n            my $itemhead = substr($tree, 0x65 + ($i * 0x19), 0x19);\n\n            my @ihb = unpack(\"QCQVV\", $itemhead);\n\n            #print Dumper(@ihb).\"\\n\";\n            print $pref;\n            printf(\"%x,%x,%x\\n\", $ihb[0], $ihb[1], $ihb[2]);\n\n            my $item = substr($tree, 0x65 + $ihb[3], $ihb[4]);\n            dump_item($ihb[1], $item, $pref, $ihb[0], $ihb[2]);\n\n            if ($treenum == 3 && $ihb[1] == 0xe4) {\n                my @b = unpack(\"QQQQVVVvv\", $item);\n                my $stripes = substr($item, 48);\n                my %obj;\n\n                my $numstripes = $b[7];\n\n                $obj{'offset'} = $ihb[2];\n                $obj{'size'} = $b[0];\n                $obj{'type'} = $b[3];\n                $obj{'num_stripes'} = $b[7];\n                $obj{'stripe_len'} = $b[2];\n                $obj{'sub_stripes'} = $b[8];\n\n                for (my $i = 0; $i < $numstripes; $i++) {\n                    my @cis = unpack(\"QQa16\", $stripes);\n                    $stripes = substr($stripes, 32);\n\n                    $obj{'stripes'}[$i]{'physoffset'} = $cis[1];\n                    $obj{'stripes'}[$i]{'devid'} = $cis[0];\n                }\n\n                push @l2p, \\%obj;\n\n                #print Dumper(@l2p);\n            }\n\n            if ($ihb[1] == 0x84) {\n                if ($treenum == 1) {\n                    $roots{$ihb[0]} = unpack(\"x176Q\", $item);\n                } elsif ($treenum == 0xfffffffffffffffa && $ihb[0] == 0xfffffffffffffffa) {\n                    $logroots{$ihb[2]} = unpack(\"x176Q\", $item);\n                }\n            }\n        }\n    } else {\n        for (my $i = 0; $i < $numitems; $i++) {\n            my $itemhead = substr($tree, 0x65 + ($i * 0x21), 0x21);\n\n            my @ihb = unpack(\"QCQQQ\", $itemhead);\n\n            print $pref;\n            printf(\"%x,%x,%x blockptr=%x generation=%x\\n\", $ihb[0], $ihb[1], $ihb[2], $ihb[3], $ihb[4]);\n\n            dump_tree($ihb[3], \" \" . $pref, $bs);\n        }\n    }\n}\n"
  },
  {
    "path": "mingw-amd64.cmake",
    "content": "#\n# To Cross-compile, use:\n#  cmake -DCMAKE_TOOLCHAIN_FILE=../mingw.cmake ..\n#\n# the name of the target operating system\nSET(CMAKE_SYSTEM_NAME Windows)\n\n# which compilers to use for C and C++\nSET(CMAKE_C_COMPILER x86_64-w64-mingw32-gcc)\nSET(CMAKE_CXX_COMPILER x86_64-w64-mingw32-g++)\nSET(CMAKE_RC_COMPILER x86_64-w64-mingw32-windres)\n\n# here is the target environment located\nSET(CMAKE_FIND_ROOT_PATH /usr/x86_64-w64-mingw32)\n\n# adjust the default behaviour of the FIND_XXX() commands:\n# search headers and libraries in the target environment, search\n# programs in the host environment\nset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\nset(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\n\nset(CMAKE_PREFIX_PATH /usr/x86_64-w64-mingw32/usr/lib/cmake)\n\nset(CMAKE_SYSTEM_PROCESSOR x86_64)\n"
  },
  {
    "path": "mingw-x86.cmake",
    "content": "#\n# To Cross-compile, use:\n#  cmake -DCMAKE_TOOLCHAIN_FILE=../mingw.cmake ..\n#\n# the name of the target operating system\nSET(CMAKE_SYSTEM_NAME Windows)\n\n# which compilers to use for C and C++\nSET(CMAKE_C_COMPILER i686-w64-mingw32-gcc)\nSET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++)\nSET(CMAKE_RC_COMPILER i686-w64-mingw32-windres)\n\nSET(CMAKE_C_FLAGS \"-m32\")\nSET(CMAKE_CXX_FLAGS \"-m32\")\nSET(CMAKE_RC_FLAGS \"-F pe-i386\")\nSET(CMAKE_ASM_FLAGS \"-m32\")\n\n# here is the target environment located\nSET(CMAKE_FIND_ROOT_PATH /usr/i686-w64-mingw32)\n\n# adjust the default behaviour of the FIND_XXX() commands:\n# search headers and libraries in the target environment, search\n# programs in the host environment\nset(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER)\nset(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY)\nset(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY)\n\nset(CMAKE_PREFIX_PATH /usr/i686-w64-mingw32/usr/lib/cmake)\n\nset(CMAKE_SYSTEM_PROCESSOR x86)\n"
  },
  {
    "path": "msvc-aarch64.cmake",
    "content": "set(CMAKE_SYSTEM_NAME Windows)\n\nSET(CMAKE_C_COMPILER /opt/msvc/bin/arm64/cl)\nSET(CMAKE_CXX_COMPILER /opt/msvc/bin/arm64/cl)\nSET(CMAKE_RC_COMPILER /opt/msvc/bin/arm64/rc)\n\nset(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO\")\nset(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_MODULE_LINKER_FLAGS} /MANIFEST:NO\")\nset(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} /MANIFEST:NO\")\n"
  },
  {
    "path": "msvc-amd64.cmake",
    "content": "set(CMAKE_SYSTEM_NAME Windows)\n\nSET(CMAKE_C_COMPILER /opt/msvc/bin/x64/cl)\nSET(CMAKE_CXX_COMPILER /opt/msvc/bin/x64/cl)\nSET(CMAKE_RC_COMPILER /opt/msvc/bin/x64/rc)\n\nset(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO\")\nset(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_MODULE_LINKER_FLAGS} /MANIFEST:NO\")\nset(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} /MANIFEST:NO\")\n"
  },
  {
    "path": "msvc-armv7.cmake",
    "content": "set(CMAKE_SYSTEM_NAME Windows)\n\nSET(CMAKE_C_COMPILER /opt/msvc/bin/arm/cl)\nSET(CMAKE_CXX_COMPILER /opt/msvc/bin/arm/cl)\nSET(CMAKE_RC_COMPILER /opt/msvc/bin/arm/rc)\n\nset(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO\")\nset(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_MODULE_LINKER_FLAGS} /MANIFEST:NO\")\nset(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} /MANIFEST:NO\")\n"
  },
  {
    "path": "msvc-x86.cmake",
    "content": "set(CMAKE_SYSTEM_NAME Windows)\n\nSET(CMAKE_C_COMPILER /opt/msvc/bin/x86/cl)\nSET(CMAKE_CXX_COMPILER /opt/msvc/bin/x86/cl)\nSET(CMAKE_RC_COMPILER /opt/msvc/bin/x86/rc)\n\nset(CMAKE_EXE_LINKER_FLAGS \"${CMAKE_EXE_LINKER_FLAGS} /MANIFEST:NO\")\nset(CMAKE_MODULE_LINKER_FLAGS \"${CMAKE_MODULE_LINKER_FLAGS} /MANIFEST:NO\")\nset(CMAKE_SHARED_LINKER_FLAGS \"${CMAKE_SHARED_LINKER_FLAGS} /MANIFEST:NO\")\n\nset(CMAKE_PREFIX_PATH \"/home/hellas/wine/vcpkg/installed/x64-windows\")\n"
  },
  {
    "path": "send-dump.pl",
    "content": "#!/usr/bin/perl\n\n# Dumper for btrfs send streams.\n# Released under the same terms, or lack thereof, as btrfs-dump.pl.\n\nopen($f,$ARGV[0]) or die \"Error opening \".$ARGV[0].\": \".$!;\nbinmode($f);\n\nwhile (!eof($f)) {\n    do_stream($f);\n\n    if (!eof($f)) {\n        print \"---\\n\";\n    }\n}\n\nclose($f);\n\nsub do_stream {\n    my ($f)=@_;\n\n    read($f,$a,0x11);\n    ($magic,$ver)=unpack(\"a13V\",$a);\n\n    if ($magic ne \"btrfs-stream\\0\") {\n        printf STDERR \"Not a send file.\\n\";\n        close($f);\n        exit;\n    }\n\n    if ($ver != 1 && $ver != 2) {\n        printf STDERR \"Version $ver not supported.\\n\";\n        close($f);\n        exit;\n    }\n\n    $type = 0;\n    while (!eof($f) && $type != 21) {\n        read($f,$a,0xa);\n        ($len,$type,$crc)=unpack(\"VvV\",$a);\n\n        if ($type == 1) {\n            printf(\"subvol, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 2) {\n            printf(\"snapshot, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 3) {\n            printf(\"mkfile, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 4) {\n            printf(\"mkdir, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 5) {\n            printf(\"mknod, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 6) {\n            printf(\"mkfifo, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 7) {\n            printf(\"mksock, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 8) {\n            printf(\"symlink, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 9) {\n            printf(\"rename, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 10) {\n            printf(\"link, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 11) {\n            printf(\"unlink, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 12) {\n            printf(\"rmdir, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 13) {\n            printf(\"set_xattr, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 14) {\n            printf(\"remove_xattr, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 15) {\n            printf(\"write, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 16) {\n            printf(\"clone, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 17) {\n            printf(\"truncate, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 18) {\n            printf(\"chmod, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 19) {\n            printf(\"chown, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 20) {\n            printf(\"utimes, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 21) {\n            printf(\"end, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 22) {\n            printf(\"update-extent, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 23) {\n            printf(\"fallocate, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 24) {\n            printf(\"fileattr, %x, %08x\\n\", $len, $crc);\n        } elsif ($type == 25) {\n            printf(\"encoded-write, %x, %08x\\n\", $len, $crc);\n        } else {\n            printf(\"unknown(%x), %x, %08x\\n\", $type, $len, $crc);\n        }\n\n        read($f,$b,$len);\n        print_tlvs($b);\n    }\n}\n\nsub btrfstime {\n    my ($t)=@_;\n\n    my $ut = unpack(\"Q\",$t);\n    my @lt = localtime($ut);\n\n    return sprintf(\"%04u-%02u-%02u %02u:%02u:%02u\",$lt[5]+1900,$lt[4]+1,$lt[3],$lt[2],$lt[1],$lt[0]);\n}\n\nsub print_tlvs {\n    my ($b)=@_;\n\n    while (length($b)>0) {\n        my ($t,$l)=unpack(\"vv\",$b);\n\n        if ($t == 1) {\n            printf(\"  uuid: %08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x\\n\", unpack(\"NnnnCCCCCC\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 2) {\n            printf(\"  transid: %x\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 3) {\n            printf(\"  inode: %x\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 4) {\n            printf(\"  size: %x\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 5) {\n            printf(\"  mode: %o\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 6) {\n            printf(\"  uid: %u\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 7) {\n            printf(\"  gid: %u\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 8) {\n            printf(\"  rdev: %x\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 9) {\n            printf(\"  ctime: %s\\n\", btrfstime(substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 10) {\n            printf(\"  mtime: %s\\n\", btrfstime(substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 11) {\n            printf(\"  atime: %s\\n\", btrfstime(substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 12) {\n            printf(\"  otime: %s\\n\", btrfstime(substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 13) {\n            printf(\"  xattr_name: \\\"%s\\\"\\n\", substr($b,4,$l));\n            $b=substr($b,$l+4);\n        } elsif ($t == 14) {\n            printf(\"  xattr_data: \\\"%s\\\"\\n\", substr($b,4,$l));\n            $b=substr($b,$l+4);\n        } elsif ($t == 15) {\n            printf(\"  path: \\\"%s\\\"\\n\", substr($b,4,$l));\n            $b=substr($b,$l+4);\n        } elsif ($t == 16) {\n            printf(\"  path_to: \\\"%s\\\"\\n\", substr($b,4,$l));\n            $b=substr($b,$l+4);\n        } elsif ($t == 17) {\n            printf(\"  path_link: \\\"%s\\\"\\n\", substr($b,4,$l));\n            $b=substr($b,$l+4);\n        } elsif ($t == 18) {\n            printf(\"  offset: %x\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 19) {\n            if ($ver == 2) {\n                printf(\"  data: (%x bytes)\\n\", length($b) - 2); # FIXME\n                $b=\"\";\n            } else {\n                printf(\"  data: (%x bytes)\\n\", $l);\n                $b=substr($b,$l+4);\n            }\n        } elsif ($t == 20) {\n            printf(\"  clone_uuid: %08x-%04x-%04x-%04x-%02x%02x%02x%02x%02x%02x\\n\", unpack(\"NnnnCCCCCC\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 21) {\n            printf(\"  clone_transid: %x\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 22) {\n            printf(\"  clone_path: \\\"%s\\\"\\n\", substr($b,4,$l));\n            $b=substr($b,$l+4);\n        } elsif ($t == 23) {\n            printf(\"  clone_offset: %x\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 24) {\n            printf(\"  clone_len: %x\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 25) {\n            printf(\"  fallocate_mode: %x\\n\", unpack(\"V\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 26) {\n            printf(\"  fileattr: %x\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 27) {\n            printf(\"  unencoded_file_len: %x\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 28) {\n            printf(\"  unencoded_len: %x\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 29) {\n            printf(\"  unencoded_offset: %x\\n\", unpack(\"Q\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 30) {\n            printf(\"  compression: %x\\n\", unpack(\"V\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } elsif ($t == 31) {\n            printf(\"  encryption: %x\\n\", unpack(\"V\",substr($b,4,$l)));\n            $b=substr($b,$l+4);\n        } else {\n            printf(\"  unknown(%u),%x\\n\",$t,$l);\n            $b=substr($b,$l+4);\n        }\n    }\n}\n"
  },
  {
    "path": "src/balance.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include \"btrfsioctl.h\"\n#include \"crc32c.h\"\n#include <ntddstor.h>\n\ntypedef struct {\n    uint64_t address;\n    uint64_t new_address;\n    tree_header* data;\n    EXTENT_ITEM* ei;\n    tree* t;\n    bool system;\n    LIST_ENTRY refs;\n    LIST_ENTRY list_entry;\n} metadata_reloc;\n\ntypedef struct {\n    uint8_t type;\n    uint64_t hash;\n\n    union {\n        TREE_BLOCK_REF tbr;\n        SHARED_BLOCK_REF sbr;\n    };\n\n    metadata_reloc* parent;\n    bool top;\n    LIST_ENTRY list_entry;\n} metadata_reloc_ref;\n\ntypedef struct {\n    uint64_t address;\n    uint64_t size;\n    uint64_t new_address;\n    chunk* newchunk;\n    EXTENT_ITEM* ei;\n    LIST_ENTRY refs;\n    LIST_ENTRY list_entry;\n} data_reloc;\n\ntypedef struct {\n    uint8_t type;\n    uint64_t hash;\n\n    union {\n        EXTENT_DATA_REF edr;\n        SHARED_DATA_REF sdr;\n    };\n\n    metadata_reloc* parent;\n    LIST_ENTRY list_entry;\n} data_reloc_ref;\n\n#define BALANCE_UNIT 0x100000 // only read 1 MB at a time\n\nstatic NTSTATUS add_metadata_reloc(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* items, traverse_ptr* tp,\n                                   bool skinny, metadata_reloc** mr2, chunk* c, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    metadata_reloc* mr;\n    EXTENT_ITEM* ei;\n    uint16_t len;\n    uint64_t inline_rc;\n    uint8_t* ptr;\n\n    mr = ExAllocatePoolWithTag(PagedPool, sizeof(metadata_reloc), ALLOC_TAG);\n    if (!mr) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    mr->address = tp->item->key.obj_id;\n    mr->data = NULL;\n    mr->ei = (EXTENT_ITEM*)tp->item->data;\n    mr->system = false;\n    InitializeListHead(&mr->refs);\n\n    Status = delete_tree_item(Vcb, tp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n        ExFreePool(mr);\n        return Status;\n    }\n\n    if (!c)\n        c = get_chunk_from_address(Vcb, tp->item->key.obj_id);\n\n    if (c) {\n        acquire_chunk_lock(c, Vcb);\n\n        c->used -= Vcb->superblock.node_size;\n\n        space_list_add(c, tp->item->key.obj_id, Vcb->superblock.node_size, rollback);\n\n        release_chunk_lock(c, Vcb);\n    }\n\n    ei = (EXTENT_ITEM*)tp->item->data;\n    inline_rc = 0;\n\n    len = tp->item->size - sizeof(EXTENT_ITEM);\n    ptr = (uint8_t*)tp->item->data + sizeof(EXTENT_ITEM);\n    if (!skinny) {\n        len -= sizeof(EXTENT_ITEM2);\n        ptr += sizeof(EXTENT_ITEM2);\n    }\n\n    while (len > 0) {\n        uint8_t secttype = *ptr;\n        uint16_t sectlen = secttype == TYPE_TREE_BLOCK_REF ? sizeof(TREE_BLOCK_REF) : (secttype == TYPE_SHARED_BLOCK_REF ? sizeof(SHARED_BLOCK_REF) : 0);\n        metadata_reloc_ref* ref;\n\n        len--;\n\n        if (sectlen > len) {\n            ERR(\"(%I64x,%x,%I64x): %x bytes left, expecting at least %x\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, len, sectlen);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (sectlen == 0) {\n            ERR(\"(%I64x,%x,%I64x): unrecognized extent type %x\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, secttype);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        ref = ExAllocatePoolWithTag(PagedPool, sizeof(metadata_reloc_ref), ALLOC_TAG);\n        if (!ref) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        if (secttype == TYPE_TREE_BLOCK_REF) {\n            ref->type = TYPE_TREE_BLOCK_REF;\n            RtlCopyMemory(&ref->tbr, ptr + sizeof(uint8_t), sizeof(TREE_BLOCK_REF));\n            inline_rc++;\n        } else if (secttype == TYPE_SHARED_BLOCK_REF) {\n            ref->type = TYPE_SHARED_BLOCK_REF;\n            RtlCopyMemory(&ref->sbr, ptr + sizeof(uint8_t), sizeof(SHARED_BLOCK_REF));\n            inline_rc++;\n        } else {\n            ERR(\"unexpected tree type %x\\n\", secttype);\n            ExFreePool(ref);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        ref->parent = NULL;\n        ref->top = false;\n        InsertTailList(&mr->refs, &ref->list_entry);\n\n        len -= sectlen;\n        ptr += sizeof(uint8_t) + sectlen;\n    }\n\n    if (inline_rc < ei->refcount) { // look for non-inline entries\n        traverse_ptr tp2 = *tp, next_tp;\n\n        while (find_next_item(Vcb, &tp2, &next_tp, false, NULL)) {\n            tp2 = next_tp;\n\n            if (tp2.item->key.obj_id == tp->item->key.obj_id) {\n                if (tp2.item->key.obj_type == TYPE_TREE_BLOCK_REF) {\n                    metadata_reloc_ref* ref = ExAllocatePoolWithTag(PagedPool, sizeof(metadata_reloc_ref), ALLOC_TAG);\n                    if (!ref) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    ref->type = TYPE_TREE_BLOCK_REF;\n                    ref->tbr.offset = tp2.item->key.offset;\n                    ref->parent = NULL;\n                    ref->top = false;\n                    InsertTailList(&mr->refs, &ref->list_entry);\n\n                    Status = delete_tree_item(Vcb, &tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n                } else if (tp2.item->key.obj_type == TYPE_SHARED_BLOCK_REF) {\n                    metadata_reloc_ref* ref = ExAllocatePoolWithTag(PagedPool, sizeof(metadata_reloc_ref), ALLOC_TAG);\n                    if (!ref) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    ref->type = TYPE_SHARED_BLOCK_REF;\n                    ref->sbr.offset = tp2.item->key.offset;\n                    ref->parent = NULL;\n                    ref->top = false;\n                    InsertTailList(&mr->refs, &ref->list_entry);\n\n                    Status = delete_tree_item(Vcb, &tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n                }\n            } else\n                break;\n        }\n    }\n\n    InsertTailList(items, &mr->list_entry);\n\n    if (mr2)\n        *mr2 = mr;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS add_metadata_reloc_parent(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* items,\n                                          uint64_t address, metadata_reloc** mr2, LIST_ENTRY* rollback) {\n    LIST_ENTRY* le;\n    KEY searchkey;\n    traverse_ptr tp;\n    bool skinny = false;\n    NTSTATUS Status;\n\n    le = items->Flink;\n    while (le != items) {\n        metadata_reloc* mr = CONTAINING_RECORD(le, metadata_reloc, list_entry);\n\n        if (mr->address == address) {\n            *mr2 = mr;\n            return STATUS_SUCCESS;\n        }\n\n        le = le->Flink;\n    }\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = TYPE_METADATA_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM))\n        skinny = true;\n    else if (tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->key.offset == Vcb->superblock.node_size &&\n             tp.item->size >= sizeof(EXTENT_ITEM)) {\n        EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data;\n\n        if (!(ei->flags & EXTENT_ITEM_TREE_BLOCK)) {\n            ERR(\"EXTENT_ITEM for %I64x found, but tree flag not set\\n\", address);\n            return STATUS_INTERNAL_ERROR;\n        }\n    } else {\n        ERR(\"could not find valid EXTENT_ITEM for address %I64x\\n\", address);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    Status = add_metadata_reloc(Vcb, items, &tp, skinny, mr2, NULL, rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"add_metadata_reloc returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic void sort_metadata_reloc_refs(metadata_reloc* mr) {\n    LIST_ENTRY newlist, *le;\n\n    if (mr->refs.Flink == mr->refs.Blink) // 0 or 1 items\n        return;\n\n    // insertion sort\n\n    InitializeListHead(&newlist);\n\n    while (!IsListEmpty(&mr->refs)) {\n        metadata_reloc_ref* ref = CONTAINING_RECORD(RemoveHeadList(&mr->refs), metadata_reloc_ref, list_entry);\n        bool inserted = false;\n\n        if (ref->type == TYPE_TREE_BLOCK_REF)\n            ref->hash = ref->tbr.offset;\n        else if (ref->type == TYPE_SHARED_BLOCK_REF)\n            ref->hash = ref->parent->new_address;\n\n        le = newlist.Flink;\n        while (le != &newlist) {\n            metadata_reloc_ref* ref2 = CONTAINING_RECORD(le, metadata_reloc_ref, list_entry);\n\n            if (ref->type < ref2->type || (ref->type == ref2->type && ref->hash > ref2->hash)) {\n                InsertHeadList(le->Blink, &ref->list_entry);\n                inserted = true;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        if (!inserted)\n            InsertTailList(&newlist, &ref->list_entry);\n    }\n\n    newlist.Flink->Blink = &mr->refs;\n    newlist.Blink->Flink = &mr->refs;\n    mr->refs.Flink = newlist.Flink;\n    mr->refs.Blink = newlist.Blink;\n}\n\nstatic NTSTATUS add_metadata_reloc_extent_item(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, metadata_reloc* mr) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    uint64_t rc = 0;\n    uint16_t inline_len;\n    bool all_inline = true;\n    metadata_reloc_ref* first_noninline = NULL;\n    EXTENT_ITEM* ei;\n    uint8_t* ptr;\n\n    inline_len = sizeof(EXTENT_ITEM);\n    if (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA))\n        inline_len += sizeof(EXTENT_ITEM2);\n\n    sort_metadata_reloc_refs(mr);\n\n    le = mr->refs.Flink;\n    while (le != &mr->refs) {\n        metadata_reloc_ref* ref = CONTAINING_RECORD(le, metadata_reloc_ref, list_entry);\n        uint16_t extlen = 0;\n\n        rc++;\n\n        if (ref->type == TYPE_TREE_BLOCK_REF)\n            extlen += sizeof(TREE_BLOCK_REF);\n        else if (ref->type == TYPE_SHARED_BLOCK_REF)\n            extlen += sizeof(SHARED_BLOCK_REF);\n\n        if (all_inline) {\n            if ((ULONG)(inline_len + 1 + extlen) > (Vcb->superblock.node_size >> 2)) {\n                all_inline = false;\n                first_noninline = ref;\n            } else\n                inline_len += extlen + 1;\n        }\n\n        le = le->Flink;\n    }\n\n    ei = ExAllocatePoolWithTag(PagedPool, inline_len, ALLOC_TAG);\n    if (!ei) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    ei->refcount = rc;\n    ei->generation = mr->ei->generation;\n    ei->flags = mr->ei->flags;\n    ptr = (uint8_t*)&ei[1];\n\n    if (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) {\n        EXTENT_ITEM2* ei2 = (EXTENT_ITEM2*)ptr;\n\n        ei2->firstitem = *(KEY*)&mr->data[1];\n        ei2->level = mr->data->level;\n\n        ptr += sizeof(EXTENT_ITEM2);\n    }\n\n    le = mr->refs.Flink;\n    while (le != &mr->refs) {\n        metadata_reloc_ref* ref = CONTAINING_RECORD(le, metadata_reloc_ref, list_entry);\n\n        if (ref == first_noninline)\n            break;\n\n        *ptr = ref->type;\n        ptr++;\n\n        if (ref->type == TYPE_TREE_BLOCK_REF) {\n            TREE_BLOCK_REF* tbr = (TREE_BLOCK_REF*)ptr;\n\n            tbr->offset = ref->tbr.offset;\n\n            ptr += sizeof(TREE_BLOCK_REF);\n        } else if (ref->type == TYPE_SHARED_BLOCK_REF) {\n            SHARED_BLOCK_REF* sbr = (SHARED_BLOCK_REF*)ptr;\n\n            sbr->offset = ref->parent->new_address;\n\n            ptr += sizeof(SHARED_BLOCK_REF);\n        }\n\n        le = le->Flink;\n    }\n\n    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)\n        Status = insert_tree_item(Vcb, Vcb->extent_root, mr->new_address, TYPE_METADATA_ITEM, mr->data->level, ei, inline_len, NULL, NULL);\n    else\n        Status = insert_tree_item(Vcb, Vcb->extent_root, mr->new_address, TYPE_EXTENT_ITEM, Vcb->superblock.node_size, ei, inline_len, NULL, NULL);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(ei);\n        return Status;\n    }\n\n    if (!all_inline) {\n        le = &first_noninline->list_entry;\n\n        while (le != &mr->refs) {\n            metadata_reloc_ref* ref = CONTAINING_RECORD(le, metadata_reloc_ref, list_entry);\n\n            if (ref->type == TYPE_TREE_BLOCK_REF) {\n                Status = insert_tree_item(Vcb, Vcb->extent_root, mr->new_address, TYPE_TREE_BLOCK_REF, ref->tbr.offset, NULL, 0, NULL, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n            } else if (ref->type == TYPE_SHARED_BLOCK_REF) {\n                Status = insert_tree_item(Vcb, Vcb->extent_root, mr->new_address, TYPE_SHARED_BLOCK_REF, ref->parent->new_address, NULL, 0, NULL, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    if (ei->flags & EXTENT_ITEM_SHARED_BACKREFS || mr->data->flags & HEADER_FLAG_SHARED_BACKREF || !(mr->data->flags & HEADER_FLAG_MIXED_BACKREF)) {\n        if (mr->data->level > 0) {\n            uint16_t i;\n            internal_node* in = (internal_node*)&mr->data[1];\n\n            for (i = 0; i < mr->data->num_items; i++) {\n                uint64_t sbrrc = find_extent_shared_tree_refcount(Vcb, in[i].address, mr->address, NULL);\n\n                if (sbrrc > 0) {\n                    SHARED_BLOCK_REF sbr;\n\n                    sbr.offset = mr->new_address;\n\n                    Status = increase_extent_refcount(Vcb, in[i].address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, NULL, 0, NULL);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"increase_extent_refcount returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    sbr.offset = mr->address;\n\n                    Status = decrease_extent_refcount(Vcb, in[i].address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, NULL, 0,\n                                                      sbr.offset, false, NULL);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"decrease_extent_refcount returned %08lx\\n\", Status);\n                        return Status;\n                    }\n                }\n            }\n        } else {\n            uint16_t i;\n            leaf_node* ln = (leaf_node*)&mr->data[1];\n\n            for (i = 0; i < mr->data->num_items; i++) {\n                if (ln[i].key.obj_type == TYPE_EXTENT_DATA && ln[i].size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {\n                    EXTENT_DATA* ed = (EXTENT_DATA*)((uint8_t*)mr->data + sizeof(tree_header) + ln[i].offset);\n\n                    if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {\n                        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n\n                        if (ed2->size > 0) { // not sparse\n                            uint32_t sdrrc = find_extent_shared_data_refcount(Vcb, ed2->address, mr->address, NULL);\n\n                            if (sdrrc > 0) {\n                                SHARED_DATA_REF sdr;\n                                chunk* c;\n\n                                sdr.offset = mr->new_address;\n                                sdr.count = sdrrc;\n\n                                Status = increase_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_SHARED_DATA_REF, &sdr, NULL, 0, NULL);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"increase_extent_refcount returned %08lx\\n\", Status);\n                                    return Status;\n                                }\n\n                                sdr.offset = mr->address;\n\n                                Status = decrease_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_SHARED_DATA_REF, &sdr, NULL, 0,\n                                                                  sdr.offset, false, NULL);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"decrease_extent_refcount returned %08lx\\n\", Status);\n                                    return Status;\n                                }\n\n                                c = get_chunk_from_address(Vcb, ed2->address);\n\n                                if (c) {\n                                    // check changed_extents\n\n                                    ExAcquireResourceExclusiveLite(&c->changed_extents_lock, true);\n\n                                    le = c->changed_extents.Flink;\n\n                                    while (le != &c->changed_extents) {\n                                        changed_extent* ce = CONTAINING_RECORD(le, changed_extent, list_entry);\n\n                                        if (ce->address == ed2->address) {\n                                            LIST_ENTRY* le2;\n\n                                            le2 = ce->refs.Flink;\n                                            while (le2 != &ce->refs) {\n                                                changed_extent_ref* cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);\n\n                                                if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == mr->address) {\n                                                    cer->sdr.offset = mr->new_address;\n                                                    break;\n                                                }\n\n                                                le2 = le2->Flink;\n                                            }\n\n                                            le2 = ce->old_refs.Flink;\n                                            while (le2 != &ce->old_refs) {\n                                                changed_extent_ref* cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);\n\n                                                if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == mr->address) {\n                                                    cer->sdr.offset = mr->new_address;\n                                                    break;\n                                                }\n\n                                                le2 = le2->Flink;\n                                            }\n\n                                            break;\n                                        }\n\n                                        le = le->Flink;\n                                    }\n\n                                    ExReleaseResourceLite(&c->changed_extents_lock);\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS write_metadata_items(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* items,\n                                     LIST_ENTRY* data_items, chunk* c, LIST_ENTRY* rollback) {\n    LIST_ENTRY tree_writes, *le;\n    NTSTATUS Status;\n    traverse_ptr tp;\n    uint8_t level, max_level = 0;\n    chunk* newchunk = NULL;\n\n    InitializeListHead(&tree_writes);\n\n    le = items->Flink;\n    while (le != items) {\n        metadata_reloc* mr = CONTAINING_RECORD(le, metadata_reloc, list_entry);\n        LIST_ENTRY* le2;\n        chunk* pc;\n\n        mr->data = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG);\n        if (!mr->data) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        Status = read_data(Vcb, mr->address, Vcb->superblock.node_size, NULL, true, (uint8_t*)mr->data,\n                           c && mr->address >= c->offset && mr->address < c->offset + c->chunk_item->size ? c : NULL, &pc, NULL, 0, false, NormalPagePriority);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"read_data returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (pc->chunk_item->type & BLOCK_FLAG_SYSTEM)\n            mr->system = true;\n\n        if (data_items && mr->data->level == 0) {\n            le2 = data_items->Flink;\n            while (le2 != data_items) {\n                data_reloc* dr = CONTAINING_RECORD(le2, data_reloc, list_entry);\n                leaf_node* ln = (leaf_node*)&mr->data[1];\n                uint16_t i;\n\n                for (i = 0; i < mr->data->num_items; i++) {\n                    if (ln[i].key.obj_type == TYPE_EXTENT_DATA && ln[i].size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {\n                        EXTENT_DATA* ed = (EXTENT_DATA*)((uint8_t*)mr->data + sizeof(tree_header) + ln[i].offset);\n\n                        if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {\n                            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n\n                            if (ed2->address == dr->address)\n                                ed2->address = dr->new_address;\n                        }\n                    }\n                }\n\n                le2 = le2->Flink;\n            }\n        }\n\n        if (mr->data->level > max_level)\n            max_level = mr->data->level;\n\n        le2 = mr->refs.Flink;\n        while (le2 != &mr->refs) {\n            metadata_reloc_ref* ref = CONTAINING_RECORD(le2, metadata_reloc_ref, list_entry);\n\n            if (ref->type == TYPE_TREE_BLOCK_REF) {\n                KEY* firstitem;\n                root* r = NULL;\n                LIST_ENTRY* le3;\n                tree* t;\n\n                firstitem = (KEY*)&mr->data[1];\n\n                le3 = Vcb->roots.Flink;\n                while (le3 != &Vcb->roots) {\n                    root* r2 = CONTAINING_RECORD(le3, root, list_entry);\n\n                    if (r2->id == ref->tbr.offset) {\n                        r = r2;\n                        break;\n                    }\n\n                    le3 = le3->Flink;\n                }\n\n                if (!r) {\n                    ERR(\"could not find subvol with id %I64x\\n\", ref->tbr.offset);\n                    return STATUS_INTERNAL_ERROR;\n                }\n\n                Status = find_item_to_level(Vcb, r, &tp, firstitem, false, mr->data->level + 1, NULL);\n                if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) {\n                    ERR(\"find_item_to_level returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                t = tp.tree;\n                while (t && t->header.level < mr->data->level + 1) {\n                    t = t->parent;\n                }\n\n                if (!t)\n                    ref->top = true;\n                else {\n                    metadata_reloc* mr2;\n\n                    Status = add_metadata_reloc_parent(Vcb, items, t->header.address, &mr2, rollback);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"add_metadata_reloc_parent returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    ref->parent = mr2;\n                }\n            } else if (ref->type == TYPE_SHARED_BLOCK_REF) {\n                metadata_reloc* mr2;\n\n                Status = add_metadata_reloc_parent(Vcb, items, ref->sbr.offset, &mr2, rollback);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"add_metadata_reloc_parent returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                ref->parent = mr2;\n            }\n\n            le2 = le2->Flink;\n        }\n\n        le = le->Flink;\n    }\n\n    le = items->Flink;\n    while (le != items) {\n        metadata_reloc* mr = CONTAINING_RECORD(le, metadata_reloc, list_entry);\n        LIST_ENTRY* le2;\n        uint32_t hash;\n\n        mr->t = NULL;\n\n        hash = calc_crc32c(0xffffffff, (uint8_t*)&mr->address, sizeof(uint64_t));\n\n        le2 = Vcb->trees_ptrs[hash >> 24];\n\n        if (le2) {\n            while (le2 != &Vcb->trees_hash) {\n                tree* t = CONTAINING_RECORD(le2, tree, list_entry_hash);\n\n                if (t->header.address == mr->address) {\n                    mr->t = t;\n                    break;\n                } else if (t->hash > hash)\n                    break;\n\n                le2 = le2->Flink;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    for (level = 0; level <= max_level; level++) {\n        le = items->Flink;\n        while (le != items) {\n            metadata_reloc* mr = CONTAINING_RECORD(le, metadata_reloc, list_entry);\n\n            if (mr->data->level == level) {\n                bool done = false;\n                LIST_ENTRY* le2;\n                tree_write* tw;\n                uint64_t flags;\n                tree* t3;\n\n                if (mr->system)\n                    flags = Vcb->system_flags;\n                else if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS)\n                    flags = Vcb->data_flags;\n                else\n                    flags = Vcb->metadata_flags;\n\n                if (newchunk) {\n                    acquire_chunk_lock(newchunk, Vcb);\n\n                    if (newchunk->chunk_item->type == flags && find_metadata_address_in_chunk(Vcb, newchunk, &mr->new_address)) {\n                        newchunk->used += Vcb->superblock.node_size;\n                        space_list_subtract(newchunk, mr->new_address, Vcb->superblock.node_size, rollback);\n                        done = true;\n                    }\n\n                    release_chunk_lock(newchunk, Vcb);\n                }\n\n                if (!done) {\n                    ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true);\n\n                    le2 = Vcb->chunks.Flink;\n                    while (le2 != &Vcb->chunks) {\n                        chunk* c2 = CONTAINING_RECORD(le2, chunk, list_entry);\n\n                        if (!c2->readonly && !c2->reloc && c2 != newchunk && c2->chunk_item->type == flags) {\n                            acquire_chunk_lock(c2, Vcb);\n\n                            if ((c2->chunk_item->size - c2->used) >= Vcb->superblock.node_size) {\n                                if (find_metadata_address_in_chunk(Vcb, c2, &mr->new_address)) {\n                                    c2->used += Vcb->superblock.node_size;\n                                    space_list_subtract(c2, mr->new_address, Vcb->superblock.node_size, rollback);\n                                    release_chunk_lock(c2, Vcb);\n                                    newchunk = c2;\n                                    done = true;\n                                    break;\n                                }\n                            }\n\n                            release_chunk_lock(c2, Vcb);\n                        }\n\n                        le2 = le2->Flink;\n                    }\n\n                    // allocate new chunk if necessary\n                    if (!done) {\n                        Status = alloc_chunk(Vcb, flags, &newchunk, false);\n\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"alloc_chunk returned %08lx\\n\", Status);\n                            ExReleaseResourceLite(&Vcb->chunk_lock);\n                            goto end;\n                        }\n\n                        acquire_chunk_lock(newchunk, Vcb);\n\n                        newchunk->balance_num = Vcb->balance.balance_num;\n\n                        if (!find_metadata_address_in_chunk(Vcb, newchunk, &mr->new_address)) {\n                            release_chunk_lock(newchunk, Vcb);\n                            ExReleaseResourceLite(&Vcb->chunk_lock);\n                            ERR(\"could not find address in new chunk\\n\");\n                            Status = STATUS_DISK_FULL;\n                            goto end;\n                        } else {\n                            newchunk->used += Vcb->superblock.node_size;\n                            space_list_subtract(newchunk, mr->new_address, Vcb->superblock.node_size, rollback);\n                        }\n\n                        release_chunk_lock(newchunk, Vcb);\n                    }\n\n                    ExReleaseResourceLite(&Vcb->chunk_lock);\n                }\n\n                // update parents\n                le2 = mr->refs.Flink;\n                while (le2 != &mr->refs) {\n                    metadata_reloc_ref* ref = CONTAINING_RECORD(le2, metadata_reloc_ref, list_entry);\n\n                    if (ref->parent) {\n                        uint16_t i;\n                        internal_node* in = (internal_node*)&ref->parent->data[1];\n\n                        for (i = 0; i < ref->parent->data->num_items; i++) {\n                            if (in[i].address == mr->address) {\n                                in[i].address = mr->new_address;\n                                break;\n                            }\n                        }\n\n                        if (ref->parent->t) {\n                            LIST_ENTRY* le3;\n\n                            le3 = ref->parent->t->itemlist.Flink;\n                            while (le3 != &ref->parent->t->itemlist) {\n                                tree_data* td = CONTAINING_RECORD(le3, tree_data, list_entry);\n\n                                if (!td->inserted && td->treeholder.address == mr->address)\n                                    td->treeholder.address = mr->new_address;\n\n                                le3 = le3->Flink;\n                            }\n                        }\n                    } else if (ref->top && ref->type == TYPE_TREE_BLOCK_REF) {\n                        LIST_ENTRY* le3;\n                        root* r = NULL;\n\n                        // alter ROOT_ITEM\n\n                        le3 = Vcb->roots.Flink;\n                        while (le3 != &Vcb->roots) {\n                            root* r2 = CONTAINING_RECORD(le3, root, list_entry);\n\n                            if (r2->id == ref->tbr.offset) {\n                                r = r2;\n                                break;\n                            }\n\n                            le3 = le3->Flink;\n                        }\n\n                        if (r) {\n                            r->treeholder.address = mr->new_address;\n\n                            if (r == Vcb->root_root)\n                                Vcb->superblock.root_tree_addr = mr->new_address;\n                            else if (r == Vcb->chunk_root)\n                                Vcb->superblock.chunk_tree_addr = mr->new_address;\n                            else if (r->root_item.block_number == mr->address) {\n                                KEY searchkey;\n                                ROOT_ITEM* ri;\n\n                                r->root_item.block_number = mr->new_address;\n\n                                searchkey.obj_id = r->id;\n                                searchkey.obj_type = TYPE_ROOT_ITEM;\n                                searchkey.offset = 0xffffffffffffffff;\n\n                                Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, NULL);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"find_item returned %08lx\\n\", Status);\n                                    goto end;\n                                }\n\n                                if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n                                    ERR(\"could not find ROOT_ITEM for tree %I64x\\n\", searchkey.obj_id);\n                                    Status = STATUS_INTERNAL_ERROR;\n                                    goto end;\n                                }\n\n                                ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);\n                                if (!ri) {\n                                    ERR(\"out of memory\\n\");\n                                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                                    goto end;\n                                }\n\n                                RtlCopyMemory(ri, &r->root_item, sizeof(ROOT_ITEM));\n\n                                Status = delete_tree_item(Vcb, &tp);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                                    goto end;\n                                }\n\n                                Status = insert_tree_item(Vcb, Vcb->root_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, ri, sizeof(ROOT_ITEM), NULL, NULL);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                                    goto end;\n                                }\n                            }\n                        }\n                    }\n\n                    le2 = le2->Flink;\n                }\n\n                mr->data->address = mr->new_address;\n\n                t3 = mr->t;\n\n                while (t3) {\n                    uint8_t h;\n                    bool inserted;\n                    tree* t4 = NULL;\n\n                    // check if tree loaded more than once\n                    if (t3->list_entry.Flink != &Vcb->trees_hash) {\n                        tree* nt = CONTAINING_RECORD(t3->list_entry_hash.Flink, tree, list_entry_hash);\n\n                        if (nt->header.address == t3->header.address)\n                            t4 = nt;\n                    }\n\n                    t3->header.address = mr->new_address;\n\n                    h = t3->hash >> 24;\n\n                    if (Vcb->trees_ptrs[h] == &t3->list_entry_hash) {\n                        if (t3->list_entry_hash.Flink == &Vcb->trees_hash)\n                            Vcb->trees_ptrs[h] = NULL;\n                        else {\n                            tree* t2 = CONTAINING_RECORD(t3->list_entry_hash.Flink, tree, list_entry_hash);\n\n                            if (t2->hash >> 24 == h)\n                                Vcb->trees_ptrs[h] = &t2->list_entry_hash;\n                            else\n                                Vcb->trees_ptrs[h] = NULL;\n                        }\n                    }\n\n                    RemoveEntryList(&t3->list_entry_hash);\n\n                    t3->hash = calc_crc32c(0xffffffff, (uint8_t*)&t3->header.address, sizeof(uint64_t));\n                    h = t3->hash >> 24;\n\n                    if (!Vcb->trees_ptrs[h]) {\n                        uint8_t h2 = h;\n\n                        le2 = Vcb->trees_hash.Flink;\n\n                        if (h2 > 0) {\n                            h2--;\n                            do {\n                                if (Vcb->trees_ptrs[h2]) {\n                                    le2 = Vcb->trees_ptrs[h2];\n                                    break;\n                                }\n\n                                h2--;\n                            } while (h2 > 0);\n                        }\n                    } else\n                        le2 = Vcb->trees_ptrs[h];\n\n                    inserted = false;\n                    while (le2 != &Vcb->trees_hash) {\n                        tree* t2 = CONTAINING_RECORD(le2, tree, list_entry_hash);\n\n                        if (t2->hash >= t3->hash) {\n                            InsertHeadList(le2->Blink, &t3->list_entry_hash);\n                            inserted = true;\n                            break;\n                        }\n\n                        le2 = le2->Flink;\n                    }\n\n                    if (!inserted)\n                        InsertTailList(&Vcb->trees_hash, &t3->list_entry_hash);\n\n                    if (!Vcb->trees_ptrs[h] || t3->list_entry_hash.Flink == Vcb->trees_ptrs[h])\n                        Vcb->trees_ptrs[h] = &t3->list_entry_hash;\n\n                    if (data_items && level == 0) {\n                        le2 = data_items->Flink;\n\n                        while (le2 != data_items) {\n                            data_reloc* dr = CONTAINING_RECORD(le2, data_reloc, list_entry);\n                            LIST_ENTRY* le3 = t3->itemlist.Flink;\n\n                            while (le3 != &t3->itemlist) {\n                                tree_data* td = CONTAINING_RECORD(le3, tree_data, list_entry);\n\n                                if (!td->inserted && td->key.obj_type == TYPE_EXTENT_DATA && td->size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {\n                                    EXTENT_DATA* ed = (EXTENT_DATA*)td->data;\n\n                                    if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {\n                                        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n\n                                        if (ed2->address == dr->address)\n                                            ed2->address = dr->new_address;\n                                    }\n                                }\n\n                                le3 = le3->Flink;\n                            }\n\n                            le2 = le2->Flink;\n                        }\n                    }\n\n                    t3 = t4;\n                }\n\n                calc_tree_checksum(Vcb, mr->data);\n\n                tw = ExAllocatePoolWithTag(PagedPool, sizeof(tree_write), ALLOC_TAG);\n                if (!tw) {\n                    ERR(\"out of memory\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto end;\n                }\n\n                tw->address = mr->new_address;\n                tw->length = Vcb->superblock.node_size;\n                tw->data = (uint8_t*)mr->data;\n                tw->allocated = false;\n\n                if (IsListEmpty(&tree_writes))\n                    InsertTailList(&tree_writes, &tw->list_entry);\n                else {\n                    bool inserted = false;\n\n                    le2 = tree_writes.Flink;\n                    while (le2 != &tree_writes) {\n                        tree_write* tw2 = CONTAINING_RECORD(le2, tree_write, list_entry);\n\n                        if (tw2->address > tw->address) {\n                            InsertHeadList(le2->Blink, &tw->list_entry);\n                            inserted = true;\n                            break;\n                        }\n\n                        le2 = le2->Flink;\n                    }\n\n                    if (!inserted)\n                        InsertTailList(&tree_writes, &tw->list_entry);\n                }\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    Status = do_tree_writes(Vcb, &tree_writes, true);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"do_tree_writes returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    le = items->Flink;\n    while (le != items) {\n        metadata_reloc* mr = CONTAINING_RECORD(le, metadata_reloc, list_entry);\n\n        Status = add_metadata_reloc_extent_item(Vcb, mr);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_metadata_reloc_extent_item returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        le = le->Flink;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    while (!IsListEmpty(&tree_writes)) {\n        tree_write* tw = CONTAINING_RECORD(RemoveHeadList(&tree_writes), tree_write, list_entry);\n\n        if (tw->allocated)\n            ExFreePool(tw->data);\n\n        ExFreePool(tw);\n    }\n\n    return Status;\n}\n\nstatic NTSTATUS balance_metadata_chunk(device_extension* Vcb, chunk* c, bool* changed) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n    bool b;\n    LIST_ENTRY items, rollback;\n    uint32_t loaded = 0;\n\n    TRACE(\"chunk %I64x\\n\", c->offset);\n\n    InitializeListHead(&rollback);\n    InitializeListHead(&items);\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    searchkey.obj_id = c->offset;\n    searchkey.obj_type = TYPE_METADATA_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    do {\n        traverse_ptr next_tp;\n\n        if (tp.item->key.obj_id >= c->offset + c->chunk_item->size)\n            break;\n\n        if (tp.item->key.obj_id >= c->offset && (tp.item->key.obj_type == TYPE_EXTENT_ITEM || tp.item->key.obj_type == TYPE_METADATA_ITEM)) {\n            bool tree = false, skinny = false;\n\n            if (tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) {\n                tree = true;\n                skinny = true;\n            } else if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->key.offset == Vcb->superblock.node_size &&\n                       tp.item->size >= sizeof(EXTENT_ITEM)) {\n                EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data;\n\n                if (ei->flags & EXTENT_ITEM_TREE_BLOCK)\n                    tree = true;\n            }\n\n            if (tree) {\n                Status = add_metadata_reloc(Vcb, &items, &tp, skinny, NULL, c, &rollback);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"add_metadata_reloc returned %08lx\\n\", Status);\n                    goto end;\n                }\n\n                loaded++;\n\n                if (loaded >= 64) // only do 64 at a time\n                    break;\n            }\n        }\n\n        b = find_next_item(Vcb, &tp, &next_tp, false, NULL);\n\n        if (b)\n            tp = next_tp;\n    } while (b);\n\n    if (IsListEmpty(&items)) {\n        *changed = false;\n        Status = STATUS_SUCCESS;\n        goto end;\n    } else\n        *changed = true;\n\n    Status = write_metadata_items(Vcb, &items, NULL, c, &rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"write_metadata_items returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    Status = STATUS_SUCCESS;\n\n    Vcb->need_write = true;\n\nend:\n    if (NT_SUCCESS(Status)) {\n        Status = do_write(Vcb, NULL);\n        if (!NT_SUCCESS(Status))\n            ERR(\"do_write returned %08lx\\n\", Status);\n    }\n\n    if (NT_SUCCESS(Status))\n        clear_rollback(&rollback);\n    else\n        do_rollback(Vcb, &rollback);\n\n    free_trees(Vcb);\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    while (!IsListEmpty(&items)) {\n        metadata_reloc* mr = CONTAINING_RECORD(RemoveHeadList(&items), metadata_reloc, list_entry);\n\n        while (!IsListEmpty(&mr->refs)) {\n            metadata_reloc_ref* ref = CONTAINING_RECORD(RemoveHeadList(&mr->refs), metadata_reloc_ref, list_entry);\n\n            ExFreePool(ref);\n        }\n\n        if (mr->data)\n            ExFreePool(mr->data);\n\n        ExFreePool(mr);\n    }\n\n    return Status;\n}\n\nstatic NTSTATUS data_reloc_add_tree_edr(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* metadata_items,\n                                        data_reloc* dr, EXTENT_DATA_REF* edr, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    KEY searchkey;\n    traverse_ptr tp;\n    root* r = NULL;\n    metadata_reloc* mr;\n    uint64_t last_tree = 0;\n    data_reloc_ref* ref;\n\n    le = Vcb->roots.Flink;\n    while (le != &Vcb->roots) {\n        root* r2 = CONTAINING_RECORD(le, root, list_entry);\n\n        if (r2->id == edr->root) {\n            r = r2;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!r) {\n        ERR(\"could not find subvol %I64x\\n\", edr->root);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    searchkey.obj_id = edr->objid;\n    searchkey.obj_type = TYPE_EXTENT_DATA;\n    searchkey.offset = 0;\n\n    Status = find_item(Vcb, r, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (tp.item->key.obj_id < searchkey.obj_id || (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type < searchkey.obj_type)) {\n        traverse_ptr tp2;\n\n        if (find_next_item(Vcb, &tp, &tp2, false, NULL))\n            tp = tp2;\n        else {\n            ERR(\"could not find EXTENT_DATA for inode %I64x in root %I64x\\n\", searchkey.obj_id, r->id);\n            return STATUS_INTERNAL_ERROR;\n        }\n    }\n\n    ref = NULL;\n\n    while (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n        traverse_ptr tp2;\n\n        if (tp.item->size >= sizeof(EXTENT_DATA)) {\n            EXTENT_DATA* ed = (EXTENT_DATA*)tp.item->data;\n\n            if ((ed->type == EXTENT_TYPE_PREALLOC || ed->type == EXTENT_TYPE_REGULAR) && tp.item->size >= offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)) {\n                EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n\n                if (ed2->address == dr->address && ed2->size == dr->size && tp.item->key.offset - ed2->offset == edr->offset) {\n                    if (ref && last_tree == tp.tree->header.address)\n                        ref->edr.count++;\n                    else {\n                        ref = ExAllocatePoolWithTag(PagedPool, sizeof(data_reloc_ref), ALLOC_TAG);\n                        if (!ref) {\n                            ERR(\"out of memory\\n\");\n                            return STATUS_INSUFFICIENT_RESOURCES;\n                        }\n\n                        ref->type = TYPE_EXTENT_DATA_REF;\n                        RtlCopyMemory(&ref->edr, edr, sizeof(EXTENT_DATA_REF));\n                        ref->edr.count = 1;\n\n                        Status = add_metadata_reloc_parent(Vcb, metadata_items, tp.tree->header.address, &mr, rollback);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"add_metadata_reloc_parent returned %08lx\\n\", Status);\n                            ExFreePool(ref);\n                            return Status;\n                        }\n\n                        last_tree = tp.tree->header.address;\n                        ref->parent = mr;\n\n                        InsertTailList(&dr->refs, &ref->list_entry);\n                    }\n                }\n            }\n        }\n\n        if (find_next_item(Vcb, &tp, &tp2, false, NULL))\n            tp = tp2;\n        else\n            break;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS add_data_reloc(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* items, LIST_ENTRY* metadata_items,\n                               traverse_ptr* tp, chunk* c, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    data_reloc* dr;\n    EXTENT_ITEM* ei;\n    uint16_t len;\n    uint64_t inline_rc;\n    uint8_t* ptr;\n\n    dr = ExAllocatePoolWithTag(PagedPool, sizeof(data_reloc), ALLOC_TAG);\n    if (!dr) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    dr->address = tp->item->key.obj_id;\n    dr->size = tp->item->key.offset;\n    dr->ei = (EXTENT_ITEM*)tp->item->data;\n    InitializeListHead(&dr->refs);\n\n    Status = delete_tree_item(Vcb, tp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (!c)\n        c = get_chunk_from_address(Vcb, tp->item->key.obj_id);\n\n    if (c) {\n        acquire_chunk_lock(c, Vcb);\n\n        c->used -= tp->item->key.offset;\n\n        space_list_add(c, tp->item->key.obj_id, tp->item->key.offset, rollback);\n\n        release_chunk_lock(c, Vcb);\n    }\n\n    ei = (EXTENT_ITEM*)tp->item->data;\n    inline_rc = 0;\n\n    len = tp->item->size - sizeof(EXTENT_ITEM);\n    ptr = (uint8_t*)tp->item->data + sizeof(EXTENT_ITEM);\n\n    while (len > 0) {\n        uint8_t secttype = *ptr;\n        uint16_t sectlen = secttype == TYPE_EXTENT_DATA_REF ? sizeof(EXTENT_DATA_REF) : (secttype == TYPE_SHARED_DATA_REF ? sizeof(SHARED_DATA_REF) : 0);\n\n        len--;\n\n        if (sectlen > len) {\n            ERR(\"(%I64x,%x,%I64x): %x bytes left, expecting at least %x\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, len, sectlen);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (sectlen == 0) {\n            ERR(\"(%I64x,%x,%I64x): unrecognized extent type %x\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, secttype);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (secttype == TYPE_EXTENT_DATA_REF) {\n            EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t));\n\n            inline_rc += edr->count;\n\n            Status = data_reloc_add_tree_edr(Vcb, metadata_items, dr, edr, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"data_reloc_add_tree_edr returned %08lx\\n\", Status);\n                return Status;\n            }\n        } else if (secttype == TYPE_SHARED_DATA_REF) {\n            metadata_reloc* mr;\n            data_reloc_ref* ref;\n\n            ref = ExAllocatePoolWithTag(PagedPool, sizeof(data_reloc_ref), ALLOC_TAG);\n            if (!ref) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            ref->type = TYPE_SHARED_DATA_REF;\n            RtlCopyMemory(&ref->sdr, ptr + sizeof(uint8_t), sizeof(SHARED_DATA_REF));\n            inline_rc += ref->sdr.count;\n\n            Status = add_metadata_reloc_parent(Vcb, metadata_items, ref->sdr.offset, &mr, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"add_metadata_reloc_parent returned %08lx\\n\", Status);\n                ExFreePool(ref);\n                return Status;\n            }\n\n            ref->parent = mr;\n\n            InsertTailList(&dr->refs, &ref->list_entry);\n        } else {\n            ERR(\"unexpected tree type %x\\n\", secttype);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n\n        len -= sectlen;\n        ptr += sizeof(uint8_t) + sectlen;\n    }\n\n    if (inline_rc < ei->refcount) { // look for non-inline entries\n        traverse_ptr tp2 = *tp, next_tp;\n\n        while (find_next_item(Vcb, &tp2, &next_tp, false, NULL)) {\n            tp2 = next_tp;\n\n            if (tp2.item->key.obj_id == tp->item->key.obj_id) {\n                if (tp2.item->key.obj_type == TYPE_EXTENT_DATA_REF && tp2.item->size >= sizeof(EXTENT_DATA_REF)) {\n                    Status = data_reloc_add_tree_edr(Vcb, metadata_items, dr, (EXTENT_DATA_REF*)tp2.item->data, rollback);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"data_reloc_add_tree_edr returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    Status = delete_tree_item(Vcb, &tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n                } else if (tp2.item->key.obj_type == TYPE_SHARED_DATA_REF && tp2.item->size >= sizeof(uint32_t)) {\n                    metadata_reloc* mr;\n                    data_reloc_ref* ref;\n\n                    ref = ExAllocatePoolWithTag(PagedPool, sizeof(data_reloc_ref), ALLOC_TAG);\n                    if (!ref) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    ref->type = TYPE_SHARED_DATA_REF;\n                    ref->sdr.offset = tp2.item->key.offset;\n                    ref->sdr.count = *((uint32_t*)tp2.item->data);\n\n                    Status = add_metadata_reloc_parent(Vcb, metadata_items, ref->sdr.offset, &mr, rollback);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"add_metadata_reloc_parent returned %08lx\\n\", Status);\n                        ExFreePool(ref);\n                        return Status;\n                    }\n\n                    ref->parent = mr;\n                    InsertTailList(&dr->refs, &ref->list_entry);\n\n                    Status = delete_tree_item(Vcb, &tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n                }\n            } else\n                break;\n        }\n    }\n\n    InsertTailList(items, &dr->list_entry);\n\n    return STATUS_SUCCESS;\n}\n\nstatic void sort_data_reloc_refs(data_reloc* dr) {\n    LIST_ENTRY newlist, *le;\n\n    if (IsListEmpty(&dr->refs))\n        return;\n\n    // insertion sort\n\n    InitializeListHead(&newlist);\n\n    while (!IsListEmpty(&dr->refs)) {\n        data_reloc_ref* ref = CONTAINING_RECORD(RemoveHeadList(&dr->refs), data_reloc_ref, list_entry);\n        bool inserted = false;\n\n        if (ref->type == TYPE_EXTENT_DATA_REF)\n            ref->hash = get_extent_data_ref_hash2(ref->edr.root, ref->edr.objid, ref->edr.offset);\n        else if (ref->type == TYPE_SHARED_DATA_REF)\n            ref->hash = ref->parent->new_address;\n\n        le = newlist.Flink;\n        while (le != &newlist) {\n            data_reloc_ref* ref2 = CONTAINING_RECORD(le, data_reloc_ref, list_entry);\n\n            if (ref->type < ref2->type || (ref->type == ref2->type && ref->hash > ref2->hash)) {\n                InsertHeadList(le->Blink, &ref->list_entry);\n                inserted = true;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        if (!inserted)\n            InsertTailList(&newlist, &ref->list_entry);\n    }\n\n    le = newlist.Flink;\n    while (le != &newlist) {\n        data_reloc_ref* ref = CONTAINING_RECORD(le, data_reloc_ref, list_entry);\n\n        if (le->Flink != &newlist) {\n            data_reloc_ref* ref2 = CONTAINING_RECORD(le->Flink, data_reloc_ref, list_entry);\n\n            if (ref->type == TYPE_EXTENT_DATA_REF && ref2->type == TYPE_EXTENT_DATA_REF && ref->edr.root == ref2->edr.root &&\n                ref->edr.objid == ref2->edr.objid && ref->edr.offset == ref2->edr.offset) {\n                RemoveEntryList(&ref2->list_entry);\n                ref->edr.count += ref2->edr.count;\n                ExFreePool(ref2);\n                continue;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    newlist.Flink->Blink = &dr->refs;\n    newlist.Blink->Flink = &dr->refs;\n    dr->refs.Flink = newlist.Flink;\n    dr->refs.Blink = newlist.Blink;\n}\n\nstatic NTSTATUS add_data_reloc_extent_item(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, data_reloc* dr) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    uint64_t rc = 0;\n    uint16_t inline_len;\n    bool all_inline = true;\n    data_reloc_ref* first_noninline = NULL;\n    EXTENT_ITEM* ei;\n    uint8_t* ptr;\n\n    inline_len = sizeof(EXTENT_ITEM);\n\n    sort_data_reloc_refs(dr);\n\n    le = dr->refs.Flink;\n    while (le != &dr->refs) {\n        data_reloc_ref* ref = CONTAINING_RECORD(le, data_reloc_ref, list_entry);\n        uint16_t extlen = 0;\n\n        if (ref->type == TYPE_EXTENT_DATA_REF) {\n            extlen += sizeof(EXTENT_DATA_REF);\n            rc += ref->edr.count;\n        } else if (ref->type == TYPE_SHARED_DATA_REF) {\n            extlen += sizeof(SHARED_DATA_REF);\n            rc++;\n        }\n\n        if (all_inline) {\n            if ((ULONG)(inline_len + 1 + extlen) > (Vcb->superblock.node_size >> 2)) {\n                all_inline = false;\n                first_noninline = ref;\n            } else\n                inline_len += extlen + 1;\n        }\n\n        le = le->Flink;\n    }\n\n    ei = ExAllocatePoolWithTag(PagedPool, inline_len, ALLOC_TAG);\n    if (!ei) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    ei->refcount = rc;\n    ei->generation = dr->ei->generation;\n    ei->flags = dr->ei->flags;\n    ptr = (uint8_t*)&ei[1];\n\n    le = dr->refs.Flink;\n    while (le != &dr->refs) {\n        data_reloc_ref* ref = CONTAINING_RECORD(le, data_reloc_ref, list_entry);\n\n        if (ref == first_noninline)\n            break;\n\n        *ptr = ref->type;\n        ptr++;\n\n        if (ref->type == TYPE_EXTENT_DATA_REF) {\n            EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)ptr;\n\n            RtlCopyMemory(edr, &ref->edr, sizeof(EXTENT_DATA_REF));\n\n            ptr += sizeof(EXTENT_DATA_REF);\n        } else if (ref->type == TYPE_SHARED_DATA_REF) {\n            SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)ptr;\n\n            sdr->offset = ref->parent->new_address;\n            sdr->count = ref->sdr.count;\n\n            ptr += sizeof(SHARED_DATA_REF);\n        }\n\n        le = le->Flink;\n    }\n\n    Status = insert_tree_item(Vcb, Vcb->extent_root, dr->new_address, TYPE_EXTENT_ITEM, dr->size, ei, inline_len, NULL, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (!all_inline) {\n        le = &first_noninline->list_entry;\n\n        while (le != &dr->refs) {\n            data_reloc_ref* ref = CONTAINING_RECORD(le, data_reloc_ref, list_entry);\n\n            if (ref->type == TYPE_EXTENT_DATA_REF) {\n                EXTENT_DATA_REF* edr;\n\n                edr = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA_REF), ALLOC_TAG);\n                if (!edr) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(edr, &ref->edr, sizeof(EXTENT_DATA_REF));\n\n                Status = insert_tree_item(Vcb, Vcb->extent_root, dr->new_address, TYPE_EXTENT_DATA_REF, ref->hash, edr, sizeof(EXTENT_DATA_REF), NULL, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n            } else if (ref->type == TYPE_SHARED_DATA_REF) {\n                uint32_t* sdr;\n\n                sdr = ExAllocatePoolWithTag(PagedPool, sizeof(uint32_t), ALLOC_TAG);\n                if (!sdr) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                *sdr = ref->sdr.count;\n\n                Status = insert_tree_item(Vcb, Vcb->extent_root, dr->new_address, TYPE_SHARED_DATA_REF, ref->parent->new_address, sdr, sizeof(uint32_t), NULL, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS balance_data_chunk(device_extension* Vcb, chunk* c, bool* changed) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n    bool b;\n    LIST_ENTRY items, metadata_items, rollback, *le;\n    uint64_t loaded = 0, num_loaded = 0;\n    chunk* newchunk = NULL;\n    uint8_t* data = NULL;\n\n    TRACE(\"chunk %I64x\\n\", c->offset);\n\n    InitializeListHead(&rollback);\n    InitializeListHead(&items);\n    InitializeListHead(&metadata_items);\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    searchkey.obj_id = c->offset;\n    searchkey.obj_type = TYPE_EXTENT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    do {\n        traverse_ptr next_tp;\n\n        if (tp.item->key.obj_id >= c->offset + c->chunk_item->size)\n            break;\n\n        if (tp.item->key.obj_id >= c->offset && tp.item->key.obj_type == TYPE_EXTENT_ITEM) {\n            bool tree = false;\n\n            if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) {\n                EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data;\n\n                if (ei->flags & EXTENT_ITEM_TREE_BLOCK)\n                    tree = true;\n            }\n\n            if (!tree) {\n                Status = add_data_reloc(Vcb, &items, &metadata_items, &tp, c, &rollback);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"add_data_reloc returned %08lx\\n\", Status);\n                    goto end;\n                }\n\n                loaded += tp.item->key.offset;\n                num_loaded++;\n\n                if (loaded >= 0x1000000 || num_loaded >= 100) // only do so much at a time, so we don't block too obnoxiously\n                    break;\n            }\n        }\n\n        b = find_next_item(Vcb, &tp, &next_tp, false, NULL);\n\n        if (b)\n            tp = next_tp;\n    } while (b);\n\n    if (IsListEmpty(&items)) {\n        *changed = false;\n        Status = STATUS_SUCCESS;\n        goto end;\n    } else\n        *changed = true;\n\n    data = ExAllocatePoolWithTag(PagedPool, BALANCE_UNIT, ALLOC_TAG);\n    if (!data) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    le = items.Flink;\n    while (le != &items) {\n        data_reloc* dr = CONTAINING_RECORD(le, data_reloc, list_entry);\n        bool done = false;\n        LIST_ENTRY* le2;\n        void* csum;\n        RTL_BITMAP bmp;\n        ULONG* bmparr;\n        ULONG bmplen, runlength, index, lastoff;\n\n        if (newchunk) {\n            acquire_chunk_lock(newchunk, Vcb);\n\n            if (find_data_address_in_chunk(Vcb, newchunk, dr->size, &dr->new_address)) {\n                newchunk->used += dr->size;\n                space_list_subtract(newchunk, dr->new_address, dr->size, &rollback);\n                done = true;\n            }\n\n            release_chunk_lock(newchunk, Vcb);\n        }\n\n        if (!done) {\n            ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true);\n\n            le2 = Vcb->chunks.Flink;\n            while (le2 != &Vcb->chunks) {\n                chunk* c2 = CONTAINING_RECORD(le2, chunk, list_entry);\n\n                if (!c2->readonly && !c2->reloc && c2 != newchunk && c2->chunk_item->type == Vcb->data_flags) {\n                    acquire_chunk_lock(c2, Vcb);\n\n                    if ((c2->chunk_item->size - c2->used) >= dr->size) {\n                        if (find_data_address_in_chunk(Vcb, c2, dr->size, &dr->new_address)) {\n                            c2->used += dr->size;\n                            space_list_subtract(c2, dr->new_address, dr->size, &rollback);\n                            release_chunk_lock(c2, Vcb);\n                            newchunk = c2;\n                            done = true;\n                            break;\n                        }\n                    }\n\n                    release_chunk_lock(c2, Vcb);\n                }\n\n                le2 = le2->Flink;\n            }\n\n            // allocate new chunk if necessary\n            if (!done) {\n                Status = alloc_chunk(Vcb, Vcb->data_flags, &newchunk, false);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"alloc_chunk returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(&Vcb->chunk_lock);\n                    goto end;\n                }\n\n                acquire_chunk_lock(newchunk, Vcb);\n\n                newchunk->balance_num = Vcb->balance.balance_num;\n\n                if (!find_data_address_in_chunk(Vcb, newchunk, dr->size, &dr->new_address)) {\n                    release_chunk_lock(newchunk, Vcb);\n                    ExReleaseResourceLite(&Vcb->chunk_lock);\n                    ERR(\"could not find address in new chunk\\n\");\n                    Status = STATUS_DISK_FULL;\n                    goto end;\n                } else {\n                    newchunk->used += dr->size;\n                    space_list_subtract(newchunk, dr->new_address, dr->size, &rollback);\n                }\n\n                release_chunk_lock(newchunk, Vcb);\n            }\n\n            ExReleaseResourceLite(&Vcb->chunk_lock);\n        }\n\n        dr->newchunk = newchunk;\n\n        bmplen = (ULONG)(dr->size >> Vcb->sector_shift);\n\n        bmparr = ExAllocatePoolWithTag(PagedPool, (ULONG)sector_align(bmplen + 1, sizeof(ULONG)), ALLOC_TAG);\n        if (!bmparr) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((dr->size * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG);\n        if (!csum) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(bmparr);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        RtlInitializeBitMap(&bmp, bmparr, bmplen);\n        RtlSetAllBits(&bmp); // 1 = no csum, 0 = csum\n\n        searchkey.obj_id = EXTENT_CSUM_ID;\n        searchkey.obj_type = TYPE_EXTENT_CSUM;\n        searchkey.offset = dr->address;\n\n        Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, NULL);\n        if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            ExFreePool(csum);\n            ExFreePool(bmparr);\n            goto end;\n        }\n\n        if (Status != STATUS_NOT_FOUND) {\n            do {\n                traverse_ptr next_tp;\n\n                if (tp.item->key.obj_type == TYPE_EXTENT_CSUM) {\n                    if (tp.item->key.offset >= dr->address + dr->size)\n                        break;\n                    else if (tp.item->size >= Vcb->csum_size && tp.item->key.offset + (((unsigned int)tp.item->size << Vcb->sector_shift) / Vcb->csum_size) >= dr->address) {\n                        uint64_t cs = max(dr->address, tp.item->key.offset);\n                        uint64_t ce = min(dr->address + dr->size, tp.item->key.offset + (((unsigned int)tp.item->size << Vcb->sector_shift) / Vcb->csum_size));\n\n                        RtlCopyMemory((uint8_t*)csum + (((cs - dr->address) * Vcb->csum_size) >> Vcb->sector_shift),\n                                      tp.item->data + (((cs - tp.item->key.offset) * Vcb->csum_size) >> Vcb->sector_shift),\n                                      (ULONG)(((ce - cs) * Vcb->csum_size) >> Vcb->sector_shift));\n\n                        RtlClearBits(&bmp, (ULONG)((cs - dr->address) >> Vcb->sector_shift), (ULONG)((ce - cs) >> Vcb->sector_shift));\n\n                        if (ce == dr->address + dr->size)\n                            break;\n                    }\n                }\n\n                if (find_next_item(Vcb, &tp, &next_tp, false, NULL))\n                    tp = next_tp;\n                else\n                    break;\n            } while (true);\n        }\n\n        lastoff = 0;\n        runlength = RtlFindFirstRunClear(&bmp, &index);\n\n        while (runlength != 0) {\n            if (index >= bmplen)\n                break;\n\n            if (index + runlength >= bmplen) {\n                runlength = bmplen - index;\n\n                if (runlength == 0)\n                    break;\n            }\n\n            if (index > lastoff) {\n                ULONG off = lastoff;\n                ULONG size = index - lastoff;\n\n                // handle no csum run\n                do {\n                    ULONG rl;\n\n                    if (size << Vcb->sector_shift > BALANCE_UNIT)\n                        rl = BALANCE_UNIT >> Vcb->sector_shift;\n                    else\n                        rl = size;\n\n                    Status = read_data(Vcb, dr->address + (off << Vcb->sector_shift), rl << Vcb->sector_shift, NULL, false, data,\n                                       c, NULL, NULL, 0, false, NormalPagePriority);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"read_data returned %08lx\\n\", Status);\n                        ExFreePool(csum);\n                        ExFreePool(bmparr);\n                        goto end;\n                    }\n\n                    Status = write_data_complete(Vcb, dr->new_address + (off << Vcb->sector_shift), data, rl << Vcb->sector_shift,\n                                                 NULL, newchunk, false, 0, NormalPagePriority);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"write_data_complete returned %08lx\\n\", Status);\n                        ExFreePool(csum);\n                        ExFreePool(bmparr);\n                        goto end;\n                    }\n\n                    size -= rl;\n                    off += rl;\n                } while (size > 0);\n            }\n\n            add_checksum_entry(Vcb, dr->new_address + (index << Vcb->sector_shift), runlength, (uint8_t*)csum + (index * Vcb->csum_size), NULL);\n            add_checksum_entry(Vcb, dr->address + (index << Vcb->sector_shift), runlength, NULL, NULL);\n\n            // handle csum run\n            do {\n                ULONG rl;\n\n                if (runlength << Vcb->sector_shift > BALANCE_UNIT)\n                    rl = BALANCE_UNIT >> Vcb->sector_shift;\n                else\n                    rl = runlength;\n\n                Status = read_data(Vcb, dr->address + (index << Vcb->sector_shift), rl << Vcb->sector_shift,\n                                   (uint8_t*)csum + (index * Vcb->csum_size), false, data, c, NULL, NULL, 0, false, NormalPagePriority);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"read_data returned %08lx\\n\", Status);\n                    ExFreePool(csum);\n                    ExFreePool(bmparr);\n                    goto end;\n                }\n\n                Status = write_data_complete(Vcb, dr->new_address + (index << Vcb->sector_shift), data, rl << Vcb->sector_shift,\n                                             NULL, newchunk, false, 0, NormalPagePriority);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"write_data_complete returned %08lx\\n\", Status);\n                    ExFreePool(csum);\n                    ExFreePool(bmparr);\n                    goto end;\n                }\n\n                runlength -= rl;\n                index += rl;\n            } while (runlength > 0);\n\n            lastoff = index;\n            runlength = RtlFindNextForwardRunClear(&bmp, index, &index);\n        }\n\n        ExFreePool(csum);\n        ExFreePool(bmparr);\n\n        // handle final nocsum run\n        if (lastoff < dr->size >> Vcb->sector_shift) {\n            ULONG off = lastoff;\n            ULONG size = (ULONG)((dr->size >> Vcb->sector_shift) - lastoff);\n\n            do {\n                ULONG rl;\n\n                if (size << Vcb->sector_shift > BALANCE_UNIT)\n                    rl = BALANCE_UNIT >> Vcb->sector_shift;\n                else\n                    rl = size;\n\n                Status = read_data(Vcb, dr->address + (off << Vcb->sector_shift), rl << Vcb->sector_shift, NULL, false, data,\n                                   c, NULL, NULL, 0, false, NormalPagePriority);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"read_data returned %08lx\\n\", Status);\n                    goto end;\n                }\n\n                Status = write_data_complete(Vcb, dr->new_address + (off << Vcb->sector_shift), data, rl << Vcb->sector_shift,\n                                             NULL, newchunk, false, 0, NormalPagePriority);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"write_data_complete returned %08lx\\n\", Status);\n                    goto end;\n                }\n\n                size -= rl;\n                off += rl;\n            } while (size > 0);\n        }\n\n        le = le->Flink;\n    }\n\n    ExFreePool(data);\n    data = NULL;\n\n    Status = write_metadata_items(Vcb, &metadata_items, &items, NULL, &rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"write_metadata_items returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    le = items.Flink;\n    while (le != &items) {\n        data_reloc* dr = CONTAINING_RECORD(le, data_reloc, list_entry);\n\n        Status = add_data_reloc_extent_item(Vcb, dr);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_data_reloc_extent_item returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        le = le->Flink;\n    }\n\n    le = c->changed_extents.Flink;\n    while (le != &c->changed_extents) {\n        LIST_ENTRY *le2, *le3;\n        changed_extent* ce = CONTAINING_RECORD(le, changed_extent, list_entry);\n\n        le3 = le->Flink;\n\n        le2 = items.Flink;\n        while (le2 != &items) {\n            data_reloc* dr = CONTAINING_RECORD(le2, data_reloc, list_entry);\n\n            if (ce->address == dr->address) {\n                ce->address = dr->new_address;\n                RemoveEntryList(&ce->list_entry);\n                InsertTailList(&dr->newchunk->changed_extents, &ce->list_entry);\n                break;\n            }\n\n            le2 = le2->Flink;\n        }\n\n        le = le3;\n    }\n\n    Status = STATUS_SUCCESS;\n\n    Vcb->need_write = true;\n\nend:\n    if (NT_SUCCESS(Status)) {\n        // update extents in cache inodes before we flush\n        le = Vcb->chunks.Flink;\n        while (le != &Vcb->chunks) {\n            chunk* c2 = CONTAINING_RECORD(le, chunk, list_entry);\n\n            if (c2->cache) {\n                LIST_ENTRY* le2;\n\n                ExAcquireResourceExclusiveLite(c2->cache->Header.Resource, true);\n\n                le2 = c2->cache->extents.Flink;\n                while (le2 != &c2->cache->extents) {\n                    extent* ext = CONTAINING_RECORD(le2, extent, list_entry);\n\n                    if (!ext->ignore) {\n                        if (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) {\n                            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n                            if (ed2->size > 0 && ed2->address >= c->offset && ed2->address < c->offset + c->chunk_item->size) {\n                                LIST_ENTRY* le3 = items.Flink;\n                                while (le3 != &items) {\n                                    data_reloc* dr = CONTAINING_RECORD(le3, data_reloc, list_entry);\n\n                                    if (ed2->address == dr->address) {\n                                        ed2->address = dr->new_address;\n                                        break;\n                                    }\n\n                                    le3 = le3->Flink;\n                                }\n                            }\n                        }\n                    }\n\n                    le2 = le2->Flink;\n                }\n\n                ExReleaseResourceLite(c2->cache->Header.Resource);\n            }\n\n            le = le->Flink;\n        }\n\n        Status = do_write(Vcb, NULL);\n        if (!NT_SUCCESS(Status))\n            ERR(\"do_write returned %08lx\\n\", Status);\n    }\n\n    if (NT_SUCCESS(Status)) {\n        clear_rollback(&rollback);\n\n        // update open FCBs\n        // FIXME - speed this up(?)\n\n        le = Vcb->all_fcbs.Flink;\n        while (le != &Vcb->all_fcbs) {\n            struct _fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_all);\n            LIST_ENTRY* le2;\n\n            ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n            le2 = fcb->extents.Flink;\n            while (le2 != &fcb->extents) {\n                extent* ext = CONTAINING_RECORD(le2, extent, list_entry);\n\n                if (!ext->ignore) {\n                    if (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) {\n                        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n                        if (ed2->size > 0 && ed2->address >= c->offset && ed2->address < c->offset + c->chunk_item->size) {\n                            LIST_ENTRY* le3 = items.Flink;\n                            while (le3 != &items) {\n                                data_reloc* dr = CONTAINING_RECORD(le3, data_reloc, list_entry);\n\n                                if (ed2->address == dr->address) {\n                                    ed2->address = dr->new_address;\n                                    break;\n                                }\n\n                                le3 = le3->Flink;\n                            }\n                        }\n                    }\n                }\n\n                le2 = le2->Flink;\n            }\n\n            ExReleaseResourceLite(fcb->Header.Resource);\n\n            le = le->Flink;\n        }\n    } else\n        do_rollback(Vcb, &rollback);\n\n    free_trees(Vcb);\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    if (data)\n        ExFreePool(data);\n\n    while (!IsListEmpty(&items)) {\n        data_reloc* dr = CONTAINING_RECORD(RemoveHeadList(&items), data_reloc, list_entry);\n\n        while (!IsListEmpty(&dr->refs)) {\n            data_reloc_ref* ref = CONTAINING_RECORD(RemoveHeadList(&dr->refs), data_reloc_ref, list_entry);\n\n            ExFreePool(ref);\n        }\n\n        ExFreePool(dr);\n    }\n\n    while (!IsListEmpty(&metadata_items)) {\n        metadata_reloc* mr = CONTAINING_RECORD(RemoveHeadList(&metadata_items), metadata_reloc, list_entry);\n\n        while (!IsListEmpty(&mr->refs)) {\n            metadata_reloc_ref* ref = CONTAINING_RECORD(RemoveHeadList(&mr->refs), metadata_reloc_ref, list_entry);\n\n            ExFreePool(ref);\n        }\n\n        if (mr->data)\n            ExFreePool(mr->data);\n\n        ExFreePool(mr);\n    }\n\n    return Status;\n}\n\nstatic __inline uint64_t get_chunk_dup_type(chunk* c) {\n    if (c->chunk_item->type & BLOCK_FLAG_RAID0)\n        return BLOCK_FLAG_RAID0;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID1)\n        return BLOCK_FLAG_RAID1;\n    else if (c->chunk_item->type & BLOCK_FLAG_DUPLICATE)\n        return BLOCK_FLAG_DUPLICATE;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID10)\n        return BLOCK_FLAG_RAID10;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID5)\n        return BLOCK_FLAG_RAID5;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID6)\n        return BLOCK_FLAG_RAID6;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID1C3)\n        return BLOCK_FLAG_RAID1C3;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID1C4)\n        return BLOCK_FLAG_RAID1C4;\n    else\n        return BLOCK_FLAG_SINGLE;\n}\n\nstatic bool should_balance_chunk(device_extension* Vcb, uint8_t sort, chunk* c) {\n    btrfs_balance_opts* opts;\n\n    opts = &Vcb->balance.opts[sort];\n\n    if (!(opts->flags & BTRFS_BALANCE_OPTS_ENABLED))\n        return false;\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_PROFILES) {\n        uint64_t type = get_chunk_dup_type(c);\n\n        if (!(type & opts->profiles))\n            return false;\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_DEVID) {\n        uint16_t i;\n        CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n        bool b = false;\n\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            if (cis[i].dev_id == opts->devid) {\n                b = true;\n                break;\n            }\n        }\n\n        if (!b)\n            return false;\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_DRANGE) {\n        uint16_t i, factor;\n        uint64_t physsize;\n        CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n        bool b = false;\n\n        if (c->chunk_item->type & BLOCK_FLAG_RAID0)\n            factor = c->chunk_item->num_stripes;\n        else if (c->chunk_item->type & BLOCK_FLAG_RAID10)\n            factor = c->chunk_item->num_stripes / c->chunk_item->sub_stripes;\n        else if (c->chunk_item->type & BLOCK_FLAG_RAID5)\n            factor = c->chunk_item->num_stripes - 1;\n        else if (c->chunk_item->type & BLOCK_FLAG_RAID6)\n            factor = c->chunk_item->num_stripes - 2;\n        else // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4\n            factor = 1;\n\n        physsize = c->chunk_item->size / factor;\n\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            if (cis[i].offset < opts->drange_end && cis[i].offset + physsize >= opts->drange_start &&\n                (!(opts->flags & BTRFS_BALANCE_OPTS_DEVID) || cis[i].dev_id == opts->devid)) {\n                b = true;\n                break;\n            }\n        }\n\n        if (!b)\n            return false;\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_VRANGE) {\n        if (c->offset + c->chunk_item->size <= opts->vrange_start || c->offset > opts->vrange_end)\n            return false;\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_STRIPES) {\n        if (c->chunk_item->num_stripes < opts->stripes_start || c->chunk_item->num_stripes < opts->stripes_end)\n            return false;\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_USAGE) {\n        uint64_t usage = c->used * 100 / c->chunk_item->size;\n\n        // usage == 0 should mean completely empty, not just that usage rounds to 0%\n        if (c->used > 0 && usage == 0)\n            usage = 1;\n\n        if (usage < opts->usage_start || usage > opts->usage_end)\n            return false;\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_CONVERT && opts->flags & BTRFS_BALANCE_OPTS_SOFT) {\n        uint64_t type = get_chunk_dup_type(c);\n\n        if (type == opts->convert)\n            return false;\n    }\n\n    return true;\n}\n\nstatic void copy_balance_args(btrfs_balance_opts* opts, BALANCE_ARGS* args) {\n    if (opts->flags & BTRFS_BALANCE_OPTS_PROFILES) {\n        args->profiles = opts->profiles;\n        args->flags |= BALANCE_ARGS_FLAGS_PROFILES;\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_USAGE) {\n        if (args->usage_start == 0) {\n            args->flags |= BALANCE_ARGS_FLAGS_USAGE_RANGE;\n            args->usage_start = opts->usage_start;\n            args->usage_end = opts->usage_end;\n        } else {\n            args->flags |= BALANCE_ARGS_FLAGS_USAGE;\n            args->usage = opts->usage_end;\n        }\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_DEVID) {\n        args->devid = opts->devid;\n        args->flags |= BALANCE_ARGS_FLAGS_DEVID;\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_DRANGE) {\n        args->drange_start = opts->drange_start;\n        args->drange_end = opts->drange_end;\n        args->flags |= BALANCE_ARGS_FLAGS_DRANGE;\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_VRANGE) {\n        args->vrange_start = opts->vrange_start;\n        args->vrange_end = opts->vrange_end;\n        args->flags |= BALANCE_ARGS_FLAGS_VRANGE;\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_CONVERT) {\n        args->convert = opts->convert;\n        args->flags |= BALANCE_ARGS_FLAGS_CONVERT;\n\n        if (opts->flags & BTRFS_BALANCE_OPTS_SOFT)\n            args->flags |= BALANCE_ARGS_FLAGS_SOFT;\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_LIMIT) {\n        if (args->limit_start == 0) {\n            args->flags |= BALANCE_ARGS_FLAGS_LIMIT_RANGE;\n            args->limit_start = (uint32_t)opts->limit_start;\n            args->limit_end = (uint32_t)opts->limit_end;\n        } else {\n            args->flags |= BALANCE_ARGS_FLAGS_LIMIT;\n            args->limit = opts->limit_end;\n        }\n    }\n\n    if (opts->flags & BTRFS_BALANCE_OPTS_STRIPES) {\n        args->stripes_start = opts->stripes_start;\n        args->stripes_end = opts->stripes_end;\n        args->flags |= BALANCE_ARGS_FLAGS_STRIPES_RANGE;\n    }\n}\n\nstatic NTSTATUS add_balance_item(device_extension* Vcb) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n    BALANCE_ITEM* bi;\n\n    searchkey.obj_id = BALANCE_ITEM_ID;\n    searchkey.obj_type = TYPE_TEMP_ITEM;\n    searchkey.offset = 0;\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (!keycmp(tp.item->key, searchkey)) {\n        Status = delete_tree_item(Vcb, &tp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            goto end;\n        }\n    }\n\n    bi = ExAllocatePoolWithTag(PagedPool, sizeof(BALANCE_ITEM), ALLOC_TAG);\n    if (!bi) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    RtlZeroMemory(bi, sizeof(BALANCE_ITEM));\n\n    if (Vcb->balance.opts[BALANCE_OPTS_DATA].flags & BTRFS_BALANCE_OPTS_ENABLED) {\n        bi->flags |= BALANCE_FLAGS_DATA;\n        copy_balance_args(&Vcb->balance.opts[BALANCE_OPTS_DATA], &bi->data);\n    }\n\n    if (Vcb->balance.opts[BALANCE_OPTS_METADATA].flags & BTRFS_BALANCE_OPTS_ENABLED) {\n        bi->flags |= BALANCE_FLAGS_METADATA;\n        copy_balance_args(&Vcb->balance.opts[BALANCE_OPTS_METADATA], &bi->metadata);\n    }\n\n    if (Vcb->balance.opts[BALANCE_OPTS_SYSTEM].flags & BTRFS_BALANCE_OPTS_ENABLED) {\n        bi->flags |= BALANCE_FLAGS_SYSTEM;\n        copy_balance_args(&Vcb->balance.opts[BALANCE_OPTS_SYSTEM], &bi->system);\n    }\n\n    Status = insert_tree_item(Vcb, Vcb->root_root, BALANCE_ITEM_ID, TYPE_TEMP_ITEM, 0, bi, sizeof(BALANCE_ITEM), NULL, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(bi);\n        goto end;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (NT_SUCCESS(Status)) {\n        Status = do_write(Vcb, NULL);\n        if (!NT_SUCCESS(Status))\n            ERR(\"do_write returned %08lx\\n\", Status);\n    }\n\n    free_trees(Vcb);\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS remove_balance_item(device_extension* Vcb) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n\n    searchkey.obj_id = BALANCE_ITEM_ID;\n    searchkey.obj_type = TYPE_TEMP_ITEM;\n    searchkey.offset = 0;\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (!keycmp(tp.item->key, searchkey)) {\n        Status = delete_tree_item(Vcb, &tp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        Status = do_write(Vcb, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"do_write returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        free_trees(Vcb);\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic void load_balance_args(btrfs_balance_opts* opts, BALANCE_ARGS* args) {\n    opts->flags = BTRFS_BALANCE_OPTS_ENABLED;\n\n    if (args->flags & BALANCE_ARGS_FLAGS_PROFILES) {\n        opts->flags |= BTRFS_BALANCE_OPTS_PROFILES;\n        opts->profiles = args->profiles;\n    }\n\n    if (args->flags & BALANCE_ARGS_FLAGS_USAGE) {\n        opts->flags |= BTRFS_BALANCE_OPTS_USAGE;\n\n        opts->usage_start = 0;\n        opts->usage_end = (uint8_t)args->usage;\n    } else if (args->flags & BALANCE_ARGS_FLAGS_USAGE_RANGE) {\n        opts->flags |= BTRFS_BALANCE_OPTS_USAGE;\n\n        opts->usage_start = (uint8_t)args->usage_start;\n        opts->usage_end = (uint8_t)args->usage_end;\n    }\n\n    if (args->flags & BALANCE_ARGS_FLAGS_DEVID) {\n        opts->flags |= BTRFS_BALANCE_OPTS_DEVID;\n        opts->devid = args->devid;\n    }\n\n    if (args->flags & BALANCE_ARGS_FLAGS_DRANGE) {\n        opts->flags |= BTRFS_BALANCE_OPTS_DRANGE;\n        opts->drange_start = args->drange_start;\n        opts->drange_end = args->drange_end;\n    }\n\n    if (args->flags & BALANCE_ARGS_FLAGS_VRANGE) {\n        opts->flags |= BTRFS_BALANCE_OPTS_VRANGE;\n        opts->vrange_start = args->vrange_start;\n        opts->vrange_end = args->vrange_end;\n    }\n\n    if (args->flags & BALANCE_ARGS_FLAGS_LIMIT) {\n        opts->flags |= BTRFS_BALANCE_OPTS_LIMIT;\n\n        opts->limit_start = 0;\n        opts->limit_end = args->limit;\n    } else if (args->flags & BALANCE_ARGS_FLAGS_LIMIT_RANGE) {\n        opts->flags |= BTRFS_BALANCE_OPTS_LIMIT;\n\n        opts->limit_start = args->limit_start;\n        opts->limit_end = args->limit_end;\n    }\n\n    if (args->flags & BALANCE_ARGS_FLAGS_STRIPES_RANGE) {\n        opts->flags |= BTRFS_BALANCE_OPTS_STRIPES;\n\n        opts->stripes_start = (uint16_t)args->stripes_start;\n        opts->stripes_end = (uint16_t)args->stripes_end;\n    }\n\n    if (args->flags & BALANCE_ARGS_FLAGS_CONVERT) {\n        opts->flags |= BTRFS_BALANCE_OPTS_CONVERT;\n        opts->convert = args->convert;\n\n        if (args->flags & BALANCE_ARGS_FLAGS_SOFT)\n            opts->flags |= BTRFS_BALANCE_OPTS_SOFT;\n    }\n}\n\nstatic NTSTATUS remove_superblocks(device* dev) {\n    NTSTATUS Status;\n    superblock* sb;\n    int i = 0;\n\n    sb = ExAllocatePoolWithTag(PagedPool, sizeof(superblock), ALLOC_TAG);\n    if (!sb) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(sb, sizeof(superblock));\n\n    while (superblock_addrs[i] > 0 && dev->devitem.num_bytes >= superblock_addrs[i] + sizeof(superblock)) {\n        Status = write_data_phys(dev->devobj, dev->fileobj, superblock_addrs[i], sb, sizeof(superblock));\n\n        if (!NT_SUCCESS(Status)) {\n            ExFreePool(sb);\n            return Status;\n        }\n\n        i++;\n    }\n\n    ExFreePool(sb);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS finish_removing_device(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, device* dev) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    volume_device_extension* vde;\n\n    if (Vcb->need_write) {\n        Status = do_write(Vcb, NULL);\n\n        if (!NT_SUCCESS(Status))\n            ERR(\"do_write returned %08lx\\n\", Status);\n    } else\n        Status = STATUS_SUCCESS;\n\n    free_trees(Vcb);\n\n    if (!NT_SUCCESS(Status))\n        return Status;\n\n    // remove entry in chunk tree\n\n    searchkey.obj_id = 1;\n    searchkey.obj_type = TYPE_DEV_ITEM;\n    searchkey.offset = dev->devitem.dev_id;\n\n    Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (!keycmp(searchkey, tp.item->key)) {\n        Status = delete_tree_item(Vcb, &tp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    // remove stats entry in device tree\n\n    searchkey.obj_id = 0;\n    searchkey.obj_type = TYPE_DEV_STATS;\n    searchkey.offset = dev->devitem.dev_id;\n\n    Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (!keycmp(searchkey, tp.item->key)) {\n        Status = delete_tree_item(Vcb, &tp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    // update superblock\n\n    Vcb->superblock.num_devices--;\n    Vcb->superblock.total_bytes -= dev->devitem.num_bytes;\n    Vcb->devices_loaded--;\n\n    RemoveEntryList(&dev->list_entry);\n\n    // flush\n\n    Status = do_write(Vcb, NULL);\n    if (!NT_SUCCESS(Status))\n        ERR(\"do_write returned %08lx\\n\", Status);\n\n    free_trees(Vcb);\n\n    if (!NT_SUCCESS(Status))\n        return Status;\n\n    if (!dev->readonly && dev->devobj) {\n        Status = remove_superblocks(dev);\n        if (!NT_SUCCESS(Status))\n            WARN(\"remove_superblocks returned %08lx\\n\", Status);\n    }\n\n    // remove entry in volume list\n\n    vde = Vcb->vde;\n\n    if (dev->devobj) {\n        pdo_device_extension* pdode = vde->pdode;\n\n        ExAcquireResourceExclusiveLite(&pdode->child_lock, true);\n\n        le = pdode->children.Flink;\n        while (le != &pdode->children) {\n            volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n            if (RtlCompareMemory(&dev->devitem.device_uuid, &vc->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n                PFILE_OBJECT FileObject;\n                PDEVICE_OBJECT mountmgr;\n                UNICODE_STRING mmdevpath;\n\n                pdode->children_loaded--;\n\n                if (vc->had_drive_letter) { // re-add entry to mountmgr\n                    RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);\n                    Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr);\n                    if (!NT_SUCCESS(Status))\n                        ERR(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n                    else {\n                        MOUNTDEV_NAME mdn;\n\n                        Status = dev_ioctl(dev->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL);\n                        if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)\n                            ERR(\"IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\\n\", Status);\n                        else {\n                            MOUNTDEV_NAME* mdn2;\n                            ULONG mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength;\n\n                            mdn2 = ExAllocatePoolWithTag(PagedPool, mdnsize, ALLOC_TAG);\n                            if (!mdn2)\n                                ERR(\"out of memory\\n\");\n                            else {\n                                Status = dev_ioctl(dev->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, true, NULL);\n                                if (!NT_SUCCESS(Status))\n                                    ERR(\"IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\\n\", Status);\n                                else {\n                                    UNICODE_STRING name;\n\n                                    name.Buffer = mdn2->Name;\n                                    name.Length = name.MaximumLength = mdn2->NameLength;\n\n                                    Status = mountmgr_add_drive_letter(mountmgr, &name);\n                                    if (!NT_SUCCESS(Status))\n                                        WARN(\"mountmgr_add_drive_letter returned %08lx\\n\", Status);\n                                }\n\n                                ExFreePool(mdn2);\n                            }\n                        }\n\n                        ObDereferenceObject(FileObject);\n                    }\n                }\n\n                ExFreePool(vc->pnp_name.Buffer);\n                RemoveEntryList(&vc->list_entry);\n                ExFreePool(vc);\n\n                ObDereferenceObject(vc->fileobj);\n\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        if (pdode->children_loaded > 0 && vde->device->Characteristics & FILE_REMOVABLE_MEDIA) {\n            vde->device->Characteristics &= ~FILE_REMOVABLE_MEDIA;\n\n            le = pdode->children.Flink;\n            while (le != &pdode->children) {\n                volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n                if (vc->devobj->Characteristics & FILE_REMOVABLE_MEDIA) {\n                    vde->device->Characteristics |= FILE_REMOVABLE_MEDIA;\n                    break;\n                }\n\n                le = le->Flink;\n            }\n        }\n\n        pdode->num_children = Vcb->superblock.num_devices;\n\n        ExReleaseResourceLite(&pdode->child_lock);\n\n        // free dev\n\n        if (dev->trim && !dev->readonly && !Vcb->options.no_trim)\n            trim_whole_device(dev);\n    }\n\n    while (!IsListEmpty(&dev->space)) {\n        LIST_ENTRY* le2 = RemoveHeadList(&dev->space);\n        space* s = CONTAINING_RECORD(le2, space, list_entry);\n\n        ExFreePool(s);\n    }\n\n    ExFreePool(dev);\n\n    if (Vcb->trim) {\n        Vcb->trim = false;\n\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev2 = CONTAINING_RECORD(le, device, list_entry);\n\n            if (dev2->trim) {\n                Vcb->trim = true;\n                break;\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_CHANGE_SIZE);\n\n    return STATUS_SUCCESS;\n}\n\nstatic void trim_unalloc_space(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, device* dev) {\n    DEVICE_MANAGE_DATA_SET_ATTRIBUTES* dmdsa;\n    DEVICE_DATA_SET_RANGE* ranges;\n    ULONG datalen, i;\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n    bool b;\n    uint64_t lastoff = 0x100000; // don't TRIM the first megabyte, in case someone has been daft enough to install GRUB there\n    LIST_ENTRY* le;\n\n    dev->num_trim_entries = 0;\n\n    searchkey.obj_id = dev->devitem.dev_id;\n    searchkey.obj_type = TYPE_DEV_EXTENT;\n    searchkey.offset = 0;\n\n    Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return;\n    }\n\n    do {\n        traverse_ptr next_tp;\n\n        if (tp.item->key.obj_id == dev->devitem.dev_id && tp.item->key.obj_type == TYPE_DEV_EXTENT) {\n            if (tp.item->size >= sizeof(DEV_EXTENT)) {\n                DEV_EXTENT* de = (DEV_EXTENT*)tp.item->data;\n\n                if (tp.item->key.offset > lastoff)\n                    add_trim_entry_avoid_sb(Vcb, dev, lastoff, tp.item->key.offset - lastoff);\n\n                lastoff = tp.item->key.offset + de->length;\n            } else {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DEV_EXTENT));\n                return;\n            }\n        }\n\n        b = find_next_item(Vcb, &tp, &next_tp, false, NULL);\n\n        if (b) {\n            tp = next_tp;\n            if (tp.item->key.obj_id > searchkey.obj_id || (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type > searchkey.obj_type))\n                break;\n        }\n    } while (b);\n\n    if (lastoff < dev->devitem.num_bytes)\n        add_trim_entry_avoid_sb(Vcb, dev, lastoff, dev->devitem.num_bytes - lastoff);\n\n    if (dev->num_trim_entries == 0)\n        return;\n\n    datalen = (ULONG)sector_align(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), sizeof(uint64_t)) + (dev->num_trim_entries * sizeof(DEVICE_DATA_SET_RANGE));\n\n    dmdsa = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG);\n    if (!dmdsa) {\n        ERR(\"out of memory\\n\");\n        goto end;\n    }\n\n    dmdsa->Size = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES);\n    dmdsa->Action = DeviceDsmAction_Trim;\n    dmdsa->Flags = DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED;\n    dmdsa->ParameterBlockOffset = 0;\n    dmdsa->ParameterBlockLength = 0;\n    dmdsa->DataSetRangesOffset = (ULONG)sector_align(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), sizeof(uint64_t));\n    dmdsa->DataSetRangesLength = dev->num_trim_entries * sizeof(DEVICE_DATA_SET_RANGE);\n\n    ranges = (DEVICE_DATA_SET_RANGE*)((uint8_t*)dmdsa + dmdsa->DataSetRangesOffset);\n\n    i = 0;\n    le = dev->trim_list.Flink;\n    while (le != &dev->trim_list) {\n        space* s = CONTAINING_RECORD(le, space, list_entry);\n\n        ranges[i].StartingOffset = s->address;\n        ranges[i].LengthInBytes = s->size;\n        i++;\n\n        le = le->Flink;\n    }\n\n    Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES, dmdsa, datalen, NULL, 0, true, NULL);\n    if (!NT_SUCCESS(Status))\n        WARN(\"IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES returned %08lx\\n\", Status);\n\n    ExFreePool(dmdsa);\n\nend:\n    while (!IsListEmpty(&dev->trim_list)) {\n        space* s = CONTAINING_RECORD(RemoveHeadList(&dev->trim_list), space, list_entry);\n        ExFreePool(s);\n    }\n\n    dev->num_trim_entries = 0;\n}\n\nstatic NTSTATUS try_consolidation(device_extension* Vcb, uint64_t flags, chunk** newchunk) {\n    NTSTATUS Status;\n    bool changed;\n    LIST_ENTRY* le;\n    chunk* rc;\n\n    // FIXME - allow with metadata chunks?\n\n    while (true) {\n        rc = NULL;\n\n        ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n        ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);\n\n        // choose the least-used chunk we haven't looked at yet\n        le = Vcb->chunks.Flink;\n        while (le != &Vcb->chunks) {\n            chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n\n            // FIXME - skip full-size chunks over e.g. 90% full?\n            if (c->chunk_item->type & BLOCK_FLAG_DATA && !c->readonly && c->balance_num != Vcb->balance.balance_num && (!rc || c->used < rc->used))\n                rc = c;\n\n            le = le->Flink;\n        }\n\n        ExReleaseResourceLite(&Vcb->chunk_lock);\n\n        if (!rc) {\n            ExReleaseResourceLite(&Vcb->tree_lock);\n            break;\n        }\n\n        if (rc->list_entry_balance.Flink) {\n            RemoveEntryList(&rc->list_entry_balance);\n            Vcb->balance.chunks_left--;\n        }\n\n        rc->list_entry_balance.Flink = (LIST_ENTRY*)1; // so it doesn't get dropped\n        rc->reloc = true;\n\n        ExReleaseResourceLite(&Vcb->tree_lock);\n\n        do {\n            changed = false;\n\n            Status = balance_data_chunk(Vcb, rc, &changed);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"balance_data_chunk returned %08lx\\n\", Status);\n                Vcb->balance.status = Status;\n                rc->list_entry_balance.Flink = NULL;\n                rc->reloc = false;\n                return Status;\n            }\n\n            KeWaitForSingleObject(&Vcb->balance.event, Executive, KernelMode, false, NULL);\n\n            if (Vcb->readonly)\n                Vcb->balance.stopping = true;\n\n            if (Vcb->balance.stopping)\n                return STATUS_SUCCESS;\n        } while (changed);\n\n        rc->list_entry_balance.Flink = NULL;\n\n        rc->changed = true;\n        rc->space_changed = true;\n        rc->balance_num = Vcb->balance.balance_num;\n\n        Status = do_write(Vcb, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"do_write returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        free_trees(Vcb);\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true);\n\n    Status = alloc_chunk(Vcb, flags, &rc, true);\n\n    ExReleaseResourceLite(&Vcb->chunk_lock);\n\n    if (NT_SUCCESS(Status)) {\n        *newchunk = rc;\n        return Status;\n    } else {\n        ERR(\"alloc_chunk returned %08lx\\n\", Status);\n        return Status;\n    }\n}\n\nstatic NTSTATUS regenerate_space_list(device_extension* Vcb, device* dev) {\n    LIST_ENTRY* le;\n\n    while (!IsListEmpty(&dev->space)) {\n        space* s = CONTAINING_RECORD(RemoveHeadList(&dev->space), space, list_entry);\n\n        ExFreePool(s);\n    }\n\n    // The Linux driver doesn't like to allocate chunks within the first megabyte of a device.\n\n    space_list_add2(&dev->space, NULL, 0x100000, dev->devitem.num_bytes - 0x100000, NULL, NULL);\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        uint16_t n;\n        chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n        CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n\n        for (n = 0; n < c->chunk_item->num_stripes; n++) {\n            uint64_t stripe_size = 0;\n\n            if (cis[n].dev_id == dev->devitem.dev_id) {\n                if (stripe_size == 0) {\n                    uint16_t factor;\n\n                    if (c->chunk_item->type & BLOCK_FLAG_RAID0)\n                        factor = c->chunk_item->num_stripes;\n                    else if (c->chunk_item->type & BLOCK_FLAG_RAID10)\n                        factor = c->chunk_item->num_stripes / c->chunk_item->sub_stripes;\n                    else if (c->chunk_item->type & BLOCK_FLAG_RAID5)\n                        factor = c->chunk_item->num_stripes - 1;\n                    else if (c->chunk_item->type & BLOCK_FLAG_RAID6)\n                        factor = c->chunk_item->num_stripes - 2;\n                    else // SINGLE, DUP, RAID1, RAID1C3, RAID1C4\n                        factor = 1;\n\n                    stripe_size = c->chunk_item->size / factor;\n                }\n\n                space_list_subtract2(&dev->space, NULL, cis[n].offset, stripe_size, NULL, NULL);\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    return STATUS_SUCCESS;\n}\n\n_Function_class_(KSTART_ROUTINE)\nvoid __stdcall balance_thread(void* context) {\n    device_extension* Vcb = (device_extension*)context;\n    LIST_ENTRY chunks;\n    LIST_ENTRY* le;\n    uint64_t num_chunks[3], okay_metadata_chunks = 0, okay_data_chunks = 0, okay_system_chunks = 0;\n    uint64_t old_data_flags = 0, old_metadata_flags = 0, old_system_flags = 0;\n    NTSTATUS Status;\n\n    Vcb->balance.balance_num++;\n\n    Vcb->balance.stopping = false;\n    KeInitializeEvent(&Vcb->balance.finished, NotificationEvent, false);\n\n    if (Vcb->balance.opts[BALANCE_OPTS_DATA].flags & BTRFS_BALANCE_OPTS_ENABLED && Vcb->balance.opts[BALANCE_OPTS_DATA].flags & BTRFS_BALANCE_OPTS_CONVERT) {\n        old_data_flags = Vcb->data_flags;\n        Vcb->data_flags = BLOCK_FLAG_DATA | (Vcb->balance.opts[BALANCE_OPTS_DATA].convert == BLOCK_FLAG_SINGLE ? 0 : Vcb->balance.opts[BALANCE_OPTS_DATA].convert);\n\n        FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_CHANGE_SIZE);\n    }\n\n    if (Vcb->balance.opts[BALANCE_OPTS_METADATA].flags & BTRFS_BALANCE_OPTS_ENABLED && Vcb->balance.opts[BALANCE_OPTS_METADATA].flags & BTRFS_BALANCE_OPTS_CONVERT) {\n        old_metadata_flags = Vcb->metadata_flags;\n        Vcb->metadata_flags = BLOCK_FLAG_METADATA | (Vcb->balance.opts[BALANCE_OPTS_METADATA].convert == BLOCK_FLAG_SINGLE ? 0 : Vcb->balance.opts[BALANCE_OPTS_METADATA].convert);\n    }\n\n    if (Vcb->balance.opts[BALANCE_OPTS_SYSTEM].flags & BTRFS_BALANCE_OPTS_ENABLED && Vcb->balance.opts[BALANCE_OPTS_SYSTEM].flags & BTRFS_BALANCE_OPTS_CONVERT) {\n        old_system_flags = Vcb->system_flags;\n        Vcb->system_flags = BLOCK_FLAG_SYSTEM | (Vcb->balance.opts[BALANCE_OPTS_SYSTEM].convert == BLOCK_FLAG_SINGLE ? 0 : Vcb->balance.opts[BALANCE_OPTS_SYSTEM].convert);\n    }\n\n    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS) {\n        if (Vcb->balance.opts[BALANCE_OPTS_DATA].flags & BTRFS_BALANCE_OPTS_ENABLED)\n            RtlCopyMemory(&Vcb->balance.opts[BALANCE_OPTS_METADATA], &Vcb->balance.opts[BALANCE_OPTS_DATA], sizeof(btrfs_balance_opts));\n        else if (Vcb->balance.opts[BALANCE_OPTS_METADATA].flags & BTRFS_BALANCE_OPTS_ENABLED)\n            RtlCopyMemory(&Vcb->balance.opts[BALANCE_OPTS_DATA], &Vcb->balance.opts[BALANCE_OPTS_METADATA], sizeof(btrfs_balance_opts));\n    }\n\n    num_chunks[0] = num_chunks[1] = num_chunks[2] = 0;\n    Vcb->balance.total_chunks = Vcb->balance.chunks_left = 0;\n\n    InitializeListHead(&chunks);\n\n    // FIXME - what are we supposed to do with limit_start?\n\n    if (!Vcb->readonly) {\n        if (!Vcb->balance.removing && !Vcb->balance.shrinking) {\n            Status = add_balance_item(Vcb);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"add_balance_item returned %08lx\\n\", Status);\n                Vcb->balance.status = Status;\n                goto end;\n            }\n        } else {\n            if (Vcb->need_write) {\n                Status = do_write(Vcb, NULL);\n\n                free_trees(Vcb);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"do_write returned %08lx\\n\", Status);\n                    Vcb->balance.status = Status;\n                    goto end;\n                }\n            }\n        }\n    }\n\n    KeWaitForSingleObject(&Vcb->balance.event, Executive, KernelMode, false, NULL);\n\n    if (Vcb->balance.stopping)\n        goto end;\n\n    ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n        uint8_t sort;\n\n        acquire_chunk_lock(c, Vcb);\n\n        if (c->chunk_item->type & BLOCK_FLAG_DATA)\n            sort = BALANCE_OPTS_DATA;\n        else if (c->chunk_item->type & BLOCK_FLAG_METADATA)\n            sort = BALANCE_OPTS_METADATA;\n        else if (c->chunk_item->type & BLOCK_FLAG_SYSTEM)\n            sort = BALANCE_OPTS_SYSTEM;\n        else {\n            ERR(\"unexpected chunk type %I64x\\n\", c->chunk_item->type);\n            release_chunk_lock(c, Vcb);\n            break;\n        }\n\n        if ((!(Vcb->balance.opts[sort].flags & BTRFS_BALANCE_OPTS_LIMIT) || num_chunks[sort] < Vcb->balance.opts[sort].limit_end) &&\n            should_balance_chunk(Vcb, sort, c)) {\n            InsertTailList(&chunks, &c->list_entry_balance);\n\n            num_chunks[sort]++;\n            Vcb->balance.total_chunks++;\n            Vcb->balance.chunks_left++;\n        } else if (sort == BALANCE_OPTS_METADATA)\n            okay_metadata_chunks++;\n        else if (sort == BALANCE_OPTS_DATA)\n            okay_data_chunks++;\n        else if (sort == BALANCE_OPTS_SYSTEM)\n            okay_system_chunks++;\n\n        if (!c->cache_loaded) {\n            Status = load_cache_chunk(Vcb, c, NULL);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"load_cache_chunk returned %08lx\\n\", Status);\n                Vcb->balance.status = Status;\n                release_chunk_lock(c, Vcb);\n                ExReleaseResourceLite(&Vcb->chunk_lock);\n                goto end;\n            }\n        }\n\n        release_chunk_lock(c, Vcb);\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&Vcb->chunk_lock);\n\n    // If we're doing a full balance, try and allocate a new chunk now, before we mess things up\n    if (okay_metadata_chunks == 0 || okay_data_chunks == 0 || okay_system_chunks == 0) {\n        bool consolidated = false;\n        chunk* c;\n\n        if (okay_metadata_chunks == 0) {\n            ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true);\n\n            Status = alloc_chunk(Vcb, Vcb->metadata_flags, &c, true);\n            if (NT_SUCCESS(Status))\n                c->balance_num = Vcb->balance.balance_num;\n            else if (Status != STATUS_DISK_FULL || consolidated) {\n                ERR(\"alloc_chunk returned %08lx\\n\", Status);\n                ExReleaseResourceLite(&Vcb->chunk_lock);\n                Vcb->balance.status = Status;\n                goto end;\n            }\n\n            ExReleaseResourceLite(&Vcb->chunk_lock);\n\n            if (Status == STATUS_DISK_FULL) {\n                Status = try_consolidation(Vcb, Vcb->metadata_flags, &c);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"try_consolidation returned %08lx\\n\", Status);\n                    Vcb->balance.status = Status;\n                    goto end;\n                } else\n                    c->balance_num = Vcb->balance.balance_num;\n\n                consolidated = true;\n\n                if (Vcb->balance.stopping)\n                    goto end;\n            }\n        }\n\n        if (okay_data_chunks == 0) {\n            ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true);\n\n            Status = alloc_chunk(Vcb, Vcb->data_flags, &c, true);\n            if (NT_SUCCESS(Status))\n                c->balance_num = Vcb->balance.balance_num;\n            else if (Status != STATUS_DISK_FULL || consolidated) {\n                ERR(\"alloc_chunk returned %08lx\\n\", Status);\n                ExReleaseResourceLite(&Vcb->chunk_lock);\n                Vcb->balance.status = Status;\n                goto end;\n            }\n\n            ExReleaseResourceLite(&Vcb->chunk_lock);\n\n            if (Status == STATUS_DISK_FULL) {\n                Status = try_consolidation(Vcb, Vcb->data_flags, &c);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"try_consolidation returned %08lx\\n\", Status);\n                    Vcb->balance.status = Status;\n                    goto end;\n                } else\n                    c->balance_num = Vcb->balance.balance_num;\n\n                consolidated = true;\n\n                if (Vcb->balance.stopping)\n                    goto end;\n            }\n        }\n\n        if (okay_system_chunks == 0) {\n            ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true);\n\n            Status = alloc_chunk(Vcb, Vcb->system_flags, &c, true);\n            if (NT_SUCCESS(Status))\n                c->balance_num = Vcb->balance.balance_num;\n            else if (Status != STATUS_DISK_FULL || consolidated) {\n                ERR(\"alloc_chunk returned %08lx\\n\", Status);\n                ExReleaseResourceLite(&Vcb->chunk_lock);\n                Vcb->balance.status = Status;\n                goto end;\n            }\n\n            ExReleaseResourceLite(&Vcb->chunk_lock);\n\n            if (Status == STATUS_DISK_FULL) {\n                Status = try_consolidation(Vcb, Vcb->system_flags, &c);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"try_consolidation returned %08lx\\n\", Status);\n                    Vcb->balance.status = Status;\n                    goto end;\n                } else\n                    c->balance_num = Vcb->balance.balance_num;\n\n                consolidated = true;\n\n                if (Vcb->balance.stopping)\n                    goto end;\n            }\n        }\n    }\n\n    ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);\n\n    le = chunks.Flink;\n    while (le != &chunks) {\n        chunk* c = CONTAINING_RECORD(le, chunk, list_entry_balance);\n\n        c->reloc = true;\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&Vcb->chunk_lock);\n\n    // do data chunks before metadata\n    le = chunks.Flink;\n    while (le != &chunks) {\n        chunk* c = CONTAINING_RECORD(le, chunk, list_entry_balance);\n        LIST_ENTRY* le2 = le->Flink;\n\n        if (c->chunk_item->type & BLOCK_FLAG_DATA) {\n            bool changed;\n\n            do {\n                changed = false;\n\n                Status = balance_data_chunk(Vcb, c, &changed);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"balance_data_chunk returned %08lx\\n\", Status);\n                    Vcb->balance.status = Status;\n                    goto end;\n                }\n\n                KeWaitForSingleObject(&Vcb->balance.event, Executive, KernelMode, false, NULL);\n\n                if (Vcb->readonly)\n                    Vcb->balance.stopping = true;\n\n                if (Vcb->balance.stopping)\n                    break;\n            } while (changed);\n\n            c->changed = true;\n            c->space_changed = true;\n        }\n\n        if (Vcb->balance.stopping)\n            goto end;\n\n        if (c->chunk_item->type & BLOCK_FLAG_DATA &&\n            (!(Vcb->balance.opts[BALANCE_OPTS_METADATA].flags & BTRFS_BALANCE_OPTS_ENABLED) || !(c->chunk_item->type & BLOCK_FLAG_METADATA))) {\n            RemoveEntryList(&c->list_entry_balance);\n            c->list_entry_balance.Flink = NULL;\n\n            Vcb->balance.chunks_left--;\n        }\n\n        le = le2;\n    }\n\n    // do metadata chunks\n    while (!IsListEmpty(&chunks)) {\n        chunk* c;\n        bool changed;\n\n        le = RemoveHeadList(&chunks);\n        c = CONTAINING_RECORD(le, chunk, list_entry_balance);\n\n        if (c->chunk_item->type & BLOCK_FLAG_METADATA || c->chunk_item->type & BLOCK_FLAG_SYSTEM) {\n            do {\n                Status = balance_metadata_chunk(Vcb, c, &changed);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"balance_metadata_chunk returned %08lx\\n\", Status);\n                    Vcb->balance.status = Status;\n                    goto end;\n                }\n\n                KeWaitForSingleObject(&Vcb->balance.event, Executive, KernelMode, false, NULL);\n\n                if (Vcb->readonly)\n                    Vcb->balance.stopping = true;\n\n                if (Vcb->balance.stopping)\n                    break;\n            } while (changed);\n\n            c->changed = true;\n            c->space_changed = true;\n        }\n\n        if (Vcb->balance.stopping)\n            break;\n\n        c->list_entry_balance.Flink = NULL;\n\n        Vcb->balance.chunks_left--;\n    }\n\nend:\n    if (!Vcb->readonly) {\n        if (Vcb->balance.stopping || !NT_SUCCESS(Vcb->balance.status)) {\n            le = chunks.Flink;\n            while (le != &chunks) {\n                chunk* c = CONTAINING_RECORD(le, chunk, list_entry_balance);\n                c->reloc = false;\n\n                le = le->Flink;\n                c->list_entry_balance.Flink = NULL;\n            }\n\n            if (old_data_flags != 0)\n                Vcb->data_flags = old_data_flags;\n\n            if (old_metadata_flags != 0)\n                Vcb->metadata_flags = old_metadata_flags;\n\n            if (old_system_flags != 0)\n                Vcb->system_flags = old_system_flags;\n        }\n\n        if (Vcb->balance.removing) {\n            device* dev = NULL;\n\n            ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n            le = Vcb->devices.Flink;\n            while (le != &Vcb->devices) {\n                device* dev2 = CONTAINING_RECORD(le, device, list_entry);\n\n                if (dev2->devitem.dev_id == Vcb->balance.opts[0].devid) {\n                    dev = dev2;\n                    break;\n                }\n\n                le = le->Flink;\n            }\n\n            if (dev) {\n                if (Vcb->balance.chunks_left == 0) {\n                    Status = finish_removing_device(Vcb, dev);\n\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"finish_removing_device returned %08lx\\n\", Status);\n                        dev->reloc = false;\n                    }\n                } else\n                    dev->reloc = false;\n            }\n\n            ExReleaseResourceLite(&Vcb->tree_lock);\n        } else if (Vcb->balance.shrinking) {\n            device* dev = NULL;\n\n            ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n            le = Vcb->devices.Flink;\n            while (le != &Vcb->devices) {\n                device* dev2 = CONTAINING_RECORD(le, device, list_entry);\n\n                if (dev2->devitem.dev_id == Vcb->balance.opts[0].devid) {\n                    dev = dev2;\n                    break;\n                }\n\n                le = le->Flink;\n            }\n\n            if (!dev) {\n                ERR(\"could not find device %I64x\\n\", Vcb->balance.opts[0].devid);\n                Vcb->balance.status = STATUS_INTERNAL_ERROR;\n            }\n\n            if (Vcb->balance.stopping || !NT_SUCCESS(Vcb->balance.status)) {\n                if (dev) {\n                    Status = regenerate_space_list(Vcb, dev);\n                    if (!NT_SUCCESS(Status))\n                        WARN(\"regenerate_space_list returned %08lx\\n\", Status);\n                }\n            } else {\n                uint64_t old_size;\n\n                old_size = dev->devitem.num_bytes;\n                dev->devitem.num_bytes = Vcb->balance.opts[0].drange_start;\n\n                Status = update_dev_item(Vcb, dev, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"update_dev_item returned %08lx\\n\", Status);\n                    dev->devitem.num_bytes = old_size;\n                    Vcb->balance.status = Status;\n\n                    Status = regenerate_space_list(Vcb, dev);\n                    if (!NT_SUCCESS(Status))\n                        WARN(\"regenerate_space_list returned %08lx\\n\", Status);\n                } else {\n                    Vcb->superblock.total_bytes -= old_size - dev->devitem.num_bytes;\n\n                    Status = do_write(Vcb, NULL);\n                    if (!NT_SUCCESS(Status))\n                        ERR(\"do_write returned %08lx\\n\", Status);\n\n                    free_trees(Vcb);\n                }\n            }\n\n            ExReleaseResourceLite(&Vcb->tree_lock);\n\n            if (!Vcb->balance.stopping && NT_SUCCESS(Vcb->balance.status))\n                FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_CHANGE_SIZE);\n        } else {\n            Status = remove_balance_item(Vcb);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"remove_balance_item returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        if (Vcb->trim && !Vcb->options.no_trim) {\n            ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n            le = Vcb->devices.Flink;\n            while (le != &Vcb->devices) {\n                device* dev2 = CONTAINING_RECORD(le, device, list_entry);\n\n                if (dev2->devobj && !dev2->readonly && dev2->trim)\n                    trim_unalloc_space(Vcb, dev2);\n\n                le = le->Flink;\n            }\n\n            ExReleaseResourceLite(&Vcb->tree_lock);\n        }\n    }\n\n    ZwClose(Vcb->balance.thread);\n    Vcb->balance.thread = NULL;\n\n    KeSetEvent(&Vcb->balance.finished, 0, false);\n}\n\nNTSTATUS start_balance(device_extension* Vcb, void* data, ULONG length, KPROCESSOR_MODE processor_mode) {\n    NTSTATUS Status;\n    btrfs_start_balance* bsb = (btrfs_start_balance*)data;\n    OBJECT_ATTRIBUTES oa;\n    uint8_t i;\n\n    if (length < sizeof(btrfs_start_balance) || !data)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (Vcb->locked) {\n        WARN(\"cannot start balance while locked\\n\");\n        return STATUS_DEVICE_NOT_READY;\n    }\n\n    if (Vcb->scrub.thread) {\n        WARN(\"cannot start balance while scrub running\\n\");\n        return STATUS_DEVICE_NOT_READY;\n    }\n\n    if (Vcb->balance.thread) {\n        WARN(\"balance already running\\n\");\n        return STATUS_DEVICE_NOT_READY;\n    }\n\n    if (Vcb->readonly)\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n    if (!(bsb->opts[BALANCE_OPTS_DATA].flags & BTRFS_BALANCE_OPTS_ENABLED) &&\n        !(bsb->opts[BALANCE_OPTS_METADATA].flags & BTRFS_BALANCE_OPTS_ENABLED) &&\n        !(bsb->opts[BALANCE_OPTS_SYSTEM].flags & BTRFS_BALANCE_OPTS_ENABLED))\n        return STATUS_SUCCESS;\n\n    for (i = 0; i < 3; i++) {\n        if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_ENABLED) {\n            if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_PROFILES) {\n                bsb->opts[i].profiles &= BLOCK_FLAG_RAID0 | BLOCK_FLAG_RAID1 | BLOCK_FLAG_DUPLICATE | BLOCK_FLAG_RAID10 |\n                                         BLOCK_FLAG_RAID5 | BLOCK_FLAG_RAID6 | BLOCK_FLAG_SINGLE | BLOCK_FLAG_RAID1C3 |\n                                         BLOCK_FLAG_RAID1C4;\n\n                if (bsb->opts[i].profiles == 0)\n                    return STATUS_INVALID_PARAMETER;\n            }\n\n            if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_DEVID) {\n                if (bsb->opts[i].devid == 0)\n                    return STATUS_INVALID_PARAMETER;\n            }\n\n            if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_DRANGE) {\n                if (bsb->opts[i].drange_start > bsb->opts[i].drange_end)\n                    return STATUS_INVALID_PARAMETER;\n            }\n\n            if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_VRANGE) {\n                if (bsb->opts[i].vrange_start > bsb->opts[i].vrange_end)\n                    return STATUS_INVALID_PARAMETER;\n            }\n\n            if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_LIMIT) {\n                bsb->opts[i].limit_start = max(1, bsb->opts[i].limit_start);\n                bsb->opts[i].limit_end = max(1, bsb->opts[i].limit_end);\n\n                if (bsb->opts[i].limit_start > bsb->opts[i].limit_end)\n                    return STATUS_INVALID_PARAMETER;\n            }\n\n            if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_STRIPES) {\n                bsb->opts[i].stripes_start = max(1, bsb->opts[i].stripes_start);\n                bsb->opts[i].stripes_end = max(1, bsb->opts[i].stripes_end);\n\n                if (bsb->opts[i].stripes_start > bsb->opts[i].stripes_end)\n                    return STATUS_INVALID_PARAMETER;\n            }\n\n            if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_USAGE) {\n                bsb->opts[i].usage_start = min(100, bsb->opts[i].stripes_start);\n                bsb->opts[i].usage_end = min(100, bsb->opts[i].stripes_end);\n\n                if (bsb->opts[i].stripes_start > bsb->opts[i].stripes_end)\n                    return STATUS_INVALID_PARAMETER;\n            }\n\n            if (bsb->opts[i].flags & BTRFS_BALANCE_OPTS_CONVERT) {\n                if (bsb->opts[i].convert != BLOCK_FLAG_RAID0 && bsb->opts[i].convert != BLOCK_FLAG_RAID1 &&\n                    bsb->opts[i].convert != BLOCK_FLAG_DUPLICATE && bsb->opts[i].convert != BLOCK_FLAG_RAID10 &&\n                    bsb->opts[i].convert != BLOCK_FLAG_RAID5 && bsb->opts[i].convert != BLOCK_FLAG_RAID6 &&\n                    bsb->opts[i].convert != BLOCK_FLAG_SINGLE && bsb->opts[i].convert != BLOCK_FLAG_RAID1C3 &&\n                    bsb->opts[i].convert != BLOCK_FLAG_RAID1C4)\n                    return STATUS_INVALID_PARAMETER;\n            }\n        }\n    }\n\n    RtlCopyMemory(&Vcb->balance.opts[BALANCE_OPTS_DATA], &bsb->opts[BALANCE_OPTS_DATA], sizeof(btrfs_balance_opts));\n    RtlCopyMemory(&Vcb->balance.opts[BALANCE_OPTS_METADATA], &bsb->opts[BALANCE_OPTS_METADATA], sizeof(btrfs_balance_opts));\n    RtlCopyMemory(&Vcb->balance.opts[BALANCE_OPTS_SYSTEM], &bsb->opts[BALANCE_OPTS_SYSTEM], sizeof(btrfs_balance_opts));\n\n    Vcb->balance.paused = false;\n    Vcb->balance.removing = false;\n    Vcb->balance.shrinking = false;\n    Vcb->balance.status = STATUS_SUCCESS;\n    KeInitializeEvent(&Vcb->balance.event, NotificationEvent, !Vcb->balance.paused);\n\n    InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = PsCreateSystemThread(&Vcb->balance.thread, 0, &oa, NULL, NULL, balance_thread, Vcb);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"PsCreateSystemThread returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS look_for_balance_item(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n    BALANCE_ITEM* bi;\n    OBJECT_ATTRIBUTES oa;\n    int i;\n\n    searchkey.obj_id = BALANCE_ITEM_ID;\n    searchkey.obj_type = TYPE_TEMP_ITEM;\n    searchkey.offset = 0;\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (keycmp(tp.item->key, searchkey)) {\n        TRACE(\"no balance item found\\n\");\n        return STATUS_NOT_FOUND;\n    }\n\n    if (tp.item->size < sizeof(BALANCE_ITEM)) {\n        WARN(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n             tp.item->size, sizeof(BALANCE_ITEM));\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    bi = (BALANCE_ITEM*)tp.item->data;\n\n    if (bi->flags & BALANCE_FLAGS_DATA)\n        load_balance_args(&Vcb->balance.opts[BALANCE_OPTS_DATA], &bi->data);\n\n    if (bi->flags & BALANCE_FLAGS_METADATA)\n        load_balance_args(&Vcb->balance.opts[BALANCE_OPTS_METADATA], &bi->metadata);\n\n    if (bi->flags & BALANCE_FLAGS_SYSTEM)\n        load_balance_args(&Vcb->balance.opts[BALANCE_OPTS_SYSTEM], &bi->system);\n\n    // do the heuristics that Linux driver does\n\n    for (i = 0; i < 3; i++) {\n        if (Vcb->balance.opts[i].flags & BTRFS_BALANCE_OPTS_ENABLED) {\n            // if converting, don't redo chunks already done\n\n            if (Vcb->balance.opts[i].flags & BTRFS_BALANCE_OPTS_CONVERT)\n                Vcb->balance.opts[i].flags |= BTRFS_BALANCE_OPTS_SOFT;\n\n            // don't balance chunks more than 90% filled - presumably these\n            // have already been done\n\n            if (!(Vcb->balance.opts[i].flags & BTRFS_BALANCE_OPTS_USAGE) &&\n                !(Vcb->balance.opts[i].flags & BTRFS_BALANCE_OPTS_CONVERT)\n            ) {\n                Vcb->balance.opts[i].flags |= BTRFS_BALANCE_OPTS_USAGE;\n                Vcb->balance.opts[i].usage_start = 0;\n                Vcb->balance.opts[i].usage_end = 90;\n            }\n        }\n    }\n\n    if (Vcb->readonly || Vcb->options.skip_balance)\n        Vcb->balance.paused = true;\n    else\n        Vcb->balance.paused = false;\n\n    Vcb->balance.removing = false;\n    Vcb->balance.shrinking = false;\n    Vcb->balance.status = STATUS_SUCCESS;\n    KeInitializeEvent(&Vcb->balance.event, NotificationEvent, !Vcb->balance.paused);\n\n    InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = PsCreateSystemThread(&Vcb->balance.thread, 0, &oa, NULL, NULL, balance_thread, Vcb);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"PsCreateSystemThread returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS query_balance(device_extension* Vcb, void* data, ULONG length) {\n    btrfs_query_balance* bqb = (btrfs_query_balance*)data;\n\n    if (length < sizeof(btrfs_query_balance) || !data)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!Vcb->balance.thread) {\n        bqb->status = BTRFS_BALANCE_STOPPED;\n\n        if (!NT_SUCCESS(Vcb->balance.status)) {\n            bqb->status |= BTRFS_BALANCE_ERROR;\n            bqb->error = Vcb->balance.status;\n        }\n\n        return STATUS_SUCCESS;\n    }\n\n    bqb->status = Vcb->balance.paused ? BTRFS_BALANCE_PAUSED : BTRFS_BALANCE_RUNNING;\n\n    if (Vcb->balance.removing)\n        bqb->status |= BTRFS_BALANCE_REMOVAL;\n\n    if (Vcb->balance.shrinking)\n        bqb->status |= BTRFS_BALANCE_SHRINKING;\n\n    if (!NT_SUCCESS(Vcb->balance.status))\n        bqb->status |= BTRFS_BALANCE_ERROR;\n\n    bqb->chunks_left = Vcb->balance.chunks_left;\n    bqb->total_chunks = Vcb->balance.total_chunks;\n    bqb->error = Vcb->balance.status;\n    RtlCopyMemory(&bqb->data_opts, &Vcb->balance.opts[BALANCE_OPTS_DATA], sizeof(btrfs_balance_opts));\n    RtlCopyMemory(&bqb->metadata_opts, &Vcb->balance.opts[BALANCE_OPTS_METADATA], sizeof(btrfs_balance_opts));\n    RtlCopyMemory(&bqb->system_opts, &Vcb->balance.opts[BALANCE_OPTS_SYSTEM], sizeof(btrfs_balance_opts));\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS pause_balance(device_extension* Vcb, KPROCESSOR_MODE processor_mode) {\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (!Vcb->balance.thread)\n        return STATUS_DEVICE_NOT_READY;\n\n    if (Vcb->balance.paused)\n        return STATUS_DEVICE_NOT_READY;\n\n    Vcb->balance.paused = true;\n    KeClearEvent(&Vcb->balance.event);\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS resume_balance(device_extension* Vcb, KPROCESSOR_MODE processor_mode) {\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (!Vcb->balance.thread)\n        return STATUS_DEVICE_NOT_READY;\n\n    if (!Vcb->balance.paused)\n        return STATUS_DEVICE_NOT_READY;\n\n    if (Vcb->readonly)\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n    Vcb->balance.paused = false;\n    KeSetEvent(&Vcb->balance.event, 0, false);\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS stop_balance(device_extension* Vcb, KPROCESSOR_MODE processor_mode) {\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (!Vcb->balance.thread)\n        return STATUS_DEVICE_NOT_READY;\n\n    Vcb->balance.paused = false;\n    Vcb->balance.stopping = true;\n    Vcb->balance.status = STATUS_SUCCESS;\n    KeSetEvent(&Vcb->balance.event, 0, false);\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS remove_device(device_extension* Vcb, void* data, ULONG length, KPROCESSOR_MODE processor_mode) {\n    uint64_t devid;\n    LIST_ENTRY* le;\n    device* dev = NULL;\n    NTSTATUS Status;\n    int i;\n    uint64_t num_rw_devices;\n    OBJECT_ATTRIBUTES oa;\n\n    TRACE(\"(%p, %p, %lx)\\n\", Vcb, data, length);\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (length < sizeof(uint64_t))\n        return STATUS_INVALID_PARAMETER;\n\n    devid = *(uint64_t*)data;\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    if (Vcb->readonly) {\n        ExReleaseResourceLite(&Vcb->tree_lock);\n        return STATUS_MEDIA_WRITE_PROTECTED;\n    }\n\n    num_rw_devices = 0;\n\n    le = Vcb->devices.Flink;\n    while (le != &Vcb->devices) {\n        device* dev2 = CONTAINING_RECORD(le, device, list_entry);\n\n        if (dev2->devitem.dev_id == devid)\n            dev = dev2;\n\n        if (!dev2->readonly)\n            num_rw_devices++;\n\n        le = le->Flink;\n    }\n\n    if (!dev) {\n        ExReleaseResourceLite(&Vcb->tree_lock);\n        WARN(\"device %I64x not found\\n\", devid);\n        return STATUS_NOT_FOUND;\n    }\n\n    if (!dev->readonly) {\n        if (num_rw_devices == 1) {\n            ExReleaseResourceLite(&Vcb->tree_lock);\n            WARN(\"not removing last non-readonly device\\n\");\n            return STATUS_INVALID_PARAMETER;\n        }\n\n        if (num_rw_devices == 4 &&\n            ((Vcb->data_flags & BLOCK_FLAG_RAID10 || Vcb->metadata_flags & BLOCK_FLAG_RAID10 || Vcb->system_flags & BLOCK_FLAG_RAID10) ||\n             (Vcb->data_flags & BLOCK_FLAG_RAID6 || Vcb->metadata_flags & BLOCK_FLAG_RAID6 || Vcb->system_flags & BLOCK_FLAG_RAID6) ||\n             (Vcb->data_flags & BLOCK_FLAG_RAID1C4 || Vcb->metadata_flags & BLOCK_FLAG_RAID1C4 || Vcb->system_flags & BLOCK_FLAG_RAID1C4)\n            )\n        ) {\n            ExReleaseResourceLite(&Vcb->tree_lock);\n            ERR(\"would not be enough devices to satisfy RAID requirement (RAID6/10/1C4)\\n\");\n            return STATUS_CANNOT_DELETE;\n        }\n\n        if (num_rw_devices == 3 &&\n            ((Vcb->data_flags & BLOCK_FLAG_RAID5 || Vcb->metadata_flags & BLOCK_FLAG_RAID5 || Vcb->system_flags & BLOCK_FLAG_RAID5) ||\n            (Vcb->data_flags & BLOCK_FLAG_RAID1C3 || Vcb->metadata_flags & BLOCK_FLAG_RAID1C3 || Vcb->system_flags & BLOCK_FLAG_RAID1C3))\n            ) {\n            ExReleaseResourceLite(&Vcb->tree_lock);\n            ERR(\"would not be enough devices to satisfy RAID requirement (RAID5/1C3)\\n\");\n            return STATUS_CANNOT_DELETE;\n        }\n\n        if (num_rw_devices == 2 &&\n            ((Vcb->data_flags & BLOCK_FLAG_RAID0 || Vcb->metadata_flags & BLOCK_FLAG_RAID0 || Vcb->system_flags & BLOCK_FLAG_RAID0) ||\n             (Vcb->data_flags & BLOCK_FLAG_RAID1 || Vcb->metadata_flags & BLOCK_FLAG_RAID1 || Vcb->system_flags & BLOCK_FLAG_RAID1))\n        ) {\n            ExReleaseResourceLite(&Vcb->tree_lock);\n            ERR(\"would not be enough devices to satisfy RAID requirement (RAID0/1)\\n\");\n            return STATUS_CANNOT_DELETE;\n        }\n    }\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    if (Vcb->balance.thread) {\n        WARN(\"balance already running\\n\");\n        return STATUS_DEVICE_NOT_READY;\n    }\n\n    dev->reloc = true;\n\n    RtlZeroMemory(Vcb->balance.opts, sizeof(btrfs_balance_opts) * 3);\n\n    for (i = 0; i < 3; i++) {\n        Vcb->balance.opts[i].flags = BTRFS_BALANCE_OPTS_ENABLED | BTRFS_BALANCE_OPTS_DEVID;\n        Vcb->balance.opts[i].devid = devid;\n    }\n\n    Vcb->balance.paused = false;\n    Vcb->balance.removing = true;\n    Vcb->balance.shrinking = false;\n    Vcb->balance.status = STATUS_SUCCESS;\n    KeInitializeEvent(&Vcb->balance.event, NotificationEvent, !Vcb->balance.paused);\n\n    InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = PsCreateSystemThread(&Vcb->balance.thread, 0, &oa, NULL, NULL, balance_thread, Vcb);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"PsCreateSystemThread returned %08lx\\n\", Status);\n        dev->reloc = false;\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n"
  },
  {
    "path": "src/blake2-impl.h",
    "content": "/*\n   BLAKE2 reference source code package - reference C implementations\n\n   Copyright 2012, Samuel Neves <sneves@dei.uc.pt>.  You may use this under the\n   terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at\n   your option.  The terms of these licenses can be found at:\n\n   - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0\n   - OpenSSL license   : https://www.openssl.org/source/license.html\n   - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0\n\n   More information about the BLAKE2 hash function can be found at\n   https://blake2.net.\n*/\n#pragma once\n\n#include <stdint.h>\n#include <string.h>\n\n#if !defined(__cplusplus) && (!defined(__STDC_VERSION__) || __STDC_VERSION__ < 199901L)\n  #if   defined(_MSC_VER)\n    #define BLAKE2_INLINE __inline\n  #elif defined(__GNUC__)\n    #define BLAKE2_INLINE __inline__\n  #else\n    #define BLAKE2_INLINE\n  #endif\n#else\n  #define BLAKE2_INLINE inline\n#endif\n\n#define NATIVE_LITTLE_ENDIAN\n\nstatic BLAKE2_INLINE uint32_t load32( const void *src )\n{\n#if defined(NATIVE_LITTLE_ENDIAN)\n  uint32_t w;\n  memcpy(&w, src, sizeof w);\n  return w;\n#else\n  const uint8_t *p = ( const uint8_t * )src;\n  return (( uint32_t )( p[0] ) <<  0) |\n         (( uint32_t )( p[1] ) <<  8) |\n         (( uint32_t )( p[2] ) << 16) |\n         (( uint32_t )( p[3] ) << 24) ;\n#endif\n}\n\nstatic BLAKE2_INLINE uint64_t load64( const void *src )\n{\n#if defined(NATIVE_LITTLE_ENDIAN)\n  uint64_t w;\n  memcpy(&w, src, sizeof w);\n  return w;\n#else\n  const uint8_t *p = ( const uint8_t * )src;\n  return (( uint64_t )( p[0] ) <<  0) |\n         (( uint64_t )( p[1] ) <<  8) |\n         (( uint64_t )( p[2] ) << 16) |\n         (( uint64_t )( p[3] ) << 24) |\n         (( uint64_t )( p[4] ) << 32) |\n         (( uint64_t )( p[5] ) << 40) |\n         (( uint64_t )( p[6] ) << 48) |\n         (( uint64_t )( p[7] ) << 56) ;\n#endif\n}\n\nstatic BLAKE2_INLINE uint16_t load16( const void *src )\n{\n#if defined(NATIVE_LITTLE_ENDIAN)\n  uint16_t w;\n  memcpy(&w, src, sizeof w);\n  return w;\n#else\n  const uint8_t *p = ( const uint8_t * )src;\n  return ( uint16_t )((( uint32_t )( p[0] ) <<  0) |\n                      (( uint32_t )( p[1] ) <<  8));\n#endif\n}\n\nstatic BLAKE2_INLINE void store16( void *dst, uint16_t w )\n{\n#if defined(NATIVE_LITTLE_ENDIAN)\n  memcpy(dst, &w, sizeof w);\n#else\n  uint8_t *p = ( uint8_t * )dst;\n  *p++ = ( uint8_t )w; w >>= 8;\n  *p++ = ( uint8_t )w;\n#endif\n}\n\nstatic BLAKE2_INLINE void store32( void *dst, uint32_t w )\n{\n#if defined(NATIVE_LITTLE_ENDIAN)\n  memcpy(dst, &w, sizeof w);\n#else\n  uint8_t *p = ( uint8_t * )dst;\n  p[0] = (uint8_t)(w >>  0);\n  p[1] = (uint8_t)(w >>  8);\n  p[2] = (uint8_t)(w >> 16);\n  p[3] = (uint8_t)(w >> 24);\n#endif\n}\n\nstatic BLAKE2_INLINE void store64( void *dst, uint64_t w )\n{\n#if defined(NATIVE_LITTLE_ENDIAN)\n  memcpy(dst, &w, sizeof w);\n#else\n  uint8_t *p = ( uint8_t * )dst;\n  p[0] = (uint8_t)(w >>  0);\n  p[1] = (uint8_t)(w >>  8);\n  p[2] = (uint8_t)(w >> 16);\n  p[3] = (uint8_t)(w >> 24);\n  p[4] = (uint8_t)(w >> 32);\n  p[5] = (uint8_t)(w >> 40);\n  p[6] = (uint8_t)(w >> 48);\n  p[7] = (uint8_t)(w >> 56);\n#endif\n}\n\nstatic BLAKE2_INLINE uint64_t load48( const void *src )\n{\n  const uint8_t *p = ( const uint8_t * )src;\n  return (( uint64_t )( p[0] ) <<  0) |\n         (( uint64_t )( p[1] ) <<  8) |\n         (( uint64_t )( p[2] ) << 16) |\n         (( uint64_t )( p[3] ) << 24) |\n         (( uint64_t )( p[4] ) << 32) |\n         (( uint64_t )( p[5] ) << 40) ;\n}\n\nstatic BLAKE2_INLINE void store48( void *dst, uint64_t w )\n{\n  uint8_t *p = ( uint8_t * )dst;\n  p[0] = (uint8_t)(w >>  0);\n  p[1] = (uint8_t)(w >>  8);\n  p[2] = (uint8_t)(w >> 16);\n  p[3] = (uint8_t)(w >> 24);\n  p[4] = (uint8_t)(w >> 32);\n  p[5] = (uint8_t)(w >> 40);\n}\n\nstatic BLAKE2_INLINE uint32_t rotr32( const uint32_t w, const unsigned c )\n{\n  return ( w >> c ) | ( w << ( 32 - c ) );\n}\n\nstatic BLAKE2_INLINE uint64_t rotr64( const uint64_t w, const unsigned c )\n{\n  return ( w >> c ) | ( w << ( 64 - c ) );\n}\n\n#if defined(_MSC_VER)\n#define BLAKE2_PACKED(x) __pragma(pack(push, 1)) x __pragma(pack(pop))\n#else\n#define BLAKE2_PACKED(x) x __attribute__((packed))\n#endif\n\nenum blake2b_constant\n{\n    BLAKE2B_BLOCKBYTES = 128,\n    BLAKE2B_OUTBYTES   = 64,\n    BLAKE2B_KEYBYTES   = 64,\n    BLAKE2B_SALTBYTES  = 16,\n    BLAKE2B_PERSONALBYTES = 16\n};\n\ntypedef struct blake2b_state__\n{\n    uint64_t h[8];\n    uint64_t t[2];\n    uint64_t f[2];\n    uint8_t  buf[BLAKE2B_BLOCKBYTES];\n    size_t   buflen;\n    size_t   outlen;\n    uint8_t  last_node;\n} blake2b_state;\n\nBLAKE2_PACKED(struct blake2b_param__\n{\n    uint8_t  digest_length; /* 1 */\n    uint8_t  key_length;    /* 2 */\n    uint8_t  fanout;        /* 3 */\n    uint8_t  depth;         /* 4 */\n    uint32_t leaf_length;   /* 8 */\n    uint32_t node_offset;   /* 12 */\n    uint32_t xof_length;    /* 16 */\n    uint8_t  node_depth;    /* 17 */\n    uint8_t  inner_length;  /* 18 */\n    uint8_t  reserved[14];  /* 32 */\n    uint8_t  salt[BLAKE2B_SALTBYTES]; /* 48 */\n    uint8_t  personal[BLAKE2B_PERSONALBYTES];  /* 64 */\n});\n\ntypedef struct blake2b_param__ blake2b_param;\n"
  },
  {
    "path": "src/blake2b-ref.c",
    "content": "/*\n   BLAKE2 reference source code package - reference C implementations\n\n   Copyright 2012, Samuel Neves <sneves@dei.uc.pt>.  You may use this under the\n   terms of the CC0, the OpenSSL Licence, or the Apache Public License 2.0, at\n   your option.  The terms of these licenses can be found at:\n\n   - CC0 1.0 Universal : http://creativecommons.org/publicdomain/zero/1.0\n   - OpenSSL license   : https://www.openssl.org/source/license.html\n   - Apache 2.0        : http://www.apache.org/licenses/LICENSE-2.0\n\n   More information about the BLAKE2 hash function can be found at\n   https://blake2.net.\n*/\n\n#include <stdint.h>\n#include <string.h>\n#include <stdio.h>\n\n#include \"blake2-impl.h\"\n\nstatic const uint64_t blake2b_IV[8] =\n{\n  0x6a09e667f3bcc908ULL, 0xbb67ae8584caa73bULL,\n  0x3c6ef372fe94f82bULL, 0xa54ff53a5f1d36f1ULL,\n  0x510e527fade682d1ULL, 0x9b05688c2b3e6c1fULL,\n  0x1f83d9abfb41bd6bULL, 0x5be0cd19137e2179ULL\n};\n\nstatic const uint8_t blake2b_sigma[12][16] =\n{\n  {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15 } ,\n  { 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3 } ,\n  { 11,  8, 12,  0,  5,  2, 15, 13, 10, 14,  3,  6,  7,  1,  9,  4 } ,\n  {  7,  9,  3,  1, 13, 12, 11, 14,  2,  6,  5, 10,  4,  0, 15,  8 } ,\n  {  9,  0,  5,  7,  2,  4, 10, 15, 14,  1, 11, 12,  6,  8,  3, 13 } ,\n  {  2, 12,  6, 10,  0, 11,  8,  3,  4, 13,  7,  5, 15, 14,  1,  9 } ,\n  { 12,  5,  1, 15, 14, 13,  4, 10,  0,  7,  6,  3,  9,  2,  8, 11 } ,\n  { 13, 11,  7, 14, 12,  1,  3,  9,  5,  0, 15,  4,  8,  6,  2, 10 } ,\n  {  6, 15, 14,  9, 11,  3,  0,  8, 12,  2, 13,  7,  1,  4, 10,  5 } ,\n  { 10,  2,  8,  4,  7,  6,  1,  5, 15, 11,  9, 14,  3, 12, 13 , 0 } ,\n  {  0,  1,  2,  3,  4,  5,  6,  7,  8,  9, 10, 11, 12, 13, 14, 15 } ,\n  { 14, 10,  4,  8,  9, 15, 13,  6,  1, 12,  0,  2, 11,  7,  5,  3 }\n};\n\nstatic int blake2b_update(blake2b_state* S, const void* in, size_t inlen);\n\nstatic void blake2b_set_lastnode( blake2b_state *S )\n{\n  S->f[1] = (uint64_t)-1;\n}\n\n/* Some helper functions, not necessarily useful */\nstatic int blake2b_is_lastblock( const blake2b_state *S )\n{\n  return S->f[0] != 0;\n}\n\nstatic void blake2b_set_lastblock( blake2b_state *S )\n{\n  if( S->last_node ) blake2b_set_lastnode( S );\n\n  S->f[0] = (uint64_t)-1;\n}\n\nstatic void blake2b_increment_counter( blake2b_state *S, const uint64_t inc )\n{\n  S->t[0] += inc;\n  S->t[1] += ( S->t[0] < inc );\n}\n\nstatic void blake2b_init0( blake2b_state *S )\n{\n  size_t i;\n  memset( S, 0, sizeof( blake2b_state ) );\n\n  for( i = 0; i < 8; ++i ) S->h[i] = blake2b_IV[i];\n}\n\n/* init xors IV with input parameter block */\nstatic void blake2b_init_param( blake2b_state *S, const blake2b_param *P )\n{\n  const uint8_t *p = ( const uint8_t * )( P );\n  size_t i;\n\n  blake2b_init0( S );\n\n  /* IV XOR ParamBlock */\n  for( i = 0; i < 8; ++i )\n    S->h[i] ^= load64( p + sizeof( S->h[i] ) * i );\n\n  S->outlen = P->digest_length;\n}\n\n\n\nstatic void blake2b_init( blake2b_state *S, size_t outlen )\n{\n  blake2b_param P[1];\n\n  P->digest_length = (uint8_t)outlen;\n  P->key_length    = 0;\n  P->fanout        = 1;\n  P->depth         = 1;\n  store32( &P->leaf_length, 0 );\n  store32( &P->node_offset, 0 );\n  store32( &P->xof_length, 0 );\n  P->node_depth    = 0;\n  P->inner_length  = 0;\n  memset( P->reserved, 0, sizeof( P->reserved ) );\n  memset( P->salt,     0, sizeof( P->salt ) );\n  memset( P->personal, 0, sizeof( P->personal ) );\n\n  blake2b_init_param( S, P );\n}\n\n#define G(r,i,a,b,c,d)                      \\\n  do {                                      \\\n    a = a + b + m[blake2b_sigma[r][2*i+0]]; \\\n    d = rotr64(d ^ a, 32);                  \\\n    c = c + d;                              \\\n    b = rotr64(b ^ c, 24);                  \\\n    a = a + b + m[blake2b_sigma[r][2*i+1]]; \\\n    d = rotr64(d ^ a, 16);                  \\\n    c = c + d;                              \\\n    b = rotr64(b ^ c, 63);                  \\\n  } while(0)\n\n#define ROUND(r)                    \\\n  do {                              \\\n    G(r,0,v[ 0],v[ 4],v[ 8],v[12]); \\\n    G(r,1,v[ 1],v[ 5],v[ 9],v[13]); \\\n    G(r,2,v[ 2],v[ 6],v[10],v[14]); \\\n    G(r,3,v[ 3],v[ 7],v[11],v[15]); \\\n    G(r,4,v[ 0],v[ 5],v[10],v[15]); \\\n    G(r,5,v[ 1],v[ 6],v[11],v[12]); \\\n    G(r,6,v[ 2],v[ 7],v[ 8],v[13]); \\\n    G(r,7,v[ 3],v[ 4],v[ 9],v[14]); \\\n  } while(0)\n\nstatic void blake2b_compress( blake2b_state *S, const uint8_t block[BLAKE2B_BLOCKBYTES] )\n{\n  uint64_t m[16];\n  uint64_t v[16];\n  size_t i;\n\n  for( i = 0; i < 16; ++i ) {\n    m[i] = load64( block + i * sizeof( m[i] ) );\n  }\n\n  for( i = 0; i < 8; ++i ) {\n    v[i] = S->h[i];\n  }\n\n  v[ 8] = blake2b_IV[0];\n  v[ 9] = blake2b_IV[1];\n  v[10] = blake2b_IV[2];\n  v[11] = blake2b_IV[3];\n  v[12] = blake2b_IV[4] ^ S->t[0];\n  v[13] = blake2b_IV[5] ^ S->t[1];\n  v[14] = blake2b_IV[6] ^ S->f[0];\n  v[15] = blake2b_IV[7] ^ S->f[1];\n\n  ROUND( 0 );\n  ROUND( 1 );\n  ROUND( 2 );\n  ROUND( 3 );\n  ROUND( 4 );\n  ROUND( 5 );\n  ROUND( 6 );\n  ROUND( 7 );\n  ROUND( 8 );\n  ROUND( 9 );\n  ROUND( 10 );\n  ROUND( 11 );\n\n  for( i = 0; i < 8; ++i ) {\n    S->h[i] = S->h[i] ^ v[i] ^ v[i + 8];\n  }\n}\n\n#undef G\n#undef ROUND\n\nstatic int blake2b_update( blake2b_state *S, const void *pin, size_t inlen )\n{\n  const unsigned char * in = (const unsigned char *)pin;\n  if( inlen > 0 )\n  {\n    size_t left = S->buflen;\n    size_t fill = BLAKE2B_BLOCKBYTES - left;\n    if( inlen > fill )\n    {\n      S->buflen = 0;\n      memcpy( S->buf + left, in, fill ); /* Fill buffer */\n      blake2b_increment_counter( S, BLAKE2B_BLOCKBYTES );\n      blake2b_compress( S, S->buf ); /* Compress */\n      in += fill; inlen -= fill;\n      while(inlen > BLAKE2B_BLOCKBYTES) {\n        blake2b_increment_counter(S, BLAKE2B_BLOCKBYTES);\n        blake2b_compress( S, in );\n        in += BLAKE2B_BLOCKBYTES;\n        inlen -= BLAKE2B_BLOCKBYTES;\n      }\n    }\n    memcpy( S->buf + S->buflen, in, inlen );\n    S->buflen += inlen;\n  }\n  return 0;\n}\n\nstatic int blake2b_final( blake2b_state *S, void *out, size_t outlen )\n{\n  uint8_t buffer[BLAKE2B_OUTBYTES] = {0};\n  size_t i;\n\n  if( out == NULL || outlen < S->outlen )\n    return -1;\n\n  if( blake2b_is_lastblock( S ) )\n    return -1;\n\n  blake2b_increment_counter( S, S->buflen );\n  blake2b_set_lastblock( S );\n  memset( S->buf + S->buflen, 0, BLAKE2B_BLOCKBYTES - S->buflen ); /* Padding */\n  blake2b_compress( S, S->buf );\n\n  for( i = 0; i < 8; ++i ) /* Output full hash to temp buffer */\n    store64( buffer + sizeof( S->h[i] ) * i, S->h[i] );\n\n  memcpy( out, buffer, S->outlen );\n\n  return 0;\n}\n\n/* inlen, at least, should be uint64_t. Others can be size_t. */\nvoid blake2b( void *out, size_t outlen, const void *in, size_t inlen )\n{\n  blake2b_state S[1];\n\n  blake2b_init( S, outlen );\n\n  blake2b_update( S, ( const uint8_t * )in, inlen );\n  blake2b_final( S, out, outlen );\n}\n"
  },
  {
    "path": "src/boot.c",
    "content": "/* Copyright (c) Mark Harmstone 2019\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n\n#ifdef _MSC_VER\n#include <ntstrsafe.h>\n#endif\n\nextern ERESOURCE pdo_list_lock;\nextern LIST_ENTRY pdo_list;\nextern ERESOURCE boot_lock;\nextern PDRIVER_OBJECT drvobj;\n\nBTRFS_UUID boot_uuid; // initialized to 0\nuint64_t boot_subvol = 0;\n\n// Not in any headers? Windbg knows about it though.\n#define DOE_START_PENDING 0x10\n\n// Just as much as we need - the version in mingw is truncated still further\ntypedef struct {\n    CSHORT Type;\n    USHORT Size;\n    PDEVICE_OBJECT DeviceObject;\n    ULONG PowerFlags;\n    void* Dope;\n    ULONG ExtensionFlags;\n} DEVOBJ_EXTENSION2;\n\nstatic bool get_system_root() {\n    NTSTATUS Status;\n    HANDLE h;\n    UNICODE_STRING us, target;\n    OBJECT_ATTRIBUTES objatt;\n    ULONG retlen = 0;\n    bool second_time = false;\n\n    static const WCHAR system_root[] = L\"\\\\SystemRoot\";\n    static const WCHAR boot_device[] = L\"\\\\Device\\\\BootDevice\";\n    static const WCHAR arc_btrfs_prefix[] = L\"\\\\ArcName\\\\btrfs(\";\n\n    us.Buffer = (WCHAR*)system_root;\n    us.Length = us.MaximumLength = sizeof(system_root) - sizeof(WCHAR);\n\n    InitializeObjectAttributes(&objatt, &us, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    while (true) {\n        Status = ZwOpenSymbolicLinkObject(&h, GENERIC_READ, &objatt);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"ZwOpenSymbolicLinkObject returned %08lx\\n\", Status);\n            return false;\n        }\n\n        target.Length = target.MaximumLength = 0;\n\n        Status = ZwQuerySymbolicLinkObject(h, &target, &retlen);\n        if (Status != STATUS_BUFFER_TOO_SMALL) {\n            ERR(\"ZwQuerySymbolicLinkObject returned %08lx\\n\", Status);\n            NtClose(h);\n            return false;\n        }\n\n        if (retlen == 0) {\n            NtClose(h);\n            return false;\n        }\n\n        target.Buffer = ExAllocatePoolWithTag(NonPagedPool, retlen, ALLOC_TAG);\n        if (!target.Buffer) {\n            ERR(\"out of memory\\n\");\n            NtClose(h);\n            return false;\n        }\n\n        target.Length = target.MaximumLength = (USHORT)retlen;\n\n        Status = ZwQuerySymbolicLinkObject(h, &target, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"ZwQuerySymbolicLinkObject returned %08lx\\n\", Status);\n            NtClose(h);\n            ExFreePool(target.Buffer);\n            return false;\n        }\n\n        NtClose(h);\n\n        if (second_time) {\n            TRACE(\"boot device is %.*S\\n\", (int)(target.Length / sizeof(WCHAR)), target.Buffer);\n        } else {\n            TRACE(\"system root is %.*S\\n\", (int)(target.Length / sizeof(WCHAR)), target.Buffer);\n        }\n\n        if (!second_time && target.Length >= sizeof(boot_device) - sizeof(WCHAR) &&\n            RtlCompareMemory(target.Buffer, boot_device, sizeof(boot_device) - sizeof(WCHAR)) == sizeof(boot_device) - sizeof(WCHAR)) {\n            ExFreePool(target.Buffer);\n\n            us.Buffer = (WCHAR*)boot_device;\n            us.Length = us.MaximumLength = sizeof(boot_device) - sizeof(WCHAR);\n\n            second_time = true;\n        } else\n            break;\n    }\n\n    if (target.Length >= sizeof(arc_btrfs_prefix) - sizeof(WCHAR) &&\n        RtlCompareMemory(target.Buffer, arc_btrfs_prefix, sizeof(arc_btrfs_prefix) - sizeof(WCHAR)) == sizeof(arc_btrfs_prefix) - sizeof(WCHAR)) {\n        WCHAR* s = &target.Buffer[(sizeof(arc_btrfs_prefix) / sizeof(WCHAR)) - 1];\n\n        for (unsigned int i = 0; i < 16; i++) {\n            if (*s >= '0' && *s <= '9')\n                boot_uuid.uuid[i] = (*s - '0') << 4;\n            else if (*s >= 'a' && *s <= 'f')\n                boot_uuid.uuid[i] = (*s - 'a' + 0xa) << 4;\n            else if (*s >= 'A' && *s <= 'F')\n                boot_uuid.uuid[i] = (*s - 'A' + 0xa) << 4;\n            else {\n                ExFreePool(target.Buffer);\n                return false;\n            }\n\n            s++;\n\n            if (*s >= '0' && *s <= '9')\n                boot_uuid.uuid[i] |= *s - '0';\n            else if (*s >= 'a' && *s <= 'f')\n                boot_uuid.uuid[i] |= *s - 'a' + 0xa;\n            else if (*s >= 'A' && *s <= 'F')\n                boot_uuid.uuid[i] |= *s - 'A' + 0xa;\n            else {\n                ExFreePool(target.Buffer);\n                return false;\n            }\n\n            s++;\n\n            if (i == 3 || i == 5 || i == 7 || i == 9) {\n                if (*s != '-') {\n                    ExFreePool(target.Buffer);\n                    return false;\n                }\n\n                s++;\n            }\n        }\n\n        if (*s != ')') {\n            ExFreePool(target.Buffer);\n            return false;\n        }\n\n        ExFreePool(target.Buffer);\n\n        return true;\n    }\n\n    ExFreePool(target.Buffer);\n\n    return false;\n}\n\nstatic void mountmgr_notification(BTRFS_UUID* uuid) {\n    UNICODE_STRING mmdevpath;\n    NTSTATUS Status;\n    PFILE_OBJECT FileObject;\n    PDEVICE_OBJECT mountmgr;\n    ULONG mmtnlen;\n    MOUNTMGR_TARGET_NAME* mmtn;\n    WCHAR* w;\n\n    RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);\n    Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n        return;\n    }\n\n    mmtnlen = offsetof(MOUNTMGR_TARGET_NAME, DeviceName[0]) + sizeof(BTRFS_VOLUME_PREFIX) + (36 * sizeof(WCHAR));\n\n    mmtn = ExAllocatePoolWithTag(NonPagedPool, mmtnlen, ALLOC_TAG);\n    if (!mmtn) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    mmtn->DeviceNameLength = sizeof(BTRFS_VOLUME_PREFIX) + (36 * sizeof(WCHAR));\n\n    RtlCopyMemory(mmtn->DeviceName, BTRFS_VOLUME_PREFIX, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR));\n\n    w = &mmtn->DeviceName[(sizeof(BTRFS_VOLUME_PREFIX) / sizeof(WCHAR)) - 1];\n\n    for (unsigned int i = 0; i < 16; i++) {\n        *w = hex_digit(uuid->uuid[i] >> 4); w++;\n        *w = hex_digit(uuid->uuid[i] & 0xf); w++;\n\n        if (i == 3 || i == 5 || i == 7 || i == 9) {\n            *w = L'-';\n            w++;\n        }\n    }\n\n    *w = L'}';\n\n    Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION, mmtn, mmtnlen, NULL, 0, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IOCTL_MOUNTMGR_VOLUME_ARRIVAL_NOTIFICATION returned %08lx\\n\", Status);\n        ExFreePool(mmtn);\n        return;\n    }\n\n    ExFreePool(mmtn);\n}\n\nstatic void check_boot_options() {\n    NTSTATUS Status;\n    WCHAR* s;\n\n    static const WCHAR pathw[] = L\"\\\\Registry\\\\Machine\\\\SYSTEM\\\\CurrentControlSet\\\\Control\";\n    static const WCHAR namew[] = L\"SystemStartOptions\";\n    static const WCHAR subvol[] = L\"SUBVOL=\";\n\n    try {\n        HANDLE control;\n        OBJECT_ATTRIBUTES oa;\n        UNICODE_STRING path;\n        ULONG kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR));\n        KEY_VALUE_FULL_INFORMATION* kvfi;\n        UNICODE_STRING name;\n        WCHAR* options;\n\n        path.Buffer = (WCHAR*)pathw;\n        path.Length = path.MaximumLength = sizeof(pathw) - sizeof(WCHAR);\n\n        InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n\n        Status = ZwOpenKey(&control, KEY_QUERY_VALUE, &oa);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"ZwOpenKey returned %08lx\\n\", Status);\n            return;\n        }\n\n        // FIXME - don't fail if value too long (can we query for the length?)\n\n        kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);\n        if (!kvfi) {\n            ERR(\"out of memory\\n\");\n            NtClose(control);\n            return;\n        }\n\n        name.Buffer = (WCHAR*)namew;\n        name.Length = name.MaximumLength = sizeof(namew) - sizeof(WCHAR);\n\n        Status = ZwQueryValueKey(control, &name, KeyValueFullInformation, kvfi,\n                                 kvfilen, &kvfilen);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"ZwQueryValueKey returned %08lx\\n\", Status);\n            NtClose(control);\n            return;\n        }\n\n        NtClose(control);\n\n        options = (WCHAR*)((uint8_t*)kvfi + kvfi->DataOffset);\n        options[kvfi->DataLength / sizeof(WCHAR)] = 0; // FIXME - make sure buffer long enough to allow this\n\n        s = wcsstr(options, subvol);\n\n        if (!s)\n            return;\n\n        s += (sizeof(subvol) / sizeof(WCHAR)) - 1;\n\n        boot_subvol = 0;\n\n        while (true) {\n            if (*s >= '0' && *s <= '9') {\n                boot_subvol <<= 4;\n                boot_subvol |= *s - '0';\n            } else if (*s >= 'a' && *s <= 'f') {\n                boot_subvol <<= 4;\n                boot_subvol |= *s - 'a' + 0xa;\n            } else if (*s >= 'A' && *s <= 'F') {\n                boot_subvol <<= 4;\n                boot_subvol |= *s - 'A' + 0xa;\n            } else\n                break;\n\n            s++;\n        }\n    } except (EXCEPTION_EXECUTE_HANDLER) {\n        return;\n    }\n\n    if (boot_subvol != 0) {\n        TRACE(\"passed subvol %I64x in boot options\\n\", boot_subvol);\n    }\n}\n\nvoid boot_add_device(DEVICE_OBJECT* pdo) {\n    pdo_device_extension* pdode = pdo->DeviceExtension;\n\n    AddDevice(drvobj, pdo);\n\n    // To stop Windows sneakily setting DOE_START_PENDING\n    pdode->dont_report = true;\n\n    if (pdo->DeviceObjectExtension) {\n        ((DEVOBJ_EXTENSION2*)pdo->DeviceObjectExtension)->ExtensionFlags &= ~DOE_START_PENDING;\n\n        if (pdode && pdode->vde && pdode->vde->device)\n            ((DEVOBJ_EXTENSION2*)pdode->vde->device->DeviceObjectExtension)->ExtensionFlags &= ~DOE_START_PENDING;\n    }\n\n    mountmgr_notification(&pdode->uuid);\n}\n\nvoid check_system_root() {\n    LIST_ENTRY* le;\n    PDEVICE_OBJECT pdo_to_add = NULL;\n\n    TRACE(\"()\\n\");\n\n    // wait for any PNP notifications in progress to finish\n    ExAcquireResourceExclusiveLite(&boot_lock, TRUE);\n    ExReleaseResourceLite(&boot_lock);\n\n    if (!get_system_root())\n        return;\n\n    ExAcquireResourceSharedLite(&pdo_list_lock, true);\n\n    le = pdo_list.Flink;\n    while (le != &pdo_list) {\n        pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);\n\n        if (RtlCompareMemory(&pdode->uuid, &boot_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n            if (!pdode->vde)\n                pdo_to_add = pdode->pdo;\n            else if (pdode->vde->device && !(pdode->vde->device->Flags & DO_SYSTEM_BOOT_PARTITION)) { // AddDevice has beaten us to it\n                NTSTATUS Status;\n\n                pdode->vde->device->Flags |= DO_SYSTEM_BOOT_PARTITION;\n                pdode->pdo->Flags |= DO_SYSTEM_BOOT_PARTITION;\n\n                Status = IoSetDeviceInterfaceState(&pdode->vde->bus_name, false);\n                if (!NT_SUCCESS(Status))\n                    ERR(\"IoSetDeviceInterfaceState returned %08lx\\n\", Status);\n\n                Status = IoSetDeviceInterfaceState(&pdode->vde->bus_name, true);\n                if (!NT_SUCCESS(Status))\n                    ERR(\"IoSetDeviceInterfaceState returned %08lx\\n\", Status);\n            }\n\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&pdo_list_lock);\n\n    check_boot_options();\n\n    // If our FS depends on volumes that aren't there when we do our IoRegisterPlugPlayNotification calls\n    // in DriverEntry, bus_query_device_relations won't get called until it's too late. We need to do our\n    // own call to AddDevice here as a result. We need to clear the DOE_START_PENDING bits, or NtOpenFile\n    // will return STATUS_NO_SUCH_DEVICE.\n    if (pdo_to_add)\n        boot_add_device(pdo_to_add);\n}\n"
  },
  {
    "path": "src/btrfs-vol.inf",
    "content": ";;;\n;;; WinBtrfs\n;;;\n;;;\n;;; Copyright (c) 2016-24 Mark Harmstone\n;;;\n\n[Version]\nSignature   = \"$Windows NT$\"\nClass       = Volume\nClassGuid   = {71a27cdd-812a-11d0-bec7-08002be2092f}\nProvider    = %Me%\nDriverVer   = 03/15/2024,1.9.0.0\nCatalogFile = btrfs.cat\nPnpLockdown = 1\n\n[DestinationDirs]\nBtrfs.DriverFiles       = 12            ;%windir%\\system32\\drivers\n\n[Manufacturer]\n%Me%=Standard,NTamd64,NTx86,NTarm,NTarm64\n\n[Standard.NTamd64]\n%VolumeName% = Btrfs_Install, BtrfsVolume\n%ControllerName% = Btrfs_Install, ROOT\\btrfs\n\n[Standard.NTx86]\n%VolumeName% = Btrfs_Install, BtrfsVolume\n%ControllerName% = Btrfs_Install, ROOT\\btrfs\n\n[Standard.NTarm]\n%VolumeName% = Btrfs_Install, BtrfsVolume\n%ControllerName% = Btrfs_Install, ROOT\\btrfs\n\n[Standard.NTarm64]\n%VolumeName% = Btrfs_Install, BtrfsVolume\n%ControllerName% = Btrfs_Install, ROOT\\btrfs\n\n[Btrfs_Install]\nOptionDesc   = %ServiceDescription%\nCopyFiles    = Btrfs.DriverFiles\n\n[Btrfs_Install.Services]\nAddService  = %ServiceName%,2,Btrfs.Service\n\n;\n; Services Section\n;\n\n[Btrfs.Service]\nDisplayName      = %ServiceName%\nDescription      = %ServiceDescription%\nServiceBinary    = %12%\\%DriverName%.sys    ;%windir%\\system32\\drivers\\\nServiceType      = 1\nStartType        = 1                        ;SERVICE_SYSTEM_START\nErrorControl     = 1\nLoadOrderGroup   = \"File System\"\n\n;\n; Copy Files\n;\n\n[Btrfs.DriverFiles]\n%DriverName%.sys\n\n[SourceDisksFiles]\nbtrfs.sys = 1,,\n\n[SourceDisksNames.x86]\n1 = %DiskId1%,,,\\x86\n\n[SourceDisksNames.amd64]\n1 = %DiskId1%,,,\\amd64\n\n[SourceDisksNames.arm]\n1 = %DiskId1%,,,\\arm\n\n[SourceDisksNames.arm64]\n1 = %DiskId1%,,,\\aarch64\n\n;;\n;; String Section\n;;\n\n[Strings]\nMe                      = \"Mark Harmstone\"\nServiceDescription      = \"Btrfs driver\"\nServiceName             = \"btrfs\"\nDriverName              = \"btrfs\"\nDiskId1                 = \"Btrfs Device Installation Disk\"\nVolumeName              = \"Btrfs volume\"\nControllerName          = \"Btrfs controller\"\nREG_EXPAND_SZ           = 0x00020000\n"
  },
  {
    "path": "src/btrfs.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#ifdef _DEBUG\n#define DEBUG\n#endif\n\n#include \"btrfs_drv.h\"\n#include \"zstd/lib/common/xxhash.h\"\n#include \"crc32c.h\"\n#ifndef _MSC_VER\n#include <cpuid.h>\n#else\n#include <intrin.h>\n#endif\n#include <ntddscsi.h>\n#include \"btrfs.h\"\n#include <ata.h>\n\n#ifndef _MSC_VER\n#include <initguid.h>\n#include <ntddstor.h>\n#undef INITGUID\n#endif\n\n#include <ntdddisk.h>\n#include <ntddvol.h>\n\n#ifdef _MSC_VER\n#include <initguid.h>\n#include <ntddstor.h>\n#undef INITGUID\n#endif\n\n#ifdef _ARM64_\n#define ARM64_ID_AA64ISAR0_EL1 0x4030\n#endif\n\n#include <ntstrsafe.h>\n\n#define INCOMPAT_SUPPORTED (BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF | BTRFS_INCOMPAT_FLAGS_DEFAULT_SUBVOL | BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS | \\\n                            BTRFS_INCOMPAT_FLAGS_COMPRESS_LZO | BTRFS_INCOMPAT_FLAGS_BIG_METADATA | BTRFS_INCOMPAT_FLAGS_RAID56 | \\\n                            BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF | BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA | BTRFS_INCOMPAT_FLAGS_NO_HOLES | \\\n                            BTRFS_INCOMPAT_FLAGS_COMPRESS_ZSTD | BTRFS_INCOMPAT_FLAGS_METADATA_UUID | BTRFS_INCOMPAT_FLAGS_RAID1C34)\n#define COMPAT_RO_SUPPORTED (BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE | BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID | \\\n                             BTRFS_COMPAT_RO_FLAGS_VERITY | BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE)\n\nstatic const WCHAR device_name[] = {'\\\\','B','t','r','f','s',0};\nstatic const WCHAR dosdevice_name[] = {'\\\\','D','o','s','D','e','v','i','c','e','s','\\\\','B','t','r','f','s',0};\n\nDEFINE_GUID(BtrfsBusInterface, 0x4d414874, 0x6865, 0x6761, 0x6d, 0x65, 0x83, 0x69, 0x17, 0x9a, 0x7d, 0x1d);\n\nPDRIVER_OBJECT drvobj;\nPDEVICE_OBJECT master_devobj, busobj;\nLIST_ENTRY uid_map_list, gid_map_list;\nLIST_ENTRY VcbList;\nERESOURCE global_loading_lock;\nuint32_t debug_log_level = 0;\nuint32_t mount_compress = 0;\nuint32_t mount_compress_force = 0;\nuint32_t mount_compress_type = 0;\nuint32_t mount_zlib_level = 3;\nuint32_t mount_zstd_level = 3;\nuint32_t mount_flush_interval = 30;\nuint32_t mount_max_inline = 2048;\nuint32_t mount_skip_balance = 0;\nuint32_t mount_no_barrier = 0;\nuint32_t mount_no_trim = 0;\nuint32_t mount_clear_cache = 0;\nuint32_t mount_allow_degraded = 0;\nuint32_t mount_readonly = 0;\nuint32_t mount_no_root_dir = 0;\nuint32_t mount_nodatacow = 0;\nuint32_t no_pnp = 0;\nbool log_started = false;\nUNICODE_STRING log_device, log_file, registry_path;\ntPsUpdateDiskCounters fPsUpdateDiskCounters;\ntCcCopyReadEx fCcCopyReadEx;\ntCcCopyWriteEx fCcCopyWriteEx;\ntCcSetAdditionalCacheAttributesEx fCcSetAdditionalCacheAttributesEx;\ntFsRtlUpdateDiskCounters fFsRtlUpdateDiskCounters;\ntIoUnregisterPlugPlayNotificationEx fIoUnregisterPlugPlayNotificationEx;\ntFsRtlGetEcpListFromIrp fFsRtlGetEcpListFromIrp;\ntFsRtlGetNextExtraCreateParameter fFsRtlGetNextExtraCreateParameter;\ntFsRtlValidateReparsePointBuffer fFsRtlValidateReparsePointBuffer;\ntFsRtlCheckLockForOplockRequest fFsRtlCheckLockForOplockRequest;\ntFsRtlAreThereCurrentOrInProgressFileLocks fFsRtlAreThereCurrentOrInProgressFileLocks;\nbool diskacc = false;\nvoid *notification_entry = NULL, *notification_entry2 = NULL, *notification_entry3 = NULL;\nERESOURCE pdo_list_lock, mapping_lock;\nLIST_ENTRY pdo_list;\nbool finished_probing = false;\nHANDLE degraded_wait_handle = NULL, mountmgr_thread_handle = NULL;\nbool degraded_wait = true;\nKEVENT mountmgr_thread_event;\nbool shutting_down = false;\nERESOURCE boot_lock;\nbool is_windows_8;\nextern uint64_t boot_subvol;\n\n#ifdef _DEBUG\nPFILE_OBJECT comfo = NULL;\nPDEVICE_OBJECT comdo = NULL;\nHANDLE log_handle = NULL;\nERESOURCE log_lock;\nHANDLE serial_thread_handle = NULL;\n\nstatic void init_serial(bool first_time);\n#endif\n\nstatic NTSTATUS close_file(_In_ PFILE_OBJECT FileObject, _In_ PIRP Irp);\nstatic void __stdcall do_xor_basic(uint8_t* buf1, uint8_t* buf2, uint32_t len);\n\nxor_func do_xor = do_xor_basic;\n\ntypedef struct {\n    KEVENT Event;\n    IO_STATUS_BLOCK iosb;\n} read_context;\n\n// no longer in Windows headers??\nextern BOOLEAN WdmlibRtlIsNtDdiVersionAvailable(ULONG Version);\n\n#ifdef _DEBUG\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall dbg_completion(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp, _In_ PVOID conptr) {\n    read_context* context = conptr;\n\n    UNUSED(DeviceObject);\n\n    context->iosb = Irp->IoStatus;\n    KeSetEvent(&context->Event, 0, false);\n\n    return STATUS_MORE_PROCESSING_REQUIRED;\n}\n\n#define DEBUG_MESSAGE_LEN 1024\n\n#ifdef DEBUG_LONG_MESSAGES\nvoid _debug_message(_In_ const char* func, _In_ const char* file, _In_ unsigned int line, _In_ char* s, ...) {\n#else\nvoid _debug_message(_In_ const char* func, _In_ char* s, ...) {\n#endif\n    LARGE_INTEGER offset;\n    PIO_STACK_LOCATION IrpSp;\n    NTSTATUS Status;\n    PIRP Irp;\n    va_list ap;\n    char *buf2, *buf;\n    read_context context;\n    uint32_t length;\n\n    buf2 = ExAllocatePoolWithTag(NonPagedPool, DEBUG_MESSAGE_LEN, ALLOC_TAG);\n\n    if (!buf2) {\n        DbgPrint(\"Couldn't allocate buffer in debug_message\\n\");\n        return;\n    }\n\n#ifdef DEBUG_LONG_MESSAGES\n    sprintf(buf2, \"%p:%s:%s:%u:\", (void*)PsGetCurrentThread(), func, file, line);\n#else\n    sprintf(buf2, \"%p:%s:\", (void*)PsGetCurrentThread(), func);\n#endif\n    buf = &buf2[strlen(buf2)];\n\n    va_start(ap, s);\n\n    RtlStringCbVPrintfA(buf, DEBUG_MESSAGE_LEN - strlen(buf2), s, ap);\n\n    ExAcquireResourceSharedLite(&log_lock, true);\n\n    if (!log_started || (log_device.Length == 0 && log_file.Length == 0)) {\n        DbgPrint(buf2);\n    } else if (log_device.Length > 0) {\n        if (!comdo) {\n            DbgPrint(buf2);\n            goto exit2;\n        }\n\n        length = (uint32_t)strlen(buf2);\n\n        offset.u.LowPart = 0;\n        offset.u.HighPart = 0;\n\n        RtlZeroMemory(&context, sizeof(read_context));\n\n        KeInitializeEvent(&context.Event, NotificationEvent, false);\n\n        Irp = IoAllocateIrp(comdo->StackSize, false);\n\n        if (!Irp) {\n            DbgPrint(\"IoAllocateIrp failed\\n\");\n            goto exit2;\n        }\n\n        IrpSp = IoGetNextIrpStackLocation(Irp);\n        IrpSp->MajorFunction = IRP_MJ_WRITE;\n        IrpSp->FileObject = comfo;\n\n        if (comdo->Flags & DO_BUFFERED_IO) {\n            Irp->AssociatedIrp.SystemBuffer = buf2;\n\n            Irp->Flags = IRP_BUFFERED_IO;\n        } else if (comdo->Flags & DO_DIRECT_IO) {\n            Irp->MdlAddress = IoAllocateMdl(buf2, length, false, false, NULL);\n            if (!Irp->MdlAddress) {\n                DbgPrint(\"IoAllocateMdl failed\\n\");\n                goto exit;\n            }\n\n            MmBuildMdlForNonPagedPool(Irp->MdlAddress);\n        } else {\n            Irp->UserBuffer = buf2;\n        }\n\n        IrpSp->Parameters.Write.Length = length;\n        IrpSp->Parameters.Write.ByteOffset = offset;\n\n        Irp->UserIosb = &context.iosb;\n\n        Irp->UserEvent = &context.Event;\n\n        IoSetCompletionRoutine(Irp, dbg_completion, &context, true, true, true);\n\n        Status = IoCallDriver(comdo, Irp);\n\n        if (Status == STATUS_PENDING) {\n            KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n            Status = context.iosb.Status;\n        }\n\n        if (comdo->Flags & DO_DIRECT_IO)\n            IoFreeMdl(Irp->MdlAddress);\n\n        if (!NT_SUCCESS(Status)) {\n            DbgPrint(\"failed to write to COM1 - error %08lx\\n\", Status);\n            goto exit;\n        }\n\nexit:\n        IoFreeIrp(Irp);\n    } else if (log_handle != NULL) {\n        IO_STATUS_BLOCK iosb;\n\n        length = (uint32_t)strlen(buf2);\n\n        Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, buf2, length, NULL, NULL);\n\n        if (!NT_SUCCESS(Status)) {\n            DbgPrint(\"failed to write to file - error %08lx\\n\", Status);\n        }\n    }\n\nexit2:\n    ExReleaseResourceLite(&log_lock);\n\n    va_end(ap);\n\n    if (buf2)\n        ExFreePool(buf2);\n}\n#endif\n\nbool is_top_level(_In_ PIRP Irp) {\n    if (!IoGetTopLevelIrp()) {\n        IoSetTopLevelIrp(Irp);\n        return true;\n    }\n\n    return false;\n}\n\nstatic void __stdcall do_xor_basic(uint8_t* buf1, uint8_t* buf2, uint32_t len) {\n    uint32_t j;\n\n#if defined(_ARM_) || defined(_ARM64_)\n    uint64x2_t x1, x2;\n\n    if (((uintptr_t)buf1 & 0xf) == 0 && ((uintptr_t)buf2 & 0xf) == 0) {\n        while (len >= 16) {\n            x1 = vld1q_u64((const uint64_t*)buf1);\n            x2 = vld1q_u64((const uint64_t*)buf2);\n            x1 = veorq_u64(x1, x2);\n            vst1q_u64((uint64_t*)buf1, x1);\n\n            buf1 += 16;\n            buf2 += 16;\n            len -= 16;\n        }\n    }\n#endif\n\n#if defined(_AMD64_) || defined(_ARM64_)\n    while (len > 8) {\n        *(uint64_t*)buf1 ^= *(uint64_t*)buf2;\n        buf1 += 8;\n        buf2 += 8;\n        len -= 8;\n    }\n#endif\n\n    while (len > 4) {\n        *(uint32_t*)buf1 ^= *(uint32_t*)buf2;\n        buf1 += 4;\n        buf2 += 4;\n        len -= 4;\n    }\n\n    for (j = 0; j < len; j++) {\n        *buf1 ^= *buf2;\n        buf1++;\n        buf2++;\n    }\n}\n\n_Function_class_(DRIVER_UNLOAD)\nstatic void __stdcall DriverUnload(_In_ PDRIVER_OBJECT DriverObject) {\n    UNICODE_STRING dosdevice_nameW;\n\n    TRACE(\"(%p)\\n\", DriverObject);\n\n    dosdevice_nameW.Buffer = (WCHAR*)dosdevice_name;\n    dosdevice_nameW.Length = dosdevice_nameW.MaximumLength = sizeof(dosdevice_name) - sizeof(WCHAR);\n\n    IoDeleteSymbolicLink(&dosdevice_nameW);\n    IoDeleteDevice(DriverObject->DeviceObject);\n\n    while (!IsListEmpty(&uid_map_list)) {\n        LIST_ENTRY* le = RemoveHeadList(&uid_map_list);\n        uid_map* um = CONTAINING_RECORD(le, uid_map, listentry);\n\n        ExFreePool(um->sid);\n\n        ExFreePool(um);\n    }\n\n    while (!IsListEmpty(&gid_map_list)) {\n        gid_map* gm = CONTAINING_RECORD(RemoveHeadList(&gid_map_list), gid_map, listentry);\n\n        ExFreePool(gm->sid);\n        ExFreePool(gm);\n    }\n\n    // FIXME - free volumes and their devpaths\n\n#ifdef _DEBUG\n    if (comfo)\n        ObDereferenceObject(comfo);\n\n    if (log_handle)\n        ZwClose(log_handle);\n#endif\n\n    ExDeleteResourceLite(&global_loading_lock);\n    ExDeleteResourceLite(&pdo_list_lock);\n\n    if (log_device.Buffer)\n        ExFreePool(log_device.Buffer);\n\n    if (log_file.Buffer)\n        ExFreePool(log_file.Buffer);\n\n    if (registry_path.Buffer)\n        ExFreePool(registry_path.Buffer);\n\n#ifdef _DEBUG\n    ExDeleteResourceLite(&log_lock);\n#endif\n    ExDeleteResourceLite(&mapping_lock);\n}\n\nstatic bool get_last_inode(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _In_opt_ PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp, prev_tp;\n    NTSTATUS Status;\n\n    // get last entry\n    searchkey.obj_id = 0xffffffffffffffff;\n    searchkey.obj_type = 0xff;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, r, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return false;\n    }\n\n    if ((tp.item->key.obj_type == TYPE_INODE_ITEM || tp.item->key.obj_type == TYPE_ROOT_ITEM) && tp.item->key.obj_id <= BTRFS_LAST_FREE_OBJECTID) {\n        r->lastinode = tp.item->key.obj_id;\n        TRACE(\"last inode for tree %I64x is %I64x\\n\", r->id, r->lastinode);\n        return true;\n    }\n\n    while (find_prev_item(Vcb, &tp, &prev_tp, Irp)) {\n        tp = prev_tp;\n\n        TRACE(\"moving on to %I64x,%x,%I64x\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n\n        if ((tp.item->key.obj_type == TYPE_INODE_ITEM || tp.item->key.obj_type == TYPE_ROOT_ITEM) && tp.item->key.obj_id <= BTRFS_LAST_FREE_OBJECTID) {\n            r->lastinode = tp.item->key.obj_id;\n            TRACE(\"last inode for tree %I64x is %I64x\\n\", r->id, r->lastinode);\n            return true;\n        }\n    }\n\n    r->lastinode = SUBVOL_ROOT_INODE;\n\n    WARN(\"no INODE_ITEMs in tree %I64x\\n\", r->id);\n\n    return true;\n}\n\n_Success_(return)\nstatic bool extract_xattr(_In_reads_bytes_(size) void* item, _In_ USHORT size, _In_z_ char* name, _Out_ uint8_t** data, _Out_ uint16_t* datalen) {\n    DIR_ITEM* xa = (DIR_ITEM*)item;\n    USHORT xasize;\n\n    while (true) {\n        if (size < sizeof(DIR_ITEM) || size < (sizeof(DIR_ITEM) - 1 + xa->m + xa->n)) {\n            WARN(\"DIR_ITEM is truncated\\n\");\n            return false;\n        }\n\n        if (xa->n == strlen(name) && RtlCompareMemory(name, xa->name, xa->n) == xa->n) {\n            TRACE(\"found xattr %s\\n\", name);\n\n            *datalen = xa->m;\n\n            if (xa->m > 0) {\n                *data = ExAllocatePoolWithTag(PagedPool, xa->m, ALLOC_TAG);\n                if (!*data) {\n                    ERR(\"out of memory\\n\");\n                    return false;\n                }\n\n                RtlCopyMemory(*data, &xa->name[xa->n], xa->m);\n            } else\n                *data = NULL;\n\n            return true;\n        }\n\n        xasize = sizeof(DIR_ITEM) - 1 + xa->m + xa->n;\n\n        if (size > xasize) {\n            size -= xasize;\n            xa = (DIR_ITEM*)&xa->name[xa->m + xa->n];\n        } else\n            break;\n    }\n\n    TRACE(\"xattr %s not found\\n\", name);\n\n    return false;\n}\n\n_Success_(return)\nbool get_xattr(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* subvol, _In_ uint64_t inode, _In_z_ char* name, _In_ uint32_t crc32,\n               _Out_ uint8_t** data, _Out_ uint16_t* datalen, _In_opt_ PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n\n    TRACE(\"(%p, %I64x, %I64x, %s, %08x, %p, %p)\\n\", Vcb, subvol->id, inode, name, crc32, data, datalen);\n\n    searchkey.obj_id = inode;\n    searchkey.obj_type = TYPE_XATTR_ITEM;\n    searchkey.offset = crc32;\n\n    Status = find_item(Vcb, subvol, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return false;\n    }\n\n    if (keycmp(tp.item->key, searchkey)) {\n        TRACE(\"could not find item (%I64x,%x,%I64x)\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n        return false;\n    }\n\n    if (tp.item->size < sizeof(DIR_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM));\n        return false;\n    }\n\n    return extract_xattr(tp.item->data, tp.item->size, name, data, datalen);\n}\n\n_Dispatch_type_(IRP_MJ_CLOSE)\n_Function_class_(DRIVER_DISPATCH)\nstatic NTSTATUS __stdcall drv_close(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {\n    NTSTATUS Status;\n    PIO_STACK_LOCATION IrpSp;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"close\\n\");\n\n    top_level = is_top_level(Irp);\n\n    if (DeviceObject == master_devobj) {\n        TRACE(\"Closing file system\\n\");\n        Status = STATUS_SUCCESS;\n        goto end;\n    } else if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = vol_close(DeviceObject, Irp);\n        goto end;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n    // FIXME - call FsRtlNotifyUninitializeSync(&Vcb->NotifySync) if unmounting\n\n    Status = close_file(IrpSp->FileObject, Irp);\n\nend:\n    Irp->IoStatus.Status = Status;\n    Irp->IoStatus.Information = 0;\n\n    IoCompleteRequest( Irp, IO_DISK_INCREMENT );\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    TRACE(\"returning %08lx\\n\", Status);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_FLUSH_BUFFERS)\n_Function_class_(DRIVER_DISPATCH)\nstatic NTSTATUS __stdcall drv_flush_buffers(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {\n    NTSTATUS Status;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation( Irp );\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    fcb* fcb = FileObject->FsContext;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"flush buffers\\n\");\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_SUCCESS;\n        goto end;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    if (!fcb) {\n        ERR(\"fcb was NULL\\n\");\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    if (fcb == Vcb->volume_fcb) {\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL);\n\n    Irp->IoStatus.Information = 0;\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n\n    Status = STATUS_SUCCESS;\n    Irp->IoStatus.Status = Status;\n\n    if (fcb->type != BTRFS_TYPE_DIRECTORY) {\n        CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &Irp->IoStatus);\n\n        if (fcb->Header.PagingIoResource) {\n            ExAcquireResourceExclusiveLite(fcb->Header.PagingIoResource, true);\n            ExReleaseResourceLite(fcb->Header.PagingIoResource);\n        }\n\n        Status = Irp->IoStatus.Status;\n    }\n\nend:\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\n    TRACE(\"returning %08lx\\n\", Status);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\nstatic void calculate_total_space(_In_ device_extension* Vcb, _Out_ uint64_t* totalsize, _Out_ uint64_t* freespace) {\n    uint64_t nfactor, dfactor, sectors_used;\n\n    if (Vcb->data_flags & BLOCK_FLAG_DUPLICATE || Vcb->data_flags & BLOCK_FLAG_RAID1 || Vcb->data_flags & BLOCK_FLAG_RAID10) {\n        nfactor = 1;\n        dfactor = 2;\n    } else if (Vcb->data_flags & BLOCK_FLAG_RAID5) {\n        nfactor = Vcb->superblock.num_devices - 1;\n        dfactor = Vcb->superblock.num_devices;\n    } else if (Vcb->data_flags & BLOCK_FLAG_RAID6) {\n        nfactor = Vcb->superblock.num_devices - 2;\n        dfactor = Vcb->superblock.num_devices;\n    } else if (Vcb->data_flags & BLOCK_FLAG_RAID1C3) {\n        nfactor = 1;\n        dfactor = 3;\n    } else if (Vcb->data_flags & BLOCK_FLAG_RAID1C4) {\n        nfactor = 1;\n        dfactor = 4;\n    } else {\n        nfactor = 1;\n        dfactor = 1;\n    }\n\n    sectors_used = (Vcb->superblock.bytes_used >> Vcb->sector_shift) * nfactor / dfactor;\n\n    *totalsize = (Vcb->superblock.total_bytes >> Vcb->sector_shift) * nfactor / dfactor;\n    *freespace = sectors_used > *totalsize ? 0 : (*totalsize - sectors_used);\n}\n\n// simplified version of FsRtlAreNamesEqual, which can be a bottleneck!\nstatic bool compare_strings(const UNICODE_STRING* us1, const UNICODE_STRING* us2) {\n    if (us1->Length != us2->Length)\n        return false;\n\n    WCHAR* s1 = us1->Buffer;\n    WCHAR* s2 = us2->Buffer;\n\n    for (unsigned int i = 0; i < us1->Length / sizeof(WCHAR); i++) {\n        WCHAR c1 = *s1;\n        WCHAR c2 = *s2;\n\n        if (c1 != c2) {\n            if (c1 >= 'a' && c1 <= 'z')\n                c1 = c1 - 'a' + 'A';\n\n            if (c2 >= 'a' && c2 <= 'z')\n                c2 = c2 - 'a' + 'A';\n\n            if (c1 != c2)\n                return false;\n        }\n\n        s1++;\n        s2++;\n    }\n\n    return true;\n}\n\n#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);\n\n// This function exists because we have to lie about our FS type in certain situations.\n// MPR!MprGetConnection queries the FS type, and compares it to a whitelist. If it doesn't match,\n// it will return ERROR_NO_NET_OR_BAD_PATH, which prevents UAC from working.\n// The command mklink refuses to create hard links on anything other than NTFS, so we have to\n// blacklist cmd.exe too.\n\nstatic bool lie_about_fs_type() {\n    NTSTATUS Status;\n    PROCESS_BASIC_INFORMATION pbi;\n    PPEB peb;\n    LIST_ENTRY* le;\n    ULONG retlen;\n#ifdef _AMD64_\n    ULONG_PTR wow64info;\n#endif\n\n    INIT_UNICODE_STRING(mpr, L\"MPR.DLL\");\n    INIT_UNICODE_STRING(cmd, L\"CMD.EXE\");\n    INIT_UNICODE_STRING(fsutil, L\"FSUTIL.EXE\");\n    INIT_UNICODE_STRING(storsvc, L\"STORSVC.DLL\");\n\n    /* Not doing a Volkswagen, honest! Some IFS tests won't run if not recognized FS. */\n    INIT_UNICODE_STRING(ifstest, L\"IFSTEST.EXE\");\n\n    if (!PsGetCurrentProcess())\n        return false;\n\n#ifdef _AMD64_\n    Status = ZwQueryInformationProcess(NtCurrentProcess(), ProcessWow64Information, &wow64info, sizeof(wow64info), NULL);\n\n    if (NT_SUCCESS(Status) && wow64info != 0)\n        return true;\n#endif\n\n    Status = ZwQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &pbi, sizeof(pbi), &retlen);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ZwQueryInformationProcess returned %08lx\\n\", Status);\n        return false;\n    }\n\n    if (!pbi.PebBaseAddress)\n        return false;\n\n    peb = pbi.PebBaseAddress;\n\n    if (!peb->Ldr)\n        return false;\n\n    le = peb->Ldr->InMemoryOrderModuleList.Flink;\n    while (le != &peb->Ldr->InMemoryOrderModuleList) {\n        LDR_DATA_TABLE_ENTRY* entry = CONTAINING_RECORD(le, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);\n        bool blacklist = false;\n\n        if (entry->FullDllName.Length >= usmpr.Length) {\n            UNICODE_STRING name;\n\n            name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - usmpr.Length) / sizeof(WCHAR)];\n            name.Length = name.MaximumLength = usmpr.Length;\n\n            blacklist = compare_strings(&name, &usmpr);\n        }\n\n        if (!blacklist && entry->FullDllName.Length >= uscmd.Length) {\n            UNICODE_STRING name;\n\n            name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - uscmd.Length) / sizeof(WCHAR)];\n            name.Length = name.MaximumLength = uscmd.Length;\n\n            blacklist = compare_strings(&name, &uscmd);\n        }\n\n        if (!blacklist && entry->FullDllName.Length >= usfsutil.Length) {\n            UNICODE_STRING name;\n\n            name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - usfsutil.Length) / sizeof(WCHAR)];\n            name.Length = name.MaximumLength = usfsutil.Length;\n\n            blacklist = compare_strings(&name, &usfsutil);\n        }\n\n        if (!blacklist && entry->FullDllName.Length >= usstorsvc.Length) {\n            UNICODE_STRING name;\n\n            name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - usstorsvc.Length) / sizeof(WCHAR)];\n            name.Length = name.MaximumLength = usstorsvc.Length;\n\n            blacklist = compare_strings(&name, &usstorsvc);\n        }\n\n        if (!blacklist && entry->FullDllName.Length >= usifstest.Length) {\n            UNICODE_STRING name;\n\n            name.Buffer = &entry->FullDllName.Buffer[(entry->FullDllName.Length - usifstest.Length) / sizeof(WCHAR)];\n            name.Length = name.MaximumLength = usifstest.Length;\n\n            blacklist = compare_strings(&name, &usifstest);\n        }\n\n        if (blacklist) {\n            void** frames;\n            ULONG i, num_frames;\n\n            frames = ExAllocatePoolWithTag(PagedPool, 256 * sizeof(void*), ALLOC_TAG);\n            if (!frames) {\n                ERR(\"out of memory\\n\");\n                return false;\n            }\n\n            num_frames = RtlWalkFrameChain(frames, 256, 1);\n\n            for (i = 0; i < num_frames; i++) {\n                // entry->Reserved3[1] appears to be the image size\n                if (frames[i] >= entry->DllBase && (ULONG_PTR)frames[i] <= (ULONG_PTR)entry->DllBase + (ULONG_PTR)entry->Reserved3[1]) {\n                    ExFreePool(frames);\n                    return true;\n                }\n            }\n\n            ExFreePool(frames);\n        }\n\n        le = le->Flink;\n    }\n\n    return false;\n}\n\n// version of RtlUTF8ToUnicodeN for Vista and below\nNTSTATUS utf8_to_utf16(WCHAR* dest, ULONG dest_max, ULONG* dest_len, char* src, ULONG src_len) {\n    NTSTATUS Status = STATUS_SUCCESS;\n    uint8_t* in = (uint8_t*)src;\n    uint16_t* out = (uint16_t*)dest;\n    ULONG needed = 0, left = dest_max / sizeof(uint16_t);\n\n    for (ULONG i = 0; i < src_len; i++) {\n        uint32_t cp;\n\n        if (!(in[i] & 0x80))\n            cp = in[i];\n        else if ((in[i] & 0xe0) == 0xc0) {\n            if (i == src_len - 1 || (in[i+1] & 0xc0) != 0x80) {\n                cp = 0xfffd;\n                Status = STATUS_SOME_NOT_MAPPED;\n            } else {\n                cp = ((in[i] & 0x1f) << 6) | (in[i+1] & 0x3f);\n                i++;\n            }\n        } else if ((in[i] & 0xf0) == 0xe0) {\n            if (i >= src_len - 2 || (in[i+1] & 0xc0) != 0x80 || (in[i+2] & 0xc0) != 0x80) {\n                cp = 0xfffd;\n                Status = STATUS_SOME_NOT_MAPPED;\n            } else {\n                cp = ((in[i] & 0xf) << 12) | ((in[i+1] & 0x3f) << 6) | (in[i+2] & 0x3f);\n                i += 2;\n            }\n        } else if ((in[i] & 0xf8) == 0xf0) {\n            if (i >= src_len - 3 || (in[i+1] & 0xc0) != 0x80 || (in[i+2] & 0xc0) != 0x80 || (in[i+3] & 0xc0) != 0x80) {\n                cp = 0xfffd;\n                Status = STATUS_SOME_NOT_MAPPED;\n            } else {\n                cp = ((in[i] & 0x7) << 18) | ((in[i+1] & 0x3f) << 12) | ((in[i+2] & 0x3f) << 6) | (in[i+3] & 0x3f);\n                i += 3;\n            }\n        } else {\n            cp = 0xfffd;\n            Status = STATUS_SOME_NOT_MAPPED;\n        }\n\n        if (cp > 0x10ffff) {\n            cp = 0xfffd;\n            Status = STATUS_SOME_NOT_MAPPED;\n        }\n\n        if (dest) {\n            if (cp <= 0xffff) {\n                if (left < 1)\n                    return STATUS_BUFFER_OVERFLOW;\n\n                *out = (uint16_t)cp;\n                out++;\n\n                left--;\n            } else {\n                if (left < 2)\n                    return STATUS_BUFFER_OVERFLOW;\n\n                cp -= 0x10000;\n\n                *out = 0xd800 | ((cp & 0xffc00) >> 10);\n                out++;\n\n                *out = 0xdc00 | (cp & 0x3ff);\n                out++;\n\n                left -= 2;\n            }\n        }\n\n        if (cp <= 0xffff)\n            needed += sizeof(uint16_t);\n        else\n            needed += 2 * sizeof(uint16_t);\n    }\n\n    if (dest_len)\n        *dest_len = needed;\n\n    return Status;\n}\n\n// version of RtlUnicodeToUTF8N for Vista and below\nNTSTATUS utf16_to_utf8(char* dest, ULONG dest_max, ULONG* dest_len, WCHAR* src, ULONG src_len) {\n    NTSTATUS Status = STATUS_SUCCESS;\n    uint16_t* in = (uint16_t*)src;\n    uint8_t* out = (uint8_t*)dest;\n    ULONG in_len = src_len / sizeof(uint16_t);\n    ULONG needed = 0, left = dest_max;\n\n    for (ULONG i = 0; i < in_len; i++) {\n        uint32_t cp = *in;\n        in++;\n\n        if ((cp & 0xfc00) == 0xd800) {\n            if (i == in_len - 1 || (*in & 0xfc00) != 0xdc00) {\n                cp = 0xfffd;\n                Status = STATUS_SOME_NOT_MAPPED;\n            } else {\n                cp = (cp & 0x3ff) << 10;\n                cp |= *in & 0x3ff;\n                cp += 0x10000;\n\n                in++;\n                i++;\n            }\n        } else if ((cp & 0xfc00) == 0xdc00) {\n            cp = 0xfffd;\n            Status = STATUS_SOME_NOT_MAPPED;\n        }\n\n        if (cp > 0x10ffff) {\n            cp = 0xfffd;\n            Status = STATUS_SOME_NOT_MAPPED;\n        }\n\n        if (dest) {\n            if (cp < 0x80) {\n                if (left < 1)\n                    return STATUS_BUFFER_OVERFLOW;\n\n                *out = (uint8_t)cp;\n                out++;\n\n                left--;\n            } else if (cp < 0x800) {\n                if (left < 2)\n                    return STATUS_BUFFER_OVERFLOW;\n\n                *out = 0xc0 | ((cp & 0x7c0) >> 6);\n                out++;\n\n                *out = 0x80 | (cp & 0x3f);\n                out++;\n\n                left -= 2;\n            } else if (cp < 0x10000) {\n                if (left < 3)\n                    return STATUS_BUFFER_OVERFLOW;\n\n                *out = 0xe0 | ((cp & 0xf000) >> 12);\n                out++;\n\n                *out = 0x80 | ((cp & 0xfc0) >> 6);\n                out++;\n\n                *out = 0x80 | (cp & 0x3f);\n                out++;\n\n                left -= 3;\n            } else {\n                if (left < 4)\n                    return STATUS_BUFFER_OVERFLOW;\n\n                *out = 0xf0 | ((cp & 0x1c0000) >> 18);\n                out++;\n\n                *out = 0x80 | ((cp & 0x3f000) >> 12);\n                out++;\n\n                *out = 0x80 | ((cp & 0xfc0) >> 6);\n                out++;\n\n                *out = 0x80 | (cp & 0x3f);\n                out++;\n\n                left -= 4;\n            }\n        }\n\n        if (cp < 0x80)\n            needed++;\n        else if (cp < 0x800)\n            needed += 2;\n        else if (cp < 0x10000)\n            needed += 3;\n        else\n            needed += 4;\n    }\n\n    if (dest_len)\n        *dest_len = needed;\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_QUERY_VOLUME_INFORMATION)\n_Function_class_(DRIVER_DISPATCH)\nstatic NTSTATUS __stdcall drv_query_volume_information(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp;\n    NTSTATUS Status;\n    ULONG BytesCopied = 0;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"query volume information\\n\");\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n    Status = STATUS_NOT_IMPLEMENTED;\n\n    switch (IrpSp->Parameters.QueryVolume.FsInformationClass) {\n        case FileFsAttributeInformation:\n        {\n            FILE_FS_ATTRIBUTE_INFORMATION* data = Irp->AssociatedIrp.SystemBuffer;\n            bool overflow = false;\n            static const WCHAR ntfs[] = L\"NTFS\";\n            static const WCHAR btrfs[] = L\"Btrfs\";\n            const WCHAR* fs_name;\n            ULONG fs_name_len, orig_fs_name_len;\n\n            if (Irp->RequestorMode == UserMode && lie_about_fs_type()) {\n                fs_name = ntfs;\n                orig_fs_name_len = fs_name_len = sizeof(ntfs) - sizeof(WCHAR);\n            } else {\n                fs_name = btrfs;\n                orig_fs_name_len = fs_name_len = sizeof(btrfs) - sizeof(WCHAR);\n            }\n\n            TRACE(\"FileFsAttributeInformation\\n\");\n\n            if (IrpSp->Parameters.QueryVolume.Length < sizeof(FILE_FS_ATTRIBUTE_INFORMATION) - sizeof(WCHAR) + fs_name_len) {\n                if (IrpSp->Parameters.QueryVolume.Length > sizeof(FILE_FS_ATTRIBUTE_INFORMATION) - sizeof(WCHAR))\n                    fs_name_len = IrpSp->Parameters.QueryVolume.Length - sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + sizeof(WCHAR);\n                else\n                    fs_name_len = 0;\n\n                overflow = true;\n            }\n\n            data->FileSystemAttributes = FILE_CASE_PRESERVED_NAMES | FILE_CASE_SENSITIVE_SEARCH |\n                                         FILE_UNICODE_ON_DISK | FILE_NAMED_STREAMS | FILE_SUPPORTS_HARD_LINKS | FILE_PERSISTENT_ACLS |\n                                         FILE_SUPPORTS_REPARSE_POINTS | FILE_SUPPORTS_SPARSE_FILES | FILE_SUPPORTS_OBJECT_IDS |\n                                         FILE_SUPPORTS_OPEN_BY_FILE_ID | FILE_SUPPORTS_EXTENDED_ATTRIBUTES | FILE_SUPPORTS_BLOCK_REFCOUNTING |\n                                         FILE_SUPPORTS_POSIX_UNLINK_RENAME;\n            if (Vcb->readonly)\n                data->FileSystemAttributes |= FILE_READ_ONLY_VOLUME;\n\n            // should also be FILE_FILE_COMPRESSION when supported\n            data->MaximumComponentNameLength = 255; // FIXME - check\n            data->FileSystemNameLength = orig_fs_name_len;\n            RtlCopyMemory(data->FileSystemName, fs_name, fs_name_len);\n\n            BytesCopied = sizeof(FILE_FS_ATTRIBUTE_INFORMATION) - sizeof(WCHAR) + fs_name_len;\n            Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;\n            break;\n        }\n\n        case FileFsDeviceInformation:\n        {\n            FILE_FS_DEVICE_INFORMATION* ffdi = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileFsDeviceInformation\\n\");\n\n            ffdi->DeviceType = FILE_DEVICE_DISK;\n\n            ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n            ffdi->Characteristics = Vcb->Vpb->RealDevice->Characteristics;\n            ExReleaseResourceLite(&Vcb->tree_lock);\n\n            if (Vcb->readonly)\n                ffdi->Characteristics |= FILE_READ_ONLY_DEVICE;\n            else\n                ffdi->Characteristics &= ~FILE_READ_ONLY_DEVICE;\n\n            BytesCopied = sizeof(FILE_FS_DEVICE_INFORMATION);\n            Status = STATUS_SUCCESS;\n\n            break;\n        }\n\n        case FileFsFullSizeInformation:\n        {\n            FILE_FS_FULL_SIZE_INFORMATION* ffsi = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileFsFullSizeInformation\\n\");\n\n            calculate_total_space(Vcb, (uint64_t*)&ffsi->TotalAllocationUnits.QuadPart, (uint64_t*)&ffsi->ActualAvailableAllocationUnits.QuadPart);\n            ffsi->CallerAvailableAllocationUnits.QuadPart = ffsi->ActualAvailableAllocationUnits.QuadPart;\n            ffsi->SectorsPerAllocationUnit = Vcb->superblock.sector_size / 512;\n            ffsi->BytesPerSector = 512;\n\n            BytesCopied = sizeof(FILE_FS_FULL_SIZE_INFORMATION);\n            Status = STATUS_SUCCESS;\n\n            break;\n        }\n\n        case FileFsObjectIdInformation:\n        {\n            FILE_FS_OBJECTID_INFORMATION* ffoi = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileFsObjectIdInformation\\n\");\n\n            RtlCopyMemory(ffoi->ObjectId, &Vcb->superblock.uuid.uuid[0], sizeof(UCHAR) * 16);\n            RtlZeroMemory(ffoi->ExtendedInfo, sizeof(ffoi->ExtendedInfo));\n\n            BytesCopied = sizeof(FILE_FS_OBJECTID_INFORMATION);\n            Status = STATUS_SUCCESS;\n\n            break;\n        }\n\n        case FileFsSizeInformation:\n        {\n            FILE_FS_SIZE_INFORMATION* ffsi = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileFsSizeInformation\\n\");\n\n            calculate_total_space(Vcb, (uint64_t*)&ffsi->TotalAllocationUnits.QuadPart, (uint64_t*)&ffsi->AvailableAllocationUnits.QuadPart);\n            ffsi->SectorsPerAllocationUnit = Vcb->superblock.sector_size / 512;\n            ffsi->BytesPerSector = 512;\n\n            BytesCopied = sizeof(FILE_FS_SIZE_INFORMATION);\n            Status = STATUS_SUCCESS;\n\n            break;\n        }\n\n        case FileFsVolumeInformation:\n        {\n            FILE_FS_VOLUME_INFORMATION* data = Irp->AssociatedIrp.SystemBuffer;\n            FILE_FS_VOLUME_INFORMATION ffvi;\n            bool overflow = false;\n            ULONG label_len, orig_label_len;\n\n            TRACE(\"FileFsVolumeInformation\\n\");\n            TRACE(\"max length = %lu\\n\", IrpSp->Parameters.QueryVolume.Length);\n\n            ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n            Status = utf8_to_utf16(NULL, 0, &label_len, Vcb->superblock.label, (ULONG)strlen(Vcb->superblock.label));\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"utf8_to_utf16 returned %08lx\\n\", Status);\n                ExReleaseResourceLite(&Vcb->tree_lock);\n                break;\n            }\n\n            orig_label_len = label_len;\n\n            if (IrpSp->Parameters.QueryVolume.Length < offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel) + label_len) {\n                if (IrpSp->Parameters.QueryVolume.Length > offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel))\n                    label_len = IrpSp->Parameters.QueryVolume.Length - offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel);\n                else\n                    label_len = 0;\n\n                overflow = true;\n            }\n\n            TRACE(\"label_len = %lu\\n\", label_len);\n\n            RtlZeroMemory(&ffvi, offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel));\n\n            ffvi.VolumeSerialNumber = Vcb->superblock.uuid.uuid[12] << 24 | Vcb->superblock.uuid.uuid[13] << 16 | Vcb->superblock.uuid.uuid[14] << 8 | Vcb->superblock.uuid.uuid[15];\n            ffvi.VolumeLabelLength = orig_label_len;\n\n            RtlCopyMemory(data, &ffvi, min(offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel), IrpSp->Parameters.QueryVolume.Length));\n\n            if (label_len > 0) {\n                ULONG bytecount;\n\n                Status = utf8_to_utf16(&data->VolumeLabel[0], label_len, &bytecount, Vcb->superblock.label, (ULONG)strlen(Vcb->superblock.label));\n                if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {\n                    ERR(\"utf8_to_utf16 returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(&Vcb->tree_lock);\n                    break;\n                }\n\n                TRACE(\"label = %.*S\\n\", (int)(label_len / sizeof(WCHAR)), data->VolumeLabel);\n            }\n\n            ExReleaseResourceLite(&Vcb->tree_lock);\n\n            BytesCopied = offsetof(FILE_FS_VOLUME_INFORMATION, VolumeLabel) + label_len;\n            Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;\n            break;\n        }\n\n#ifdef _MSC_VER // not in mingw yet\n        case FileFsSectorSizeInformation:\n        {\n            FILE_FS_SECTOR_SIZE_INFORMATION* data = Irp->AssociatedIrp.SystemBuffer;\n\n            data->LogicalBytesPerSector = Vcb->superblock.sector_size;\n            data->PhysicalBytesPerSectorForAtomicity = Vcb->superblock.sector_size;\n            data->PhysicalBytesPerSectorForPerformance = Vcb->superblock.sector_size;\n            data->FileSystemEffectivePhysicalBytesPerSectorForAtomicity = Vcb->superblock.sector_size;\n            data->ByteOffsetForSectorAlignment = 0;\n            data->ByteOffsetForPartitionAlignment = 0;\n\n            data->Flags = SSINFO_FLAGS_ALIGNED_DEVICE | SSINFO_FLAGS_PARTITION_ALIGNED_ON_DEVICE;\n\n            if (Vcb->trim && !Vcb->options.no_trim)\n                data->Flags |= SSINFO_FLAGS_TRIM_ENABLED;\n\n            BytesCopied = sizeof(FILE_FS_SECTOR_SIZE_INFORMATION);\n            Status = STATUS_SUCCESS;\n\n            break;\n        }\n#endif\n\n        default:\n            Status = STATUS_INVALID_PARAMETER;\n            WARN(\"unknown FsInformationClass %u\\n\", IrpSp->Parameters.QueryVolume.FsInformationClass);\n            break;\n    }\n\n    if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)\n        Irp->IoStatus.Information = 0;\n    else\n        Irp->IoStatus.Information = BytesCopied;\n\nend:\n    Irp->IoStatus.Status = Status;\n\n    IoCompleteRequest( Irp, IO_DISK_INCREMENT );\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    TRACE(\"query volume information returning %08lx\\n\", Status);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall read_completion(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp, _In_ PVOID conptr) {\n    read_context* context = conptr;\n\n    UNUSED(DeviceObject);\n\n    context->iosb = Irp->IoStatus;\n    KeSetEvent(&context->Event, 0, false);\n\n    return STATUS_MORE_PROCESSING_REQUIRED;\n}\n\nNTSTATUS create_root(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ uint64_t id,\n                     _Out_ root** rootptr, _In_ bool no_tree, _In_ uint64_t offset, _In_opt_ PIRP Irp) {\n    NTSTATUS Status;\n    root* r;\n    ROOT_ITEM* ri;\n    traverse_ptr tp;\n\n    r = ExAllocatePoolWithTag(PagedPool, sizeof(root), ALLOC_TAG);\n    if (!r) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    r->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(root_nonpaged), ALLOC_TAG);\n    if (!r->nonpaged) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(r);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);\n    if (!ri) {\n        ERR(\"out of memory\\n\");\n\n        ExFreePool(r->nonpaged);\n        ExFreePool(r);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    r->id = id;\n    r->treeholder.address = 0;\n    r->treeholder.generation = Vcb->superblock.generation;\n    r->treeholder.tree = NULL;\n    r->lastinode = 0;\n    r->dirty = false;\n    r->received = false;\n    r->reserved = NULL;\n    r->parent = 0;\n    r->send_ops = 0;\n    RtlZeroMemory(&r->root_item, sizeof(ROOT_ITEM));\n    r->root_item.num_references = 1;\n    r->fcbs_version = 0;\n    r->checked_for_orphans = true;\n    r->dropped = false;\n    InitializeListHead(&r->fcbs);\n    RtlZeroMemory(r->fcbs_ptrs, sizeof(LIST_ENTRY*) * 256);\n\n    RtlCopyMemory(ri, &r->root_item, sizeof(ROOT_ITEM));\n\n    // We ask here for a traverse_ptr to the item we're inserting, so we can\n    // copy some of the tree's variables\n\n    Status = insert_tree_item(Vcb, Vcb->root_root, id, TYPE_ROOT_ITEM, offset, ri, sizeof(ROOT_ITEM), &tp, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(ri);\n        ExFreePool(r->nonpaged);\n        ExFreePool(r);\n        return Status;\n    }\n\n    ExInitializeResourceLite(&r->nonpaged->load_tree_lock);\n\n    InsertTailList(&Vcb->roots, &r->list_entry);\n\n    if (!no_tree) {\n        tree* t = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG);\n        if (!t) {\n            ERR(\"out of memory\\n\");\n\n            delete_tree_item(Vcb, &tp);\n\n            ExFreePool(r->nonpaged);\n            ExFreePool(r);\n            ExFreePool(ri);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        t->nonpaged = NULL;\n\n        t->is_unique = true;\n        t->uniqueness_determined = true;\n        t->buf = NULL;\n\n        r->treeholder.tree = t;\n\n        RtlZeroMemory(&t->header, sizeof(tree_header));\n        t->header.fs_uuid = tp.tree->header.fs_uuid;\n        t->header.address = 0;\n        t->header.flags = HEADER_FLAG_MIXED_BACKREF | 1; // 1 == \"written\"? Why does the Linux driver record this?\n        t->header.chunk_tree_uuid = tp.tree->header.chunk_tree_uuid;\n        t->header.generation = Vcb->superblock.generation;\n        t->header.tree_id = id;\n        t->header.num_items = 0;\n        t->header.level = 0;\n\n        t->has_address = false;\n        t->size = 0;\n        t->Vcb = Vcb;\n        t->parent = NULL;\n        t->paritem = NULL;\n        t->root = r;\n\n        InitializeListHead(&t->itemlist);\n\n        t->new_address = 0;\n        t->has_new_address = false;\n        t->updated_extents = false;\n\n        InsertTailList(&Vcb->trees, &t->list_entry);\n        t->list_entry_hash.Flink = NULL;\n\n        t->write = true;\n        Vcb->need_write = true;\n    }\n\n    *rootptr = r;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS set_label(_In_ device_extension* Vcb, _In_ FILE_FS_LABEL_INFORMATION* ffli) {\n    ULONG utf8len;\n    NTSTATUS Status;\n    ULONG vollen, i;\n\n    TRACE(\"label = %.*S\\n\", (int)(ffli->VolumeLabelLength / sizeof(WCHAR)), ffli->VolumeLabel);\n\n    vollen = ffli->VolumeLabelLength;\n\n    for (i = 0; i < ffli->VolumeLabelLength / sizeof(WCHAR); i++) {\n        if (ffli->VolumeLabel[i] == 0) {\n            vollen = i * sizeof(WCHAR);\n            break;\n        } else if (ffli->VolumeLabel[i] == '/' || ffli->VolumeLabel[i] == '\\\\') {\n            Status = STATUS_INVALID_VOLUME_LABEL;\n            goto end;\n        }\n    }\n\n    if (vollen == 0) {\n        utf8len = 0;\n    } else {\n        Status = utf16_to_utf8(NULL, 0, &utf8len, ffli->VolumeLabel, vollen);\n        if (!NT_SUCCESS(Status))\n            goto end;\n\n        if (utf8len > MAX_LABEL_SIZE) {\n            Status = STATUS_INVALID_VOLUME_LABEL;\n            goto end;\n        }\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    if (utf8len > 0) {\n        Status = utf16_to_utf8((PCHAR)&Vcb->superblock.label, MAX_LABEL_SIZE, &utf8len, ffli->VolumeLabel, vollen);\n        if (!NT_SUCCESS(Status))\n            goto release;\n    } else\n        Status = STATUS_SUCCESS;\n\n    if (utf8len < MAX_LABEL_SIZE)\n        RtlZeroMemory(Vcb->superblock.label + utf8len, MAX_LABEL_SIZE - utf8len);\n\n    Vcb->need_write = true;\n\nrelease:\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\nend:\n    TRACE(\"returning %08lx\\n\", Status);\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_SET_VOLUME_INFORMATION)\n_Function_class_(DRIVER_DISPATCH)\nstatic NTSTATUS __stdcall drv_set_volume_information(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    NTSTATUS Status;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"set volume information\\n\");\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    Status = STATUS_NOT_IMPLEMENTED;\n\n    if (Vcb->readonly) {\n        Status = STATUS_MEDIA_WRITE_PROTECTED;\n        goto end;\n    }\n\n    if (Vcb->removing || Vcb->locked) {\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    switch (IrpSp->Parameters.SetVolume.FsInformationClass) {\n        case FileFsControlInformation:\n            FIXME(\"STUB: FileFsControlInformation\\n\");\n            break;\n\n        case FileFsLabelInformation:\n            TRACE(\"FileFsLabelInformation\\n\");\n\n            Status = set_label(Vcb, Irp->AssociatedIrp.SystemBuffer);\n            break;\n\n        case FileFsObjectIdInformation:\n            FIXME(\"STUB: FileFsObjectIdInformation\\n\");\n            break;\n\n        default:\n            WARN(\"Unrecognized FsInformationClass 0x%x\\n\", IrpSp->Parameters.SetVolume.FsInformationClass);\n            break;\n    }\n\nend:\n    Irp->IoStatus.Status = Status;\n    Irp->IoStatus.Information = 0;\n\n    TRACE(\"returning %08lx\\n\", Status);\n\n    IoCompleteRequest( Irp, IO_NO_INCREMENT );\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\nvoid send_notification_fileref(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream) {\n    UNICODE_STRING fn;\n    NTSTATUS Status;\n    ULONG reqlen;\n    USHORT name_offset;\n    fcb* fcb = fileref->fcb;\n\n    fn.Length = fn.MaximumLength = 0;\n    Status = fileref_get_filename(fileref, &fn, NULL, &reqlen);\n    if (Status != STATUS_BUFFER_OVERFLOW) {\n        ERR(\"fileref_get_filename returned %08lx\\n\", Status);\n        return;\n    }\n\n    if (reqlen > 0xffff) {\n        WARN(\"reqlen was too long for FsRtlNotifyFilterReportChange\\n\");\n        return;\n    }\n\n    fn.Buffer = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG);\n    if (!fn.Buffer) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    fn.MaximumLength = (USHORT)reqlen;\n    fn.Length = 0;\n\n    Status = fileref_get_filename(fileref, &fn, &name_offset, &reqlen);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"fileref_get_filename returned %08lx\\n\", Status);\n        ExFreePool(fn.Buffer);\n        return;\n    }\n\n    FsRtlNotifyFilterReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&fn, name_offset,\n                                  (PSTRING)stream, NULL, filter_match, action, NULL, NULL);\n    ExFreePool(fn.Buffer);\n}\n\nstatic void send_notification_fcb(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream) {\n    fcb* fcb = fileref->fcb;\n    LIST_ENTRY* le;\n    NTSTATUS Status;\n\n    // no point looking for hardlinks if st_nlink == 1\n    if (fileref->fcb->inode_item.st_nlink == 1) {\n        ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true);\n        send_notification_fileref(fileref, filter_match, action, stream);\n        ExReleaseResourceLite(&fcb->Vcb->fileref_lock);\n        return;\n    }\n\n    ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true);\n\n    le = fcb->hardlinks.Flink;\n    while (le != &fcb->hardlinks) {\n        hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry);\n        file_ref* parfr;\n\n        Status = open_fileref_by_inode(fcb->Vcb, fcb->subvol, hl->parent, &parfr, NULL);\n\n        if (!NT_SUCCESS(Status))\n            ERR(\"open_fileref_by_inode returned %08lx\\n\", Status);\n        else if (!parfr->deleted) {\n            UNICODE_STRING fn;\n            ULONG pathlen;\n\n            fn.Length = fn.MaximumLength = 0;\n            Status = fileref_get_filename(parfr, &fn, NULL, &pathlen);\n            if (Status != STATUS_BUFFER_OVERFLOW) {\n                ERR(\"fileref_get_filename returned %08lx\\n\", Status);\n                free_fileref(parfr);\n                break;\n            }\n\n            if (parfr != fcb->Vcb->root_fileref)\n                pathlen += sizeof(WCHAR);\n\n            if (pathlen + hl->name.Length > 0xffff) {\n                WARN(\"pathlen + hl->name.Length was too long for FsRtlNotifyFilterReportChange\\n\");\n                free_fileref(parfr);\n                break;\n            }\n\n            fn.MaximumLength = (USHORT)(pathlen + hl->name.Length);\n            fn.Buffer = ExAllocatePoolWithTag(PagedPool, fn.MaximumLength, ALLOC_TAG);\n            if (!fn.Buffer) {\n                ERR(\"out of memory\\n\");\n                free_fileref(parfr);\n                break;\n            }\n\n            Status = fileref_get_filename(parfr, &fn, NULL, NULL);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"fileref_get_filename returned %08lx\\n\", Status);\n                free_fileref(parfr);\n                ExFreePool(fn.Buffer);\n                break;\n            }\n\n            if (parfr != fcb->Vcb->root_fileref) {\n                fn.Buffer[(pathlen / sizeof(WCHAR)) - 1] = '\\\\';\n                fn.Length += sizeof(WCHAR);\n            }\n\n            RtlCopyMemory(&fn.Buffer[pathlen / sizeof(WCHAR)], hl->name.Buffer, hl->name.Length);\n            fn.Length += hl->name.Length;\n\n            FsRtlNotifyFilterReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&fn, (USHORT)pathlen,\n                                          (PSTRING)stream, NULL, filter_match, action, NULL, NULL);\n\n            ExFreePool(fn.Buffer);\n\n            free_fileref(parfr);\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&fcb->Vcb->fileref_lock);\n}\n\ntypedef struct {\n    file_ref* fileref;\n    ULONG filter_match;\n    ULONG action;\n    PUNICODE_STRING stream;\n    PIO_WORKITEM work_item;\n} notification_fcb;\n\n_Function_class_(IO_WORKITEM_ROUTINE)\nstatic void __stdcall notification_work_item(PDEVICE_OBJECT DeviceObject, PVOID con) {\n    notification_fcb* nf = con;\n\n    UNUSED(DeviceObject);\n\n    ExAcquireResourceSharedLite(&nf->fileref->fcb->Vcb->tree_lock, TRUE); // protect us from fileref being reaped\n\n    send_notification_fcb(nf->fileref, nf->filter_match, nf->action, nf->stream);\n\n    free_fileref(nf->fileref);\n\n    ExReleaseResourceLite(&nf->fileref->fcb->Vcb->tree_lock);\n\n    IoFreeWorkItem(nf->work_item);\n\n    ExFreePool(nf);\n}\n\nvoid queue_notification_fcb(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream) {\n    notification_fcb* nf;\n    PIO_WORKITEM work_item;\n\n    nf = ExAllocatePoolWithTag(PagedPool, sizeof(notification_fcb), ALLOC_TAG);\n    if (!nf) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    work_item = IoAllocateWorkItem(master_devobj);\n    if (!work_item) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(nf);\n        return;\n    }\n\n    InterlockedIncrement(&fileref->refcount);\n\n    nf->fileref = fileref;\n    nf->filter_match = filter_match;\n    nf->action = action;\n    nf->stream = stream;\n    nf->work_item = work_item;\n\n    IoQueueWorkItem(work_item, notification_work_item, DelayedWorkQueue, nf);\n}\n\nvoid mark_fcb_dirty(_In_ fcb* fcb) {\n    if (!fcb->dirty) {\n#ifdef DEBUG_FCB_REFCOUNTS\n        LONG rc;\n#endif\n        fcb->dirty = true;\n\n#ifdef DEBUG_FCB_REFCOUNTS\n        rc = InterlockedIncrement(&fcb->refcount);\n        WARN(\"fcb %p: refcount now %i\\n\", fcb, rc);\n#else\n        InterlockedIncrement(&fcb->refcount);\n#endif\n\n        ExAcquireResourceExclusiveLite(&fcb->Vcb->dirty_fcbs_lock, true);\n        InsertTailList(&fcb->Vcb->dirty_fcbs, &fcb->list_entry_dirty);\n        ExReleaseResourceLite(&fcb->Vcb->dirty_fcbs_lock);\n    }\n\n    fcb->Vcb->need_write = true;\n}\n\nvoid mark_fileref_dirty(_In_ file_ref* fileref) {\n    if (!fileref->dirty) {\n        fileref->dirty = true;\n        increase_fileref_refcount(fileref);\n\n        ExAcquireResourceExclusiveLite(&fileref->fcb->Vcb->dirty_filerefs_lock, true);\n        InsertTailList(&fileref->fcb->Vcb->dirty_filerefs, &fileref->list_entry_dirty);\n        ExReleaseResourceLite(&fileref->fcb->Vcb->dirty_filerefs_lock);\n    }\n\n    fileref->fcb->Vcb->need_write = true;\n}\n\n#ifdef DEBUG_FCB_REFCOUNTS\nvoid _free_fcb(_Inout_ fcb* fcb, _In_ const char* func) {\n    LONG rc = InterlockedDecrement(&fcb->refcount);\n#else\nvoid free_fcb(_Inout_ fcb* fcb) {\n    InterlockedDecrement(&fcb->refcount);\n#endif\n\n#ifdef DEBUG_FCB_REFCOUNTS\n    ERR(\"fcb %p (%s): refcount now %i (subvol %I64x, inode %I64x)\\n\", fcb, func, rc, fcb->subvol ? fcb->subvol->id : 0, fcb->inode);\n#endif\n}\n\nvoid reap_fcb(fcb* fcb) {\n    uint8_t c = fcb->hash >> 24;\n\n    if (fcb->subvol && fcb->subvol->fcbs_ptrs[c] == &fcb->list_entry) {\n        if (fcb->list_entry.Flink != &fcb->subvol->fcbs && (CONTAINING_RECORD(fcb->list_entry.Flink, struct _fcb, list_entry)->hash >> 24) == c)\n            fcb->subvol->fcbs_ptrs[c] = fcb->list_entry.Flink;\n        else\n            fcb->subvol->fcbs_ptrs[c] = NULL;\n    }\n\n    if (fcb->list_entry.Flink) {\n        RemoveEntryList(&fcb->list_entry);\n\n        if (fcb->subvol && fcb->subvol->dropped && IsListEmpty(&fcb->subvol->fcbs)) {\n            ExDeleteResourceLite(&fcb->subvol->nonpaged->load_tree_lock);\n            ExFreePool(fcb->subvol->nonpaged);\n            ExFreePool(fcb->subvol);\n        }\n    }\n\n    if (fcb->list_entry_all.Flink)\n        RemoveEntryList(&fcb->list_entry_all);\n\n    ExDeleteResourceLite(&fcb->nonpaged->resource);\n    ExDeleteResourceLite(&fcb->nonpaged->paging_resource);\n    ExDeleteResourceLite(&fcb->nonpaged->dir_children_lock);\n\n    ExFreeToNPagedLookasideList(&fcb->Vcb->fcb_np_lookaside, fcb->nonpaged);\n\n    if (fcb->sd)\n        ExFreePool(fcb->sd);\n\n    if (fcb->adsxattr.Buffer)\n        ExFreePool(fcb->adsxattr.Buffer);\n\n    if (fcb->reparse_xattr.Buffer)\n        ExFreePool(fcb->reparse_xattr.Buffer);\n\n    if (fcb->ea_xattr.Buffer)\n        ExFreePool(fcb->ea_xattr.Buffer);\n\n    if (fcb->adsdata.Buffer)\n        ExFreePool(fcb->adsdata.Buffer);\n\n    while (!IsListEmpty(&fcb->extents)) {\n        LIST_ENTRY* le = RemoveHeadList(&fcb->extents);\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if (ext->csum)\n            ExFreePool(ext->csum);\n\n        ExFreePool(ext);\n    }\n\n    while (!IsListEmpty(&fcb->hardlinks)) {\n        LIST_ENTRY* le = RemoveHeadList(&fcb->hardlinks);\n        hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry);\n\n        if (hl->name.Buffer)\n            ExFreePool(hl->name.Buffer);\n\n        if (hl->utf8.Buffer)\n            ExFreePool(hl->utf8.Buffer);\n\n        ExFreePool(hl);\n    }\n\n    while (!IsListEmpty(&fcb->xattrs)) {\n        xattr* xa = CONTAINING_RECORD(RemoveHeadList(&fcb->xattrs), xattr, list_entry);\n\n        ExFreePool(xa);\n    }\n\n    while (!IsListEmpty(&fcb->dir_children_index)) {\n        LIST_ENTRY* le = RemoveHeadList(&fcb->dir_children_index);\n        dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index);\n\n        ExFreePool(dc->utf8.Buffer);\n        ExFreePool(dc->name.Buffer);\n        ExFreePool(dc->name_uc.Buffer);\n        ExFreePool(dc);\n    }\n\n    if (fcb->hash_ptrs)\n        ExFreePool(fcb->hash_ptrs);\n\n    if (fcb->hash_ptrs_uc)\n        ExFreePool(fcb->hash_ptrs_uc);\n\n    FsRtlUninitializeFileLock(&fcb->lock);\n    FsRtlUninitializeOplock(fcb_oplock(fcb));\n\n    if (fcb->pool_type == NonPagedPool)\n        ExFreePool(fcb);\n    else\n        ExFreeToPagedLookasideList(&fcb->Vcb->fcb_lookaside, fcb);\n}\n\nvoid reap_fcbs(device_extension* Vcb) {\n    LIST_ENTRY* le;\n\n    le = Vcb->all_fcbs.Flink;\n    while (le != &Vcb->all_fcbs) {\n        fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_all);\n        LIST_ENTRY* le2 = le->Flink;\n\n        if (fcb->refcount == 0)\n            reap_fcb(fcb);\n\n        le = le2;\n    }\n}\n\nvoid free_fileref(_Inout_ file_ref* fr) {\n#if defined(_DEBUG) || defined(DEBUG_FCB_REFCOUNTS)\n    LONG rc = InterlockedDecrement(&fr->refcount);\n\n#ifdef DEBUG_FCB_REFCOUNTS\n    ERR(\"fileref %p: refcount now %i\\n\", fr, rc);\n#endif\n\n#ifdef _DEBUG\n    if (rc < 0) {\n        ERR(\"fileref %p: refcount now %li\\n\", fr, rc);\n        int3;\n    }\n#endif\n#else\n    InterlockedDecrement(&fr->refcount);\n#endif\n}\n\nvoid reap_fileref(device_extension* Vcb, file_ref* fr) {\n    // FIXME - do we need a file_ref lock?\n\n    // FIXME - do delete if needed\n\n    // FIXME - throw error if children not empty\n\n    if (fr->fcb->fileref == fr)\n        fr->fcb->fileref = NULL;\n\n    if (fr->dc) {\n        if (fr->fcb->ads)\n            fr->dc->size = fr->fcb->adsdata.Length;\n\n        fr->dc->fileref = NULL;\n    }\n\n    if (fr->list_entry.Flink)\n        RemoveEntryList(&fr->list_entry);\n\n    if (fr->parent)\n        free_fileref(fr->parent);\n\n    free_fcb(fr->fcb);\n\n    if (fr->oldutf8.Buffer)\n        ExFreePool(fr->oldutf8.Buffer);\n\n    ExFreeToPagedLookasideList(&Vcb->fileref_lookaside, fr);\n}\n\nvoid reap_filerefs(device_extension* Vcb, file_ref* fr) {\n    LIST_ENTRY* le;\n\n    // FIXME - recursion is a bad idea in kernel mode\n\n    le = fr->children.Flink;\n    while (le != &fr->children) {\n        file_ref* c = CONTAINING_RECORD(le, file_ref, list_entry);\n        LIST_ENTRY* le2 = le->Flink;\n\n        reap_filerefs(Vcb, c);\n\n        le = le2;\n    }\n\n    if (fr->refcount == 0)\n        reap_fileref(Vcb, fr);\n}\n\nstatic NTSTATUS close_file(_In_ PFILE_OBJECT FileObject, _In_ PIRP Irp) {\n    fcb* fcb;\n    ccb* ccb;\n    file_ref* fileref = NULL;\n    LONG open_files;\n\n    UNUSED(Irp);\n\n    TRACE(\"FileObject = %p\\n\", FileObject);\n\n    fcb = FileObject->FsContext;\n    if (!fcb) {\n        TRACE(\"FCB was NULL, returning success\\n\");\n        return STATUS_SUCCESS;\n    }\n\n    open_files = InterlockedDecrement(&fcb->Vcb->open_files);\n\n    ccb = FileObject->FsContext2;\n\n    TRACE(\"close called for fcb %p)\\n\", fcb);\n\n    // FIXME - make sure notification gets sent if file is being deleted\n\n    if (ccb) {\n        if (ccb->query_string.Buffer)\n            RtlFreeUnicodeString(&ccb->query_string);\n\n        if (ccb->filename.Buffer)\n            ExFreePool(ccb->filename.Buffer);\n\n        // FIXME - use refcounts for fileref\n        fileref = ccb->fileref;\n\n        if (fcb->Vcb->running_sends > 0) {\n            bool send_cancelled = false;\n\n            ExAcquireResourceExclusiveLite(&fcb->Vcb->send_load_lock, true);\n\n            if (ccb->send) {\n                ccb->send->cancelling = true;\n                send_cancelled = true;\n                KeSetEvent(&ccb->send->cleared_event, 0, false);\n            }\n\n            ExReleaseResourceLite(&fcb->Vcb->send_load_lock);\n\n            if (send_cancelled) {\n                while (ccb->send) {\n                    ExAcquireResourceExclusiveLite(&fcb->Vcb->send_load_lock, true);\n                    ExReleaseResourceLite(&fcb->Vcb->send_load_lock);\n                }\n            }\n        }\n\n        ExFreePool(ccb);\n    }\n\n    CcUninitializeCacheMap(FileObject, NULL, NULL);\n\n    if (open_files == 0 && fcb->Vcb->removing) {\n        uninit(fcb->Vcb);\n        return STATUS_SUCCESS;\n    }\n\n    if (!(fcb->Vcb->Vpb->Flags & VPB_MOUNTED))\n        return STATUS_SUCCESS;\n\n    if (fileref)\n        free_fileref(fileref);\n    else\n        free_fcb(fcb);\n\n    return STATUS_SUCCESS;\n}\n\nvoid uninit(_In_ device_extension* Vcb) {\n    uint64_t i;\n    KIRQL irql;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    LARGE_INTEGER time;\n\n    if (!Vcb->removing) {\n        ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n        Vcb->removing = true;\n        ExReleaseResourceLite(&Vcb->tree_lock);\n    }\n\n    if (Vcb->vde && Vcb->vde->mounted_device == Vcb->devobj)\n        Vcb->vde->mounted_device = NULL;\n\n    IoAcquireVpbSpinLock(&irql);\n    Vcb->Vpb->Flags &= ~VPB_MOUNTED;\n    Vcb->Vpb->Flags |= VPB_DIRECT_WRITES_ALLOWED;\n    Vcb->Vpb->DeviceObject = NULL;\n    IoReleaseVpbSpinLock(irql);\n\n    // FIXME - needs global_loading_lock to be held\n    if (Vcb->list_entry.Flink)\n        RemoveEntryList(&Vcb->list_entry);\n\n    if (Vcb->balance.thread) {\n        Vcb->balance.paused = false;\n        Vcb->balance.stopping = true;\n        KeSetEvent(&Vcb->balance.event, 0, false);\n        KeWaitForSingleObject(&Vcb->balance.finished, Executive, KernelMode, false, NULL);\n    }\n\n    if (Vcb->scrub.thread) {\n        Vcb->scrub.paused = false;\n        Vcb->scrub.stopping = true;\n        KeSetEvent(&Vcb->scrub.event, 0, false);\n        KeWaitForSingleObject(&Vcb->scrub.finished, Executive, KernelMode, false, NULL);\n    }\n\n    if (Vcb->running_sends != 0) {\n        bool send_cancelled = false;\n\n        ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, true);\n\n        le = Vcb->send_ops.Flink;\n        while (le != &Vcb->send_ops) {\n            send_info* send = CONTAINING_RECORD(le, send_info, list_entry);\n\n            if (!send->cancelling) {\n                send->cancelling = true;\n                send_cancelled = true;\n                send->ccb = NULL;\n                KeSetEvent(&send->cleared_event, 0, false);\n            }\n\n            le = le->Flink;\n        }\n\n        ExReleaseResourceLite(&Vcb->send_load_lock);\n\n        if (send_cancelled) {\n            while (Vcb->running_sends != 0) {\n                ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, true);\n                ExReleaseResourceLite(&Vcb->send_load_lock);\n            }\n        }\n    }\n\n    Status = registry_mark_volume_unmounted(&Vcb->superblock.uuid);\n    if (!NT_SUCCESS(Status) && Status != STATUS_TOO_LATE)\n        WARN(\"registry_mark_volume_unmounted returned %08lx\\n\", Status);\n\n    for (i = 0; i < Vcb->calcthreads.num_threads; i++) {\n        Vcb->calcthreads.threads[i].quit = true;\n    }\n\n    KeSetEvent(&Vcb->calcthreads.event, 0, false);\n\n    for (i = 0; i < Vcb->calcthreads.num_threads; i++) {\n        KeWaitForSingleObject(&Vcb->calcthreads.threads[i].finished, Executive, KernelMode, false, NULL);\n\n        ZwClose(Vcb->calcthreads.threads[i].handle);\n    }\n\n    ExFreePool(Vcb->calcthreads.threads);\n\n    time.QuadPart = 0;\n    KeSetTimer(&Vcb->flush_thread_timer, time, NULL); // trigger the timer early\n    KeWaitForSingleObject(&Vcb->flush_thread_finished, Executive, KernelMode, false, NULL);\n\n    reap_fcb(Vcb->volume_fcb);\n    reap_fcb(Vcb->dummy_fcb);\n\n    if (Vcb->root_file)\n        ObDereferenceObject(Vcb->root_file);\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (c->cache) {\n            reap_fcb(c->cache);\n            c->cache = NULL;\n        }\n\n        le = le->Flink;\n    }\n\n    while (!IsListEmpty(&Vcb->all_fcbs)) {\n        fcb* fcb = CONTAINING_RECORD(Vcb->all_fcbs.Flink, struct _fcb, list_entry_all);\n\n        reap_fcb(fcb);\n    }\n\n    while (!IsListEmpty(&Vcb->sys_chunks)) {\n        sys_chunk* sc = CONTAINING_RECORD(RemoveHeadList(&Vcb->sys_chunks), sys_chunk, list_entry);\n\n        if (sc->data)\n            ExFreePool(sc->data);\n\n        ExFreePool(sc);\n    }\n\n    while (!IsListEmpty(&Vcb->roots)) {\n        root* r = CONTAINING_RECORD(RemoveHeadList(&Vcb->roots), root, list_entry);\n\n        ExDeleteResourceLite(&r->nonpaged->load_tree_lock);\n        ExFreePool(r->nonpaged);\n        ExFreePool(r);\n    }\n\n    while (!IsListEmpty(&Vcb->chunks)) {\n        chunk* c = CONTAINING_RECORD(RemoveHeadList(&Vcb->chunks), chunk, list_entry);\n\n        while (!IsListEmpty(&c->space)) {\n            LIST_ENTRY* le2 = RemoveHeadList(&c->space);\n            space* s = CONTAINING_RECORD(le2, space, list_entry);\n\n            ExFreePool(s);\n        }\n\n        while (!IsListEmpty(&c->deleting)) {\n            LIST_ENTRY* le2 = RemoveHeadList(&c->deleting);\n            space* s = CONTAINING_RECORD(le2, space, list_entry);\n\n            ExFreePool(s);\n        }\n\n        if (c->devices)\n            ExFreePool(c->devices);\n\n        if (c->cache)\n            reap_fcb(c->cache);\n\n        ExDeleteResourceLite(&c->range_locks_lock);\n        ExDeleteResourceLite(&c->partial_stripes_lock);\n        ExDeleteResourceLite(&c->lock);\n        ExDeleteResourceLite(&c->changed_extents_lock);\n\n        ExFreePool(c->chunk_item);\n        ExFreePool(c);\n    }\n\n    while (!IsListEmpty(&Vcb->devices)) {\n        device* dev = CONTAINING_RECORD(RemoveHeadList(&Vcb->devices), device, list_entry);\n\n        while (!IsListEmpty(&dev->space)) {\n            LIST_ENTRY* le2 = RemoveHeadList(&dev->space);\n            space* s = CONTAINING_RECORD(le2, space, list_entry);\n\n            ExFreePool(s);\n        }\n\n        ExFreePool(dev);\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, true);\n    while (!IsListEmpty(&Vcb->scrub.errors)) {\n        scrub_error* err = CONTAINING_RECORD(RemoveHeadList(&Vcb->scrub.errors), scrub_error, list_entry);\n\n        ExFreePool(err);\n    }\n    ExReleaseResourceLite(&Vcb->scrub.stats_lock);\n\n    ExDeleteResourceLite(&Vcb->fcb_lock);\n    ExDeleteResourceLite(&Vcb->fileref_lock);\n    ExDeleteResourceLite(&Vcb->load_lock);\n    ExDeleteResourceLite(&Vcb->tree_lock);\n    ExDeleteResourceLite(&Vcb->chunk_lock);\n    ExDeleteResourceLite(&Vcb->dirty_fcbs_lock);\n    ExDeleteResourceLite(&Vcb->dirty_filerefs_lock);\n    ExDeleteResourceLite(&Vcb->dirty_subvols_lock);\n    ExDeleteResourceLite(&Vcb->scrub.stats_lock);\n    ExDeleteResourceLite(&Vcb->send_load_lock);\n\n    ExDeletePagedLookasideList(&Vcb->tree_data_lookaside);\n    ExDeletePagedLookasideList(&Vcb->traverse_ptr_lookaside);\n    ExDeletePagedLookasideList(&Vcb->batch_item_lookaside);\n    ExDeletePagedLookasideList(&Vcb->fileref_lookaside);\n    ExDeletePagedLookasideList(&Vcb->fcb_lookaside);\n    ExDeletePagedLookasideList(&Vcb->name_bit_lookaside);\n    ExDeleteNPagedLookasideList(&Vcb->range_lock_lookaside);\n    ExDeleteNPagedLookasideList(&Vcb->fcb_np_lookaside);\n\n    ZwClose(Vcb->flush_thread_handle);\n\n    if (Vcb->devobj->AttachedDevice)\n        IoDetachDevice(Vcb->devobj);\n\n    IoDeleteDevice(Vcb->devobj);\n}\n\nstatic NTSTATUS delete_fileref_fcb(_In_ file_ref* fileref, _In_opt_ PFILE_OBJECT FileObject, _In_opt_ PIRP Irp, _In_ LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n\n    // excise extents\n\n    if (fileref->fcb->type != BTRFS_TYPE_DIRECTORY && fileref->fcb->inode_item.st_size > 0) {\n        Status = excise_extents(fileref->fcb->Vcb, fileref->fcb, 0, sector_align(fileref->fcb->inode_item.st_size, fileref->fcb->Vcb->superblock.sector_size), Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"excise_extents returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    fileref->fcb->Header.AllocationSize.QuadPart = 0;\n    fileref->fcb->Header.FileSize.QuadPart = 0;\n    fileref->fcb->Header.ValidDataLength.QuadPart = 0;\n\n    if (FileObject) {\n        CC_FILE_SIZES ccfs;\n\n        ccfs.AllocationSize = fileref->fcb->Header.AllocationSize;\n        ccfs.FileSize = fileref->fcb->Header.FileSize;\n        ccfs.ValidDataLength = fileref->fcb->Header.ValidDataLength;\n\n        Status = STATUS_SUCCESS;\n\n        try {\n            CcSetFileSizes(FileObject, &ccfs);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"CcSetFileSizes threw exception %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    fileref->fcb->deleted = true;\n\n    le = fileref->children.Flink;\n    while (le != &fileref->children) {\n        file_ref* fr2 = CONTAINING_RECORD(le, file_ref, list_entry);\n\n        if (fr2->fcb->ads) {\n            fr2->fcb->deleted = true;\n            mark_fcb_dirty(fr2->fcb);\n        }\n\n        le = le->Flink;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS delete_fileref(_In_ file_ref* fileref, _In_opt_ PFILE_OBJECT FileObject, _In_ bool make_orphan, _In_opt_ PIRP Irp, _In_ LIST_ENTRY* rollback) {\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n    NTSTATUS Status;\n    ULONG utf8len = 0;\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    ExAcquireResourceExclusiveLite(fileref->fcb->Header.Resource, true);\n\n    if (fileref->deleted) {\n        ExReleaseResourceLite(fileref->fcb->Header.Resource);\n        return STATUS_SUCCESS;\n    }\n\n    if (fileref->fcb->subvol->send_ops > 0) {\n        ExReleaseResourceLite(fileref->fcb->Header.Resource);\n        return STATUS_ACCESS_DENIED;\n    }\n\n    fileref->deleted = true;\n    mark_fileref_dirty(fileref);\n\n    // delete INODE_ITEM (0x1)\n\n    TRACE(\"nlink = %u\\n\", fileref->fcb->inode_item.st_nlink);\n\n    if (!fileref->fcb->ads) {\n        if (fileref->parent->fcb->subvol == fileref->fcb->subvol) {\n            LIST_ENTRY* le;\n\n            mark_fcb_dirty(fileref->fcb);\n\n            fileref->fcb->inode_item_changed = true;\n\n            if (fileref->fcb->inode_item.st_nlink > 1 || make_orphan) {\n                fileref->fcb->inode_item.st_nlink--;\n                fileref->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation;\n                fileref->fcb->inode_item.sequence++;\n                fileref->fcb->inode_item.st_ctime = now;\n            } else {\n                Status = delete_fileref_fcb(fileref, FileObject, Irp, rollback);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_fileref_fcb returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(fileref->fcb->Header.Resource);\n                    return Status;\n                }\n            }\n\n            if (fileref->dc) {\n                le = fileref->fcb->hardlinks.Flink;\n                while (le != &fileref->fcb->hardlinks) {\n                    hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry);\n\n                    if (hl->parent == fileref->parent->fcb->inode && hl->index == fileref->dc->index) {\n                        RemoveEntryList(&hl->list_entry);\n\n                        if (hl->name.Buffer)\n                            ExFreePool(hl->name.Buffer);\n\n                        if (hl->utf8.Buffer)\n                            ExFreePool(hl->utf8.Buffer);\n\n                        ExFreePool(hl);\n                        break;\n                    }\n\n                    le = le->Flink;\n                }\n            }\n        } else if (fileref->fcb->subvol->parent == fileref->parent->fcb->subvol->id) { // valid subvolume\n            if (fileref->fcb->subvol->root_item.num_references > 1) {\n                fileref->fcb->subvol->root_item.num_references--;\n\n                mark_fcb_dirty(fileref->fcb); // so ROOT_ITEM gets updated\n            } else {\n                LIST_ENTRY* le;\n\n                // FIXME - we need a lock here\n\n                RemoveEntryList(&fileref->fcb->subvol->list_entry);\n\n                InsertTailList(&fileref->fcb->Vcb->drop_roots, &fileref->fcb->subvol->list_entry);\n\n                le = fileref->children.Flink;\n                while (le != &fileref->children) {\n                    file_ref* fr2 = CONTAINING_RECORD(le, file_ref, list_entry);\n\n                    if (fr2->fcb->ads) {\n                        fr2->fcb->deleted = true;\n                        mark_fcb_dirty(fr2->fcb);\n                    }\n\n                    le = le->Flink;\n                }\n            }\n        }\n    } else {\n        fileref->fcb->deleted = true;\n        mark_fcb_dirty(fileref->fcb);\n    }\n\n    // remove dir_child from parent\n\n    if (fileref->dc) {\n        TRACE(\"delete file %.*S\\n\", (int)(fileref->dc->name.Length / sizeof(WCHAR)), fileref->dc->name.Buffer);\n\n        ExAcquireResourceExclusiveLite(&fileref->parent->fcb->nonpaged->dir_children_lock, true);\n        RemoveEntryList(&fileref->dc->list_entry_index);\n\n        if (!fileref->fcb->ads)\n            remove_dir_child_from_hash_lists(fileref->parent->fcb, fileref->dc);\n\n        ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock);\n\n        if (!fileref->oldutf8.Buffer)\n            fileref->oldutf8 = fileref->dc->utf8;\n        else\n            ExFreePool(fileref->dc->utf8.Buffer);\n\n        utf8len = fileref->dc->utf8.Length;\n\n        fileref->oldindex = fileref->dc->index;\n\n        ExFreePool(fileref->dc->name.Buffer);\n        ExFreePool(fileref->dc->name_uc.Buffer);\n        ExFreePool(fileref->dc);\n\n        fileref->dc = NULL;\n    }\n\n    // update INODE_ITEM of parent\n\n    ExAcquireResourceExclusiveLite(fileref->parent->fcb->Header.Resource, true);\n\n    fileref->parent->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation;\n    fileref->parent->fcb->inode_item.sequence++;\n    fileref->parent->fcb->inode_item.st_ctime = now;\n\n    if (!fileref->fcb->ads) {\n        TRACE(\"fileref->parent->fcb->inode_item.st_size (inode %I64x) was %I64x\\n\", fileref->parent->fcb->inode, fileref->parent->fcb->inode_item.st_size);\n        fileref->parent->fcb->inode_item.st_size -= utf8len * 2;\n        TRACE(\"fileref->parent->fcb->inode_item.st_size (inode %I64x) now %I64x\\n\", fileref->parent->fcb->inode, fileref->parent->fcb->inode_item.st_size);\n        fileref->parent->fcb->inode_item.st_mtime = now;\n    }\n\n    fileref->parent->fcb->inode_item_changed = true;\n    ExReleaseResourceLite(fileref->parent->fcb->Header.Resource);\n\n    if (!fileref->fcb->ads && fileref->parent->dc)\n        send_notification_fcb(fileref->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n\n    mark_fcb_dirty(fileref->parent->fcb);\n\n    fileref->fcb->subvol->root_item.ctransid = fileref->fcb->Vcb->superblock.generation;\n    fileref->fcb->subvol->root_item.ctime = now;\n\n    ExReleaseResourceLite(fileref->fcb->Header.Resource);\n\n    return STATUS_SUCCESS;\n}\n\n_Dispatch_type_(IRP_MJ_CLEANUP)\n_Function_class_(DRIVER_DISPATCH)\nstatic NTSTATUS __stdcall drv_cleanup(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {\n    NTSTATUS Status;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    fcb* fcb = FileObject->FsContext;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"cleanup\\n\");\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Irp->IoStatus.Information = 0;\n        Status = STATUS_SUCCESS;\n        goto exit;\n    } else if (DeviceObject == master_devobj) {\n        TRACE(\"closing file system\\n\");\n        Status = STATUS_SUCCESS;\n        goto exit;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto exit;\n    }\n\n    if (FileObject->Flags & FO_CLEANUP_COMPLETE) {\n        TRACE(\"FileObject %p already cleaned up\\n\", FileObject);\n        Status = STATUS_SUCCESS;\n        goto exit;\n    }\n\n    if (!fcb) {\n        ERR(\"fcb was NULL\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto exit;\n    }\n\n    FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL);\n\n    // We have to use the pointer to Vcb stored in the fcb, as we can receive cleanup\n    // messages belonging to other devices.\n\n    if (FileObject && FileObject->FsContext) {\n        ccb* ccb;\n        file_ref* fileref;\n        bool locked = true;\n        bool uninitialize_cache_map = false;\n        bool flush_and_purge_cache = false;\n\n        ccb = FileObject->FsContext2;\n        fileref = ccb ? ccb->fileref : NULL;\n\n        TRACE(\"cleanup called for FileObject %p\\n\", FileObject);\n        TRACE(\"fileref %p, refcount = %li, open_count = %li\\n\", fileref, fileref ? fileref->refcount : 0, fileref ? fileref->open_count : 0);\n\n        ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);\n\n        ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n        IoRemoveShareAccess(FileObject, &fcb->share_access);\n\n        FsRtlFastUnlockAll(&fcb->lock, FileObject, IoGetRequestorProcess(Irp), NULL);\n\n        if (ccb)\n            FsRtlNotifyCleanup(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, ccb);\n\n        if (ccb && ccb->options & FILE_DELETE_ON_CLOSE && fileref)\n            fileref->delete_on_close = true;\n\n        if (fileref && fileref->delete_on_close && fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0 && fcb != fcb->Vcb->dummy_fcb)\n            fileref->delete_on_close = false;\n\n        if (fcb->Vcb->locked && fcb->Vcb->locked_fileobj == FileObject) {\n            TRACE(\"unlocking volume\\n\");\n            do_unlock_volume(fcb->Vcb);\n            FsRtlNotifyVolumeEvent(FileObject, FSRTL_VOLUME_UNLOCK);\n        }\n\n        if (ccb && ccb->reserving) {\n            fcb->subvol->reserved = NULL;\n            ccb->reserving = false;\n            // FIXME - flush all of subvol's fcbs\n        }\n\n        if (fileref) {\n            LONG oc = InterlockedDecrement(&fileref->open_count);\n#ifdef DEBUG_FCB_REFCOUNTS\n            ERR(\"fileref %p: open_count now %i\\n\", fileref, oc);\n#endif\n\n            if (oc == 0 || (fileref->delete_on_close && fileref->posix_delete)) {\n                if (!fcb->Vcb->removing) {\n                    if (oc == 0 && fileref->fcb->inode_item.st_nlink == 0 && fileref != fcb->Vcb->root_fileref &&\n                        fcb != fcb->Vcb->volume_fcb && !fcb->ads) { // last handle closed on POSIX-deleted file\n                        LIST_ENTRY rollback;\n\n                        InitializeListHead(&rollback);\n\n                        Status = delete_fileref_fcb(fileref, FileObject, Irp, &rollback);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"delete_fileref_fcb returned %08lx\\n\", Status);\n                            do_rollback(fcb->Vcb, &rollback);\n                            ExReleaseResourceLite(fileref->fcb->Header.Resource);\n                            ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n                            goto exit;\n                        }\n\n                        clear_rollback(&rollback);\n\n                        mark_fcb_dirty(fileref->fcb);\n                    } else if (fileref->delete_on_close && fileref != fcb->Vcb->root_fileref && fcb != fcb->Vcb->volume_fcb) {\n                        LIST_ENTRY rollback;\n\n                        InitializeListHead(&rollback);\n\n                        if (!fileref->fcb->ads || fileref->dc) {\n                            if (fileref->fcb->ads) {\n                                send_notification_fileref(fileref->parent, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME,\n                                                        FILE_ACTION_REMOVED, &fileref->dc->name);\n                            } else\n                                send_notification_fileref(fileref, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED, NULL);\n                        }\n\n                        ExReleaseResourceLite(fcb->Header.Resource);\n                        locked = false;\n\n                        // fileref_lock needs to be acquired before fcb->Header.Resource\n                        ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true);\n\n                        Status = delete_fileref(fileref, FileObject, oc > 0 && fileref->posix_delete, Irp, &rollback);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"delete_fileref returned %08lx\\n\", Status);\n                            do_rollback(fcb->Vcb, &rollback);\n                            ExReleaseResourceLite(&fcb->Vcb->fileref_lock);\n                            ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n                            goto exit;\n                        }\n\n                        ExReleaseResourceLite(&fcb->Vcb->fileref_lock);\n\n                        clear_rollback(&rollback);\n                    } else if (FileObject->Flags & FO_CACHE_SUPPORTED && FileObject->SectionObjectPointer->DataSectionObject)\n                        flush_and_purge_cache = true;\n                }\n\n                if (fcb->Vcb && fcb != fcb->Vcb->volume_fcb)\n                    uninitialize_cache_map = true;\n            }\n        }\n\n        if (locked)\n            ExReleaseResourceLite(fcb->Header.Resource);\n\n        ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n\n        if (flush_and_purge_cache) {\n            IO_STATUS_BLOCK iosb;\n\n            CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb);\n\n            if (!NT_SUCCESS(iosb.Status))\n                ERR(\"CcFlushCache returned %08lx\\n\", iosb.Status);\n\n            if (!ExIsResourceAcquiredSharedLite(fcb->Header.PagingIoResource)) {\n                ExAcquireResourceExclusiveLite(fcb->Header.PagingIoResource, true);\n                ExReleaseResourceLite(fcb->Header.PagingIoResource);\n            }\n\n            CcPurgeCacheSection(FileObject->SectionObjectPointer, NULL, 0, false);\n\n            TRACE(\"flushed cache on close (FileObject = %p, fcb = %p, AllocationSize = %I64x, FileSize = %I64x, ValidDataLength = %I64x)\\n\",\n                  FileObject, fcb, fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);\n        }\n\n        /* In rare instances CcUninitializeCacheMap can block - we need to make\n           sure we're not holding tree_lock if it does. */\n        if (uninitialize_cache_map)\n            CcUninitializeCacheMap(FileObject, NULL, NULL);\n\n        FileObject->Flags |= FO_CLEANUP_COMPLETE;\n    }\n\n    Status = STATUS_SUCCESS;\n\nexit:\n    TRACE(\"returning %08lx\\n\", Status);\n\n    Irp->IoStatus.Status = Status;\n    Irp->IoStatus.Information = 0;\n\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\n_Success_(return)\nbool get_file_attributes_from_xattr(_In_reads_bytes_(len) char* val, _In_ uint16_t len, _Out_ ULONG* atts) {\n    if (len > 2 && val[0] == '0' && val[1] == 'x') {\n        int i;\n        ULONG dosnum = 0;\n\n        for (i = 2; i < len; i++) {\n            dosnum *= 0x10;\n\n            if (val[i] >= '0' && val[i] <= '9')\n                dosnum |= val[i] - '0';\n            else if (val[i] >= 'a' && val[i] <= 'f')\n                dosnum |= val[i] + 10 - 'a';\n            else if (val[i] >= 'A' && val[i] <= 'F')\n                dosnum |= val[i] + 10 - 'a';\n        }\n\n        TRACE(\"DOSATTRIB: %08lx\\n\", dosnum);\n\n        *atts = dosnum;\n\n        return true;\n    }\n\n    return false;\n}\n\nULONG get_file_attributes(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _In_ uint64_t inode,\n                          _In_ uint8_t type, _In_ bool dotfile, _In_ bool ignore_xa, _In_opt_ PIRP Irp) {\n    ULONG att;\n    char* eaval;\n    uint16_t ealen;\n\n    if (!ignore_xa && get_xattr(Vcb, r, inode, EA_DOSATTRIB, EA_DOSATTRIB_HASH, (uint8_t**)&eaval, &ealen, Irp)) {\n        ULONG dosnum = 0;\n\n        if (get_file_attributes_from_xattr(eaval, ealen, &dosnum)) {\n            ExFreePool(eaval);\n\n            if (type == BTRFS_TYPE_DIRECTORY)\n                dosnum |= FILE_ATTRIBUTE_DIRECTORY;\n            else if (type == BTRFS_TYPE_SYMLINK)\n                dosnum |= FILE_ATTRIBUTE_REPARSE_POINT;\n\n            if (type != BTRFS_TYPE_DIRECTORY)\n                dosnum &= ~FILE_ATTRIBUTE_DIRECTORY;\n\n            if (inode == SUBVOL_ROOT_INODE) {\n                if (r->root_item.flags & BTRFS_SUBVOL_READONLY)\n                    dosnum |= FILE_ATTRIBUTE_READONLY;\n                else\n                    dosnum &= ~FILE_ATTRIBUTE_READONLY;\n            }\n\n            return dosnum;\n        }\n\n        ExFreePool(eaval);\n    }\n\n    switch (type) {\n        case BTRFS_TYPE_DIRECTORY:\n            att = FILE_ATTRIBUTE_DIRECTORY;\n            break;\n\n        case BTRFS_TYPE_SYMLINK:\n            att = FILE_ATTRIBUTE_REPARSE_POINT;\n            break;\n\n        default:\n            att = 0;\n            break;\n    }\n\n    if (dotfile || (r->id == BTRFS_ROOT_FSTREE && inode == SUBVOL_ROOT_INODE))\n        att |= FILE_ATTRIBUTE_HIDDEN;\n\n    att |= FILE_ATTRIBUTE_ARCHIVE;\n\n    if (inode == SUBVOL_ROOT_INODE) {\n        if (r->root_item.flags & BTRFS_SUBVOL_READONLY)\n            att |= FILE_ATTRIBUTE_READONLY;\n        else\n            att &= ~FILE_ATTRIBUTE_READONLY;\n    }\n\n    // FIXME - get READONLY from ii->st_mode\n    // FIXME - return SYSTEM for block/char devices?\n\n    if (att == 0)\n        att = FILE_ATTRIBUTE_NORMAL;\n\n    return att;\n}\n\nNTSTATUS sync_read_phys(_In_ PDEVICE_OBJECT DeviceObject, _In_ PFILE_OBJECT FileObject, _In_ uint64_t StartingOffset, _In_ ULONG Length,\n                        _Out_writes_bytes_(Length) PUCHAR Buffer, _In_ bool override) {\n    IO_STATUS_BLOCK IoStatus;\n    LARGE_INTEGER Offset;\n    PIRP Irp;\n    PIO_STACK_LOCATION IrpSp;\n    NTSTATUS Status;\n    read_context context;\n\n    RtlZeroMemory(&context, sizeof(read_context));\n    KeInitializeEvent(&context.Event, NotificationEvent, false);\n\n    Offset.QuadPart = (LONGLONG)StartingOffset;\n\n    Irp = IoAllocateIrp(DeviceObject->StackSize, false);\n\n    if (!Irp) {\n        ERR(\"IoAllocateIrp failed\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    Irp->Flags |= IRP_NOCACHE;\n    IrpSp = IoGetNextIrpStackLocation(Irp);\n    IrpSp->MajorFunction = IRP_MJ_READ;\n    IrpSp->FileObject = FileObject;\n\n    if (override)\n        IrpSp->Flags |= SL_OVERRIDE_VERIFY_VOLUME;\n\n    if (DeviceObject->Flags & DO_BUFFERED_IO) {\n        Irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPool, Length, ALLOC_TAG);\n        if (!Irp->AssociatedIrp.SystemBuffer) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        Irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION;\n\n        Irp->UserBuffer = Buffer;\n    } else if (DeviceObject->Flags & DO_DIRECT_IO) {\n        Irp->MdlAddress = IoAllocateMdl(Buffer, Length, false, false, NULL);\n        if (!Irp->MdlAddress) {\n            ERR(\"IoAllocateMdl failed\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        Status = STATUS_SUCCESS;\n\n        try {\n            MmProbeAndLockPages(Irp->MdlAddress, KernelMode, IoWriteAccess);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n            IoFreeMdl(Irp->MdlAddress);\n            goto exit;\n        }\n    } else\n        Irp->UserBuffer = Buffer;\n\n    IrpSp->Parameters.Read.Length = Length;\n    IrpSp->Parameters.Read.ByteOffset = Offset;\n\n    Irp->UserIosb = &IoStatus;\n\n    Irp->UserEvent = &context.Event;\n\n    IoSetCompletionRoutine(Irp, read_completion, &context, true, true, true);\n\n    Status = IoCallDriver(DeviceObject, Irp);\n\n    if (Status == STATUS_PENDING) {\n        KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n        Status = context.iosb.Status;\n    }\n\n    if (DeviceObject->Flags & DO_DIRECT_IO) {\n        MmUnlockPages(Irp->MdlAddress);\n        IoFreeMdl(Irp->MdlAddress);\n    }\n\nexit:\n    IoFreeIrp(Irp);\n\n    return Status;\n}\n\nbool check_superblock_checksum(superblock* sb) {\n    switch (sb->csum_type) {\n        case CSUM_TYPE_CRC32C: {\n            uint32_t crc32 = ~calc_crc32c(0xffffffff, (uint8_t*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum));\n\n            if (crc32 == *((uint32_t*)sb->checksum))\n                return true;\n\n            WARN(\"crc32 was %08x, expected %08x\\n\", crc32, *((uint32_t*)sb->checksum));\n\n            break;\n        }\n\n        case CSUM_TYPE_XXHASH: {\n            uint64_t hash = XXH64(&sb->uuid, sizeof(superblock) - sizeof(sb->checksum), 0);\n\n            if (hash == *((uint64_t*)sb->checksum))\n                return true;\n\n            WARN(\"superblock hash was %I64x, expected %I64x\\n\", hash, *((uint64_t*)sb->checksum));\n\n            break;\n        }\n\n        case CSUM_TYPE_SHA256: {\n            uint8_t hash[SHA256_HASH_SIZE];\n\n            calc_sha256(hash, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum));\n\n            if (RtlCompareMemory(hash, sb, SHA256_HASH_SIZE) == SHA256_HASH_SIZE)\n                return true;\n\n            WARN(\"superblock hash was invalid\\n\");\n\n            break;\n        }\n\n        case CSUM_TYPE_BLAKE2: {\n            uint8_t hash[BLAKE2_HASH_SIZE];\n\n            blake2b(hash, sizeof(hash), &sb->uuid, sizeof(superblock) - sizeof(sb->checksum));\n\n            if (RtlCompareMemory(hash, sb, BLAKE2_HASH_SIZE) == BLAKE2_HASH_SIZE)\n                return true;\n\n            WARN(\"superblock hash was invalid\\n\");\n\n            break;\n        }\n\n        default:\n            WARN(\"unrecognized csum type %x\\n\", sb->csum_type);\n    }\n\n    return false;\n}\n\nstatic NTSTATUS read_superblock(_In_ device_extension* Vcb, _In_ PDEVICE_OBJECT device, _In_ PFILE_OBJECT fileobj, _In_ uint64_t length) {\n    NTSTATUS Status;\n    superblock* sb;\n    ULONG i, to_read;\n    uint8_t valid_superblocks;\n\n    to_read = device->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), device->SectorSize);\n\n    sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG);\n    if (!sb) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    if (superblock_addrs[0] + to_read > length) {\n        WARN(\"device was too short to have any superblock\\n\");\n        ExFreePool(sb);\n        return STATUS_UNRECOGNIZED_VOLUME;\n    }\n\n    i = 0;\n    valid_superblocks = 0;\n\n    while (superblock_addrs[i] > 0) {\n        if (i > 0 && superblock_addrs[i] + to_read > length)\n            break;\n\n        Status = sync_read_phys(device, fileobj, superblock_addrs[i], to_read, (PUCHAR)sb, false);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"Failed to read superblock %lu: %08lx\\n\", i, Status);\n            ExFreePool(sb);\n            return Status;\n        }\n\n        if (sb->magic != BTRFS_MAGIC) {\n            if (i == 0) {\n                TRACE(\"not a BTRFS volume\\n\");\n                ExFreePool(sb);\n                return STATUS_UNRECOGNIZED_VOLUME;\n            }\n        } else {\n            TRACE(\"got superblock %lu!\\n\", i);\n\n            if (sb->sector_size == 0)\n                WARN(\"superblock sector size was 0\\n\");\n            else if (sb->sector_size & (sb->sector_size - 1))\n                WARN(\"superblock sector size was not power of 2\\n\");\n            else if (sb->node_size < sizeof(tree_header) + sizeof(internal_node) || sb->node_size > 0x10000)\n                WARN(\"invalid node size %x\\n\", sb->node_size);\n            else if ((sb->node_size % sb->sector_size) != 0)\n                WARN(\"node size %x was not a multiple of sector_size %x\\n\", sb->node_size, sb->sector_size);\n            else if (check_superblock_checksum(sb) && (valid_superblocks == 0 || sb->generation > Vcb->superblock.generation)) {\n                RtlCopyMemory(&Vcb->superblock, sb, sizeof(superblock));\n                valid_superblocks++;\n            }\n        }\n\n        i++;\n    }\n\n    ExFreePool(sb);\n\n    if (valid_superblocks == 0) {\n        ERR(\"could not find any valid superblocks\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    TRACE(\"label is %s\\n\", Vcb->superblock.label);\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS dev_ioctl(_In_ PDEVICE_OBJECT DeviceObject, _In_ ULONG ControlCode, _In_reads_bytes_opt_(InputBufferSize) PVOID InputBuffer, _In_ ULONG InputBufferSize,\n                   _Out_writes_bytes_opt_(OutputBufferSize) PVOID OutputBuffer, _In_ ULONG OutputBufferSize, _In_ bool Override, _Out_opt_ IO_STATUS_BLOCK* iosb) {\n    PIRP Irp;\n    KEVENT Event;\n    NTSTATUS Status;\n    PIO_STACK_LOCATION IrpSp;\n    IO_STATUS_BLOCK IoStatus;\n\n    KeInitializeEvent(&Event, NotificationEvent, false);\n\n    Irp = IoBuildDeviceIoControlRequest(ControlCode,\n                                        DeviceObject,\n                                        InputBuffer,\n                                        InputBufferSize,\n                                        OutputBuffer,\n                                        OutputBufferSize,\n                                        false,\n                                        &Event,\n                                        &IoStatus);\n\n    if (!Irp) return STATUS_INSUFFICIENT_RESOURCES;\n\n    if (Override) {\n        IrpSp = IoGetNextIrpStackLocation(Irp);\n        IrpSp->Flags |= SL_OVERRIDE_VERIFY_VOLUME;\n    }\n\n    Status = IoCallDriver(DeviceObject, Irp);\n\n    if (Status == STATUS_PENDING) {\n        KeWaitForSingleObject(&Event, Executive, KernelMode, false, NULL);\n        Status = IoStatus.Status;\n    }\n\n    if (iosb)\n        *iosb = IoStatus;\n\n    return Status;\n}\n\n_Requires_exclusive_lock_held_(Vcb->tree_lock)\nstatic NTSTATUS add_root(_Inout_ device_extension* Vcb, _In_ uint64_t id, _In_ uint64_t addr,\n                         _In_ uint64_t generation, _In_opt_ traverse_ptr* tp) {\n    root* r = ExAllocatePoolWithTag(PagedPool, sizeof(root), ALLOC_TAG);\n    if (!r) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    r->id = id;\n    r->dirty = false;\n    r->received = false;\n    r->reserved = NULL;\n    r->treeholder.address = addr;\n    r->treeholder.tree = NULL;\n    r->treeholder.generation = generation;\n    r->parent = 0;\n    r->send_ops = 0;\n    r->fcbs_version = 0;\n    r->checked_for_orphans = false;\n    r->dropped = false;\n    InitializeListHead(&r->fcbs);\n    RtlZeroMemory(r->fcbs_ptrs, sizeof(LIST_ENTRY*) * 256);\n\n    r->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(root_nonpaged), ALLOC_TAG);\n    if (!r->nonpaged) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(r);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    ExInitializeResourceLite(&r->nonpaged->load_tree_lock);\n\n    r->lastinode = 0;\n\n    if (tp) {\n        RtlCopyMemory(&r->root_item, tp->item->data, min(sizeof(ROOT_ITEM), tp->item->size));\n        if (tp->item->size < sizeof(ROOT_ITEM))\n            RtlZeroMemory(((uint8_t*)&r->root_item) + tp->item->size, sizeof(ROOT_ITEM) - tp->item->size);\n    } else\n        RtlZeroMemory(&r->root_item, sizeof(ROOT_ITEM));\n\n    if (!Vcb->readonly && (r->id == BTRFS_ROOT_ROOT || r->id == BTRFS_ROOT_FSTREE || (r->id >= 0x100 && !(r->id & 0xf000000000000000)))) { // FS tree root\n        // FIXME - don't call this if subvol is readonly (though we will have to if we ever toggle this flag)\n        get_last_inode(Vcb, r, NULL);\n\n        if (r->id == BTRFS_ROOT_ROOT && r->lastinode < 0x100)\n            r->lastinode = 0x100;\n    }\n\n    InsertTailList(&Vcb->roots, &r->list_entry);\n\n    switch (r->id) {\n        case BTRFS_ROOT_ROOT:\n            Vcb->root_root = r;\n            break;\n\n        case BTRFS_ROOT_EXTENT:\n            Vcb->extent_root = r;\n            break;\n\n        case BTRFS_ROOT_CHUNK:\n            Vcb->chunk_root = r;\n            break;\n\n        case BTRFS_ROOT_DEVTREE:\n            Vcb->dev_root = r;\n            break;\n\n        case BTRFS_ROOT_CHECKSUM:\n            Vcb->checksum_root = r;\n            break;\n\n        case BTRFS_ROOT_UUID:\n            Vcb->uuid_root = r;\n            break;\n\n        case BTRFS_ROOT_FREE_SPACE:\n            Vcb->space_root = r;\n            break;\n\n        case BTRFS_ROOT_DATA_RELOC:\n            Vcb->data_reloc_root = r;\n            break;\n\n        case BTRFS_ROOT_BLOCK_GROUP:\n            if (Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE)\n                Vcb->block_group_root = r;\n            break;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS look_for_roots(_Requires_exclusive_lock_held_(_Curr_->tree_lock) _In_ device_extension* Vcb, _In_opt_ PIRP Irp) {\n    traverse_ptr tp, next_tp;\n    KEY searchkey;\n    bool b;\n    NTSTATUS Status;\n\n    searchkey.obj_id = 0;\n    searchkey.obj_type = 0;\n    searchkey.offset = 0;\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    do {\n        TRACE(\"(%I64x,%x,%I64x)\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n\n        if (tp.item->key.obj_type == TYPE_ROOT_ITEM) {\n            ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;\n\n            if (tp.item->size < offsetof(ROOT_ITEM, byte_limit)) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, offsetof(ROOT_ITEM, byte_limit));\n            } else {\n                TRACE(\"root %I64x - address %I64x\\n\", tp.item->key.obj_id, ri->block_number);\n\n                Status = add_root(Vcb, tp.item->key.obj_id, ri->block_number, ri->generation, &tp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"add_root returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n        } else if (tp.item->key.obj_type == TYPE_ROOT_BACKREF && !IsListEmpty(&Vcb->roots)) {\n            root* lastroot = CONTAINING_RECORD(Vcb->roots.Blink, root, list_entry);\n\n            if (lastroot->id == tp.item->key.obj_id)\n                lastroot->parent = tp.item->key.offset;\n        }\n\n        b = find_next_item(Vcb, &tp, &next_tp, false, Irp);\n\n        if (b)\n            tp = next_tp;\n    } while (b);\n\n    if (!Vcb->readonly && !Vcb->data_reloc_root) {\n        root* reloc_root;\n        INODE_ITEM* ii;\n        uint16_t irlen;\n        INODE_REF* ir;\n        LARGE_INTEGER time;\n        BTRFS_TIME now;\n\n        WARN(\"data reloc root doesn't exist, creating it\\n\");\n\n        Status = create_root(Vcb, BTRFS_ROOT_DATA_RELOC, &reloc_root, false, 0, Irp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"create_root returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        reloc_root->root_item.inode.generation = 1;\n        reloc_root->root_item.inode.st_size = 3;\n        reloc_root->root_item.inode.st_blocks = Vcb->superblock.node_size;\n        reloc_root->root_item.inode.st_nlink = 1;\n        reloc_root->root_item.inode.st_mode = 040755;\n        reloc_root->root_item.inode.flags = 0x80000000;\n        reloc_root->root_item.inode.flags_ro = 0xffffffff;\n        reloc_root->root_item.objid = SUBVOL_ROOT_INODE;\n        reloc_root->root_item.bytes_used = Vcb->superblock.node_size;\n\n        ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);\n        if (!ii) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        KeQuerySystemTime(&time);\n        win_time_to_unix(time, &now);\n\n        RtlZeroMemory(ii, sizeof(INODE_ITEM));\n        ii->generation = Vcb->superblock.generation;\n        ii->st_blocks = Vcb->superblock.node_size;\n        ii->st_nlink = 1;\n        ii->st_mode = 040755;\n        ii->st_atime = now;\n        ii->st_ctime = now;\n        ii->st_mtime = now;\n\n        Status = insert_tree_item(Vcb, reloc_root, SUBVOL_ROOT_INODE, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            ExFreePool(ii);\n            return Status;\n        }\n\n        irlen = (uint16_t)offsetof(INODE_REF, name[0]) + 2;\n        ir = ExAllocatePoolWithTag(PagedPool, irlen, ALLOC_TAG);\n        if (!ir) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        ir->index = 0;\n        ir->n = 2;\n        ir->name[0] = '.';\n        ir->name[1] = '.';\n\n        Status = insert_tree_item(Vcb, reloc_root, SUBVOL_ROOT_INODE, TYPE_INODE_REF, SUBVOL_ROOT_INODE, ir, irlen, NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            ExFreePool(ir);\n            return Status;\n        }\n\n        Vcb->data_reloc_root = reloc_root;\n        Vcb->need_write = true;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS find_disk_holes(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ device* dev, _In_opt_ PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp, next_tp;\n    bool b;\n    uint64_t lastaddr;\n    NTSTATUS Status;\n\n    InitializeListHead(&dev->space);\n\n    searchkey.obj_id = 0;\n    searchkey.obj_type = TYPE_DEV_STATS;\n    searchkey.offset = dev->devitem.dev_id;\n\n    Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, Irp);\n    if (NT_SUCCESS(Status) && !keycmp(tp.item->key, searchkey))\n        RtlCopyMemory(dev->stats, tp.item->data, min(sizeof(uint64_t) * 5, tp.item->size));\n\n    searchkey.obj_id = dev->devitem.dev_id;\n    searchkey.obj_type = TYPE_DEV_EXTENT;\n    searchkey.offset = 0;\n\n    Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    lastaddr = 0;\n\n    do {\n        if (tp.item->key.obj_id == dev->devitem.dev_id && tp.item->key.obj_type == TYPE_DEV_EXTENT) {\n            if (tp.item->size >= sizeof(DEV_EXTENT)) {\n                DEV_EXTENT* de = (DEV_EXTENT*)tp.item->data;\n\n                if (tp.item->key.offset > lastaddr) {\n                    Status = add_space_entry(&dev->space, NULL, lastaddr, tp.item->key.offset - lastaddr);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"add_space_entry returned %08lx\\n\", Status);\n                        return Status;\n                    }\n                }\n\n                lastaddr = tp.item->key.offset + de->length;\n            } else {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DEV_EXTENT));\n            }\n        }\n\n        b = find_next_item(Vcb, &tp, &next_tp, false, Irp);\n\n        if (b) {\n            tp = next_tp;\n            if (tp.item->key.obj_id > searchkey.obj_id || tp.item->key.obj_type > searchkey.obj_type)\n                break;\n        }\n    } while (b);\n\n    if (lastaddr < dev->devitem.num_bytes) {\n        Status = add_space_entry(&dev->space, NULL, lastaddr, dev->devitem.num_bytes - lastaddr);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_space_entry returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    // The Linux driver doesn't like to allocate chunks within the first megabyte of a device.\n\n    space_list_subtract2(&dev->space, NULL, 0, 0x100000, NULL, NULL);\n\n    return STATUS_SUCCESS;\n}\n\nstatic void add_device_to_list(_In_ device_extension* Vcb, _In_ device* dev) {\n    LIST_ENTRY* le;\n\n    le = Vcb->devices.Flink;\n\n    while (le != &Vcb->devices) {\n        device* dev2 = CONTAINING_RECORD(le, device, list_entry);\n\n        if (dev2->devitem.dev_id > dev->devitem.dev_id) {\n            InsertHeadList(le->Blink, &dev->list_entry);\n            return;\n        }\n\n        le = le->Flink;\n    }\n\n    InsertTailList(&Vcb->devices, &dev->list_entry);\n}\n\n_Ret_maybenull_\ndevice* find_device_from_uuid(_In_ device_extension* Vcb, _In_ BTRFS_UUID* uuid) {\n    volume_device_extension* vde;\n    pdo_device_extension* pdode;\n    LIST_ENTRY* le;\n\n    le = Vcb->devices.Flink;\n    while (le != &Vcb->devices) {\n        device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n        TRACE(\"device %I64x, uuid %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\\n\", dev->devitem.dev_id,\n            dev->devitem.device_uuid.uuid[0], dev->devitem.device_uuid.uuid[1], dev->devitem.device_uuid.uuid[2], dev->devitem.device_uuid.uuid[3], dev->devitem.device_uuid.uuid[4], dev->devitem.device_uuid.uuid[5], dev->devitem.device_uuid.uuid[6], dev->devitem.device_uuid.uuid[7],\n            dev->devitem.device_uuid.uuid[8], dev->devitem.device_uuid.uuid[9], dev->devitem.device_uuid.uuid[10], dev->devitem.device_uuid.uuid[11], dev->devitem.device_uuid.uuid[12], dev->devitem.device_uuid.uuid[13], dev->devitem.device_uuid.uuid[14], dev->devitem.device_uuid.uuid[15]);\n\n        if (RtlCompareMemory(&dev->devitem.device_uuid, uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n            TRACE(\"returning device %I64x\\n\", dev->devitem.dev_id);\n            return dev;\n        }\n\n        le = le->Flink;\n    }\n\n    vde = Vcb->vde;\n\n    if (!vde)\n        goto end;\n\n    pdode = vde->pdode;\n\n    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n    if (Vcb->devices_loaded < Vcb->superblock.num_devices) {\n        le = pdode->children.Flink;\n\n        while (le != &pdode->children) {\n            volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n            if (RtlCompareMemory(uuid, &vc->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n                device* dev;\n\n                dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG);\n                if (!dev) {\n                    ExReleaseResourceLite(&pdode->child_lock);\n                    ERR(\"out of memory\\n\");\n                    return NULL;\n                }\n\n                RtlZeroMemory(dev, sizeof(device));\n                dev->devobj = vc->devobj;\n                dev->fileobj = vc->fileobj;\n                dev->devitem.device_uuid = *uuid;\n                dev->devitem.dev_id = vc->devid;\n                dev->devitem.num_bytes = vc->size;\n                dev->seeding = vc->seeding;\n                dev->readonly = dev->seeding;\n                dev->reloc = false;\n                dev->removable = false;\n                dev->disk_num = vc->disk_num;\n                dev->part_num = vc->part_num;\n                dev->num_trim_entries = 0;\n                InitializeListHead(&dev->trim_list);\n\n                add_device_to_list(Vcb, dev);\n                Vcb->devices_loaded++;\n\n                ExReleaseResourceLite(&pdode->child_lock);\n\n                return dev;\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    ExReleaseResourceLite(&pdode->child_lock);\n\nend:\n    WARN(\"could not find device with uuid %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\\n\",\n         uuid->uuid[0], uuid->uuid[1], uuid->uuid[2], uuid->uuid[3], uuid->uuid[4], uuid->uuid[5], uuid->uuid[6], uuid->uuid[7],\n         uuid->uuid[8], uuid->uuid[9], uuid->uuid[10], uuid->uuid[11], uuid->uuid[12], uuid->uuid[13], uuid->uuid[14], uuid->uuid[15]);\n\n    return NULL;\n}\n\nstatic bool is_device_removable(_In_ PDEVICE_OBJECT devobj) {\n    NTSTATUS Status;\n    STORAGE_HOTPLUG_INFO shi;\n\n    Status = dev_ioctl(devobj, IOCTL_STORAGE_GET_HOTPLUG_INFO, NULL, 0, &shi, sizeof(STORAGE_HOTPLUG_INFO), true, NULL);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"dev_ioctl returned %08lx\\n\", Status);\n        return false;\n    }\n\n    return shi.MediaRemovable != 0 ? true : false;\n}\n\nstatic ULONG get_device_change_count(_In_ PDEVICE_OBJECT devobj) {\n    NTSTATUS Status;\n    ULONG cc;\n    IO_STATUS_BLOCK iosb;\n\n    Status = dev_ioctl(devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, &cc, sizeof(ULONG), true, &iosb);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"dev_ioctl returned %08lx\\n\", Status);\n        return 0;\n    }\n\n    if (iosb.Information < sizeof(ULONG)) {\n        ERR(\"iosb.Information was too short\\n\");\n        return 0;\n    }\n\n    return cc;\n}\n\nvoid init_device(_In_ device_extension* Vcb, _Inout_ device* dev, _In_ bool get_nums) {\n    NTSTATUS Status;\n    ULONG aptelen;\n    ATA_PASS_THROUGH_EX* apte;\n    STORAGE_PROPERTY_QUERY spq;\n    DEVICE_TRIM_DESCRIPTOR dtd;\n\n    dev->removable = is_device_removable(dev->devobj);\n    dev->change_count = dev->removable ? get_device_change_count(dev->devobj) : 0;\n\n    if (get_nums) {\n        STORAGE_DEVICE_NUMBER sdn;\n\n        Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0,\n                           &sdn, sizeof(STORAGE_DEVICE_NUMBER), true, NULL);\n\n        if (!NT_SUCCESS(Status)) {\n            WARN(\"IOCTL_STORAGE_GET_DEVICE_NUMBER returned %08lx\\n\", Status);\n            dev->disk_num = 0xffffffff;\n            dev->part_num = 0xffffffff;\n        } else {\n            dev->disk_num = sdn.DeviceNumber;\n            dev->part_num = sdn.PartitionNumber;\n        }\n    }\n\n    dev->trim = false;\n    dev->readonly = dev->seeding;\n    dev->reloc = false;\n    dev->num_trim_entries = 0;\n    dev->stats_changed = false;\n    InitializeListHead(&dev->trim_list);\n\n    if (!dev->readonly) {\n        Status = dev_ioctl(dev->devobj, IOCTL_DISK_IS_WRITABLE, NULL, 0,\n                        NULL, 0, true, NULL);\n        if (Status == STATUS_MEDIA_WRITE_PROTECTED)\n            dev->readonly = true;\n    }\n\n    aptelen = sizeof(ATA_PASS_THROUGH_EX) + 512;\n    apte = ExAllocatePoolWithTag(NonPagedPool, aptelen, ALLOC_TAG);\n    if (!apte) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    RtlZeroMemory(apte, aptelen);\n\n    apte->Length = sizeof(ATA_PASS_THROUGH_EX);\n    apte->AtaFlags = ATA_FLAGS_DATA_IN;\n    apte->DataTransferLength = aptelen - sizeof(ATA_PASS_THROUGH_EX);\n    apte->TimeOutValue = 3;\n    apte->DataBufferOffset = apte->Length;\n    apte->CurrentTaskFile[6] = IDE_COMMAND_IDENTIFY;\n\n    Status = dev_ioctl(dev->devobj, IOCTL_ATA_PASS_THROUGH, apte, aptelen,\n                       apte, aptelen, true, NULL);\n\n    if (!NT_SUCCESS(Status))\n        TRACE(\"IOCTL_ATA_PASS_THROUGH returned %08lx for IDENTIFY DEVICE\\n\", Status);\n    else {\n        IDENTIFY_DEVICE_DATA* idd = (IDENTIFY_DEVICE_DATA*)((uint8_t*)apte + sizeof(ATA_PASS_THROUGH_EX));\n\n        if (idd->CommandSetSupport.FlushCache) {\n            dev->can_flush = true;\n            TRACE(\"FLUSH CACHE supported\\n\");\n        } else\n            TRACE(\"FLUSH CACHE not supported\\n\");\n    }\n\n    ExFreePool(apte);\n\n#ifdef DEBUG_TRIM_EMULATION\n    dev->trim = true;\n    Vcb->trim = true;\n#else\n    spq.PropertyId = StorageDeviceTrimProperty;\n    spq.QueryType = PropertyStandardQuery;\n    spq.AdditionalParameters[0] = 0;\n\n    Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_QUERY_PROPERTY, &spq, sizeof(STORAGE_PROPERTY_QUERY),\n                       &dtd, sizeof(DEVICE_TRIM_DESCRIPTOR), true, NULL);\n\n    if (NT_SUCCESS(Status)) {\n        if (dtd.TrimEnabled) {\n            dev->trim = true;\n            Vcb->trim = true;\n            TRACE(\"TRIM supported\\n\");\n        } else\n            TRACE(\"TRIM not supported\\n\");\n    }\n#endif\n\n    RtlZeroMemory(dev->stats, sizeof(uint64_t) * 5);\n}\n\nstatic NTSTATUS load_chunk_root(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp) {\n    traverse_ptr tp, next_tp;\n    KEY searchkey;\n    bool b;\n    chunk* c;\n    NTSTATUS Status;\n\n    searchkey.obj_id = 0;\n    searchkey.obj_type = 0;\n    searchkey.offset = 0;\n\n    Vcb->data_flags = 0;\n    Vcb->metadata_flags = 0;\n    Vcb->system_flags = 0;\n\n    Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    do {\n        TRACE(\"(%I64x,%x,%I64x)\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n\n        if (tp.item->key.obj_id == 1 && tp.item->key.obj_type == TYPE_DEV_ITEM) {\n            if (tp.item->size < sizeof(DEV_ITEM)) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DEV_ITEM));\n            } else {\n                DEV_ITEM* di = (DEV_ITEM*)tp.item->data;\n                LIST_ENTRY* le;\n                bool done = false;\n\n                le = Vcb->devices.Flink;\n                while (le != &Vcb->devices) {\n                    device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n                    if (dev->devobj && RtlCompareMemory(&dev->devitem.device_uuid, &di->device_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n                        RtlCopyMemory(&dev->devitem, tp.item->data, min(tp.item->size, sizeof(DEV_ITEM)));\n\n                        if (le != Vcb->devices.Flink)\n                            init_device(Vcb, dev, true);\n\n                        done = true;\n                        break;\n                    }\n\n                    le = le->Flink;\n                }\n\n                if (!done && Vcb->vde) {\n                    volume_device_extension* vde = Vcb->vde;\n                    pdo_device_extension* pdode = vde->pdode;\n\n                    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n                    if (Vcb->devices_loaded < Vcb->superblock.num_devices) {\n                        le = pdode->children.Flink;\n\n                        while (le != &pdode->children) {\n                            volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n                            if (RtlCompareMemory(&di->device_uuid, &vc->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n                                device* dev;\n\n                                dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG);\n                                if (!dev) {\n                                    ExReleaseResourceLite(&pdode->child_lock);\n                                    ERR(\"out of memory\\n\");\n                                    return STATUS_INSUFFICIENT_RESOURCES;\n                                }\n\n                                RtlZeroMemory(dev, sizeof(device));\n\n                                dev->devobj = vc->devobj;\n                                dev->fileobj = vc->fileobj;\n                                RtlCopyMemory(&dev->devitem, di, min(tp.item->size, sizeof(DEV_ITEM)));\n                                dev->seeding = vc->seeding;\n                                init_device(Vcb, dev, false);\n\n                                if (dev->devitem.num_bytes > vc->size) {\n                                    WARN(\"device %I64x: DEV_ITEM says %I64x bytes, but Windows only reports %I64x\\n\", tp.item->key.offset,\n                                         dev->devitem.num_bytes, vc->size);\n\n                                    dev->devitem.num_bytes = vc->size;\n                                }\n\n                                dev->disk_num = vc->disk_num;\n                                dev->part_num = vc->part_num;\n                                add_device_to_list(Vcb, dev);\n                                Vcb->devices_loaded++;\n\n                                done = true;\n                                break;\n                            }\n\n                            le = le->Flink;\n                        }\n\n                        if (!done) {\n                            if (!Vcb->options.allow_degraded) {\n                                ERR(\"volume not found: device %I64x, uuid %02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\\n\", tp.item->key.offset,\n                                    di->device_uuid.uuid[0], di->device_uuid.uuid[1], di->device_uuid.uuid[2], di->device_uuid.uuid[3], di->device_uuid.uuid[4], di->device_uuid.uuid[5], di->device_uuid.uuid[6], di->device_uuid.uuid[7],\n                                    di->device_uuid.uuid[8], di->device_uuid.uuid[9], di->device_uuid.uuid[10], di->device_uuid.uuid[11], di->device_uuid.uuid[12], di->device_uuid.uuid[13], di->device_uuid.uuid[14], di->device_uuid.uuid[15]);\n                            } else {\n                                device* dev;\n\n                                dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG);\n                                if (!dev) {\n                                    ExReleaseResourceLite(&pdode->child_lock);\n                                    ERR(\"out of memory\\n\");\n                                    return STATUS_INSUFFICIENT_RESOURCES;\n                                }\n\n                                RtlZeroMemory(dev, sizeof(device));\n\n                                // Missing device, so we keep dev->devobj as NULL\n                                RtlCopyMemory(&dev->devitem, di, min(tp.item->size, sizeof(DEV_ITEM)));\n                                InitializeListHead(&dev->trim_list);\n\n                                add_device_to_list(Vcb, dev);\n                                Vcb->devices_loaded++;\n                            }\n                        }\n                    } else\n                        ERR(\"unexpected device %I64x found\\n\", tp.item->key.offset);\n\n                    ExReleaseResourceLite(&pdode->child_lock);\n                }\n            }\n        } else if (tp.item->key.obj_type == TYPE_CHUNK_ITEM) {\n            if (tp.item->size < sizeof(CHUNK_ITEM)) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(CHUNK_ITEM));\n            } else {\n                c = ExAllocatePoolWithTag(NonPagedPool, sizeof(chunk), ALLOC_TAG);\n\n                if (!c) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                c->size = tp.item->size;\n                c->offset = tp.item->key.offset;\n                c->used = c->oldused = 0;\n                c->cache = c->old_cache = NULL;\n                c->created = false;\n                c->readonly = false;\n                c->reloc = false;\n                c->cache_loaded = false;\n                c->changed = false;\n                c->space_changed = false;\n                c->balance_num = 0;\n\n                c->chunk_item = ExAllocatePoolWithTag(NonPagedPool, tp.item->size, ALLOC_TAG);\n\n                if (!c->chunk_item) {\n                    ERR(\"out of memory\\n\");\n                    ExFreePool(c);\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(c->chunk_item, tp.item->data, tp.item->size);\n\n                if (c->chunk_item->type & BLOCK_FLAG_DATA && c->chunk_item->type > Vcb->data_flags)\n                    Vcb->data_flags = c->chunk_item->type;\n\n                if (c->chunk_item->type & BLOCK_FLAG_METADATA && c->chunk_item->type > Vcb->metadata_flags)\n                    Vcb->metadata_flags = c->chunk_item->type;\n\n                if (c->chunk_item->type & BLOCK_FLAG_SYSTEM && c->chunk_item->type > Vcb->system_flags)\n                    Vcb->system_flags = c->chunk_item->type;\n\n                if (c->chunk_item->type & BLOCK_FLAG_RAID10) {\n                    if (c->chunk_item->sub_stripes == 0 || c->chunk_item->sub_stripes > c->chunk_item->num_stripes) {\n                        ERR(\"chunk %I64x: invalid stripes (num_stripes %u, sub_stripes %u)\\n\", c->offset, c->chunk_item->num_stripes, c->chunk_item->sub_stripes);\n                        ExFreePool(c->chunk_item);\n                        ExFreePool(c);\n                        return STATUS_INTERNAL_ERROR;\n                    }\n                }\n\n                if (c->chunk_item->num_stripes > 0) {\n                    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n                    uint16_t i;\n\n                    c->devices = ExAllocatePoolWithTag(NonPagedPool, sizeof(device*) * c->chunk_item->num_stripes, ALLOC_TAG);\n\n                    if (!c->devices) {\n                        ERR(\"out of memory\\n\");\n                        ExFreePool(c->chunk_item);\n                        ExFreePool(c);\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n                        c->devices[i] = find_device_from_uuid(Vcb, &cis[i].dev_uuid);\n                        TRACE(\"device %u = %p\\n\", i, c->devices[i]);\n\n                        if (!c->devices[i]) {\n                            ERR(\"missing device\\n\");\n                            ExFreePool(c->chunk_item);\n                            ExFreePool(c);\n                            return STATUS_INTERNAL_ERROR;\n                        }\n\n                        if (c->devices[i]->readonly)\n                            c->readonly = true;\n                    }\n                } else {\n                    ERR(\"chunk %I64x: number of stripes is 0\\n\", c->offset);\n                    ExFreePool(c->chunk_item);\n                    ExFreePool(c);\n                    return STATUS_INTERNAL_ERROR;\n                }\n\n                ExInitializeResourceLite(&c->lock);\n                ExInitializeResourceLite(&c->changed_extents_lock);\n\n                InitializeListHead(&c->space);\n                InitializeListHead(&c->space_size);\n                InitializeListHead(&c->deleting);\n                InitializeListHead(&c->changed_extents);\n\n                InitializeListHead(&c->range_locks);\n                ExInitializeResourceLite(&c->range_locks_lock);\n                KeInitializeEvent(&c->range_locks_event, NotificationEvent, false);\n\n                InitializeListHead(&c->partial_stripes);\n                ExInitializeResourceLite(&c->partial_stripes_lock);\n\n                c->last_alloc_set = false;\n\n                c->last_stripe = 0;\n\n                InsertTailList(&Vcb->chunks, &c->list_entry);\n\n                c->list_entry_balance.Flink = NULL;\n            }\n        }\n\n        b = find_next_item(Vcb, &tp, &next_tp, false, Irp);\n\n        if (b)\n            tp = next_tp;\n    } while (b);\n\n    Vcb->log_to_phys_loaded = true;\n\n    if (Vcb->data_flags == 0)\n        Vcb->data_flags = BLOCK_FLAG_DATA | (Vcb->superblock.num_devices > 1 ? BLOCK_FLAG_RAID0 : 0);\n\n    if (Vcb->metadata_flags == 0)\n        Vcb->metadata_flags = BLOCK_FLAG_METADATA | (Vcb->superblock.num_devices > 1 ? BLOCK_FLAG_RAID1 : BLOCK_FLAG_DUPLICATE);\n\n    if (Vcb->system_flags == 0)\n        Vcb->system_flags = BLOCK_FLAG_SYSTEM | (Vcb->superblock.num_devices > 1 ? BLOCK_FLAG_RAID1 : BLOCK_FLAG_DUPLICATE);\n\n    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS) {\n        Vcb->metadata_flags |= BLOCK_FLAG_DATA;\n        Vcb->data_flags = Vcb->metadata_flags;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nvoid protect_superblocks(_Inout_ chunk* c) {\n    uint16_t i = 0, j;\n    uint64_t off_start, off_end;\n\n    // The Linux driver also protects all the space before the first superblock.\n    // I realize this confuses physical and logical addresses, but this is what btrfs-progs does -\n    // evidently Linux assumes the chunk at 0 is always SINGLE.\n    if (c->offset < superblock_addrs[0])\n        space_list_subtract(c, c->offset, superblock_addrs[0] - c->offset, NULL);\n\n    while (superblock_addrs[i] != 0) {\n        CHUNK_ITEM* ci = c->chunk_item;\n        CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1];\n\n        if (ci->type & BLOCK_FLAG_RAID0 || ci->type & BLOCK_FLAG_RAID10) {\n            for (j = 0; j < ci->num_stripes; j++) {\n                uint16_t sub_stripes = max(ci->sub_stripes, 1);\n\n                if (cis[j].offset + (ci->size * ci->num_stripes / sub_stripes) > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {\n#ifdef _DEBUG\n                    uint64_t startoff;\n                    uint16_t startoffstripe;\n#endif\n\n                    TRACE(\"cut out superblock in chunk %I64x\\n\", c->offset);\n\n                    off_start = superblock_addrs[i] - cis[j].offset;\n                    off_start -= off_start % ci->stripe_length;\n                    off_start *= ci->num_stripes / sub_stripes;\n                    off_start += (j / sub_stripes) * ci->stripe_length;\n\n                    off_end = off_start + ci->stripe_length;\n\n#ifdef _DEBUG\n                    get_raid0_offset(off_start, ci->stripe_length, ci->num_stripes / sub_stripes, &startoff, &startoffstripe);\n                    TRACE(\"j = %u, startoffstripe = %u\\n\", j, startoffstripe);\n                    TRACE(\"startoff = %I64x, superblock = %I64x\\n\", startoff + cis[j].offset, superblock_addrs[i]);\n#endif\n\n                    space_list_subtract(c, c->offset + off_start, off_end - off_start, NULL);\n                }\n            }\n        } else if (ci->type & BLOCK_FLAG_RAID5) {\n            uint64_t stripe_size = ci->size / (ci->num_stripes - 1);\n\n            for (j = 0; j < ci->num_stripes; j++) {\n                if (cis[j].offset + stripe_size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {\n                    TRACE(\"cut out superblock in chunk %I64x\\n\", c->offset);\n\n                    off_start = superblock_addrs[i] - cis[j].offset;\n                    off_start -= off_start % ci->stripe_length;\n                    off_start *= ci->num_stripes - 1;\n\n                    off_end = sector_align(superblock_addrs[i] - cis[j].offset + sizeof(superblock), ci->stripe_length);\n                    off_end *= ci->num_stripes - 1;\n\n                    TRACE(\"cutting out %I64x, size %I64x\\n\", c->offset + off_start, off_end - off_start);\n\n                    space_list_subtract(c, c->offset + off_start, off_end - off_start, NULL);\n                }\n            }\n        } else if (ci->type & BLOCK_FLAG_RAID6) {\n            uint64_t stripe_size = ci->size / (ci->num_stripes - 2);\n\n            for (j = 0; j < ci->num_stripes; j++) {\n                if (cis[j].offset + stripe_size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {\n                    TRACE(\"cut out superblock in chunk %I64x\\n\", c->offset);\n\n                    off_start = superblock_addrs[i] - cis[j].offset;\n                    off_start -= off_start % ci->stripe_length;\n                    off_start *= ci->num_stripes - 2;\n\n                    off_end = sector_align(superblock_addrs[i] - cis[j].offset + sizeof(superblock), ci->stripe_length);\n                    off_end *= ci->num_stripes - 2;\n\n                    TRACE(\"cutting out %I64x, size %I64x\\n\", c->offset + off_start, off_end - off_start);\n\n                    space_list_subtract(c, c->offset + off_start, off_end - off_start, NULL);\n                }\n            }\n        } else { // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4\n            for (j = 0; j < ci->num_stripes; j++) {\n                if (cis[j].offset + ci->size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {\n                    TRACE(\"cut out superblock in chunk %I64x\\n\", c->offset);\n\n                    // The Linux driver protects the whole stripe in which the superblock lives\n\n                    off_start = ((superblock_addrs[i] - cis[j].offset) / c->chunk_item->stripe_length) * c->chunk_item->stripe_length;\n                    off_end = sector_align(superblock_addrs[i] - cis[j].offset + sizeof(superblock), c->chunk_item->stripe_length);\n\n                    space_list_subtract(c, c->offset + off_start, off_end - off_start, NULL);\n                }\n            }\n        }\n\n        i++;\n    }\n}\n\nNTSTATUS find_chunk_usage(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp) {\n    LIST_ENTRY* le = Vcb->chunks.Flink;\n    chunk* c;\n    KEY searchkey;\n    traverse_ptr tp;\n    BLOCK_GROUP_ITEM* bgi;\n    NTSTATUS Status;\n    root* r;\n\n    if (Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE) {\n        r = Vcb->block_group_root;\n\n        if (!r) {\n            ERR(\"block_group_tree compat_ro flag set but no block group root found\\n\");\n            return STATUS_INVALID_PARAMETER;\n        }\n    } else\n        r = Vcb->extent_root;\n\n    searchkey.obj_type = TYPE_BLOCK_GROUP_ITEM;\n\n    Vcb->superblock.bytes_used = 0;\n\n    while (le != &Vcb->chunks) {\n        c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        searchkey.obj_id = c->offset;\n        searchkey.offset = c->chunk_item->size;\n\n        Status = find_item(Vcb, r, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (!keycmp(searchkey, tp.item->key)) {\n            if (tp.item->size >= sizeof(BLOCK_GROUP_ITEM)) {\n                bgi = (BLOCK_GROUP_ITEM*)tp.item->data;\n\n                c->used = c->oldused = bgi->used;\n\n                TRACE(\"chunk %I64x has %I64x bytes used\\n\", c->offset, c->used);\n\n                Vcb->superblock.bytes_used += bgi->used;\n            } else {\n                ERR(\"(%I64x;%I64x,%x,%I64x) is %u bytes, expected %Iu\\n\",\n                    r->id, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(BLOCK_GROUP_ITEM));\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    Vcb->chunk_usage_found = true;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS load_sys_chunks(_In_ device_extension* Vcb) {\n    KEY key;\n    ULONG n = Vcb->superblock.n;\n\n    while (n > 0) {\n        if (n > sizeof(KEY)) {\n            RtlCopyMemory(&key, &Vcb->superblock.sys_chunk_array[Vcb->superblock.n - n], sizeof(KEY));\n            n -= sizeof(KEY);\n        } else\n            return STATUS_SUCCESS;\n\n        TRACE(\"bootstrap: %I64x,%x,%I64x\\n\", key.obj_id, key.obj_type, key.offset);\n\n        if (key.obj_type == TYPE_CHUNK_ITEM) {\n            CHUNK_ITEM* ci;\n            USHORT cisize;\n            sys_chunk* sc;\n\n            if (n < sizeof(CHUNK_ITEM))\n                return STATUS_SUCCESS;\n\n            ci = (CHUNK_ITEM*)&Vcb->superblock.sys_chunk_array[Vcb->superblock.n - n];\n            cisize = sizeof(CHUNK_ITEM) + (ci->num_stripes * sizeof(CHUNK_ITEM_STRIPE));\n\n            if (n < cisize)\n                return STATUS_SUCCESS;\n\n            sc = ExAllocatePoolWithTag(PagedPool, sizeof(sys_chunk), ALLOC_TAG);\n\n            if (!sc) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            sc->key = key;\n            sc->size = cisize;\n            sc->data = ExAllocatePoolWithTag(PagedPool, sc->size, ALLOC_TAG);\n\n            if (!sc->data) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(sc);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(sc->data, ci, sc->size);\n            InsertTailList(&Vcb->sys_chunks, &sc->list_entry);\n\n            n -= cisize;\n        } else {\n            ERR(\"unexpected item %I64x,%x,%I64x in bootstrap\\n\", key.obj_id, key.obj_type, key.offset);\n            return STATUS_INTERNAL_ERROR;\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\n_Ret_maybenull_\nroot* find_default_subvol(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp) {\n    LIST_ENTRY* le;\n\n    static const char fn[] = \"default\";\n    static uint32_t crc32 = 0x8dbfc2d2;\n\n    if (Vcb->options.subvol_id != 0) {\n        le = Vcb->roots.Flink;\n        while (le != &Vcb->roots) {\n            root* r = CONTAINING_RECORD(le, root, list_entry);\n\n            if (r->id == Vcb->options.subvol_id)\n                return r;\n\n            le = le->Flink;\n        }\n    }\n\n    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_DEFAULT_SUBVOL) {\n        NTSTATUS Status;\n        KEY searchkey;\n        traverse_ptr tp;\n        DIR_ITEM* di;\n\n        searchkey.obj_id = Vcb->superblock.root_dir_objectid;\n        searchkey.obj_type = TYPE_DIR_ITEM;\n        searchkey.offset = crc32;\n\n        Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        if (keycmp(tp.item->key, searchkey)) {\n            ERR(\"could not find (%I64x,%x,%I64x) in root tree\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n            goto end;\n        }\n\n        if (tp.item->size < sizeof(DIR_ITEM)) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM));\n            goto end;\n        }\n\n        di = (DIR_ITEM*)tp.item->data;\n\n        if (tp.item->size < sizeof(DIR_ITEM) - 1 + di->n) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM) - 1 + di->n);\n            goto end;\n        }\n\n        if (di->n != strlen(fn) || RtlCompareMemory(di->name, fn, di->n) != di->n) {\n            ERR(\"root DIR_ITEM had same CRC32, but was not \\\"default\\\"\\n\");\n            goto end;\n        }\n\n        if (di->key.obj_type != TYPE_ROOT_ITEM) {\n            ERR(\"default root has key (%I64x,%x,%I64x), expected subvolume\\n\", di->key.obj_id, di->key.obj_type, di->key.offset);\n            goto end;\n        }\n\n        le = Vcb->roots.Flink;\n        while (le != &Vcb->roots) {\n            root* r = CONTAINING_RECORD(le, root, list_entry);\n\n            if (r->id == di->key.obj_id)\n                return r;\n\n            le = le->Flink;\n        }\n\n        ERR(\"could not find root %I64x, using default instead\\n\", di->key.obj_id);\n    }\n\nend:\n    le = Vcb->roots.Flink;\n    while (le != &Vcb->roots) {\n        root* r = CONTAINING_RECORD(le, root, list_entry);\n\n        if (r->id == BTRFS_ROOT_FSTREE)\n            return r;\n\n        le = le->Flink;\n    }\n\n    return NULL;\n}\n\nvoid init_file_cache(_In_ PFILE_OBJECT FileObject, _In_ CC_FILE_SIZES* ccfs) {\n    TRACE(\"(%p, %p)\\n\", FileObject, ccfs);\n\n    CcInitializeCacheMap(FileObject, ccfs, false, &cache_callbacks, FileObject);\n\n    if (diskacc)\n        fCcSetAdditionalCacheAttributesEx(FileObject, CC_ENABLE_DISK_IO_ACCOUNTING);\n\n    CcSetReadAheadGranularity(FileObject, READ_AHEAD_GRANULARITY);\n}\n\nuint32_t get_num_of_processors() {\n    KAFFINITY p = KeQueryActiveProcessors();\n    uint32_t r = 0;\n\n    while (p != 0) {\n        if (p & 1)\n            r++;\n\n        p >>= 1;\n    }\n\n    return r;\n}\n\nstatic NTSTATUS create_calc_threads(_In_ PDEVICE_OBJECT DeviceObject) {\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    OBJECT_ATTRIBUTES oa;\n    ULONG i;\n\n    Vcb->calcthreads.num_threads = get_num_of_processors();\n\n    Vcb->calcthreads.threads = ExAllocatePoolWithTag(NonPagedPool, sizeof(drv_calc_thread) * Vcb->calcthreads.num_threads, ALLOC_TAG);\n    if (!Vcb->calcthreads.threads) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    InitializeListHead(&Vcb->calcthreads.job_list);\n    KeInitializeSpinLock(&Vcb->calcthreads.spinlock);\n    KeInitializeEvent(&Vcb->calcthreads.event, NotificationEvent, false);\n\n    RtlZeroMemory(Vcb->calcthreads.threads, sizeof(drv_calc_thread) * Vcb->calcthreads.num_threads);\n\n    InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    for (i = 0; i < Vcb->calcthreads.num_threads; i++) {\n        NTSTATUS Status;\n\n        Vcb->calcthreads.threads[i].DeviceObject = DeviceObject;\n        Vcb->calcthreads.threads[i].number = i;\n        KeInitializeEvent(&Vcb->calcthreads.threads[i].finished, NotificationEvent, false);\n\n        Status = PsCreateSystemThread(&Vcb->calcthreads.threads[i].handle, 0, &oa, NULL, NULL, calc_thread, &Vcb->calcthreads.threads[i]);\n        if (!NT_SUCCESS(Status)) {\n            ULONG j;\n\n            ERR(\"PsCreateSystemThread returned %08lx\\n\", Status);\n\n            for (j = 0; j < i; j++) {\n                Vcb->calcthreads.threads[i].quit = true;\n            }\n\n            KeSetEvent(&Vcb->calcthreads.event, 0, false);\n\n            return Status;\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic bool is_btrfs_volume(_In_ PDEVICE_OBJECT DeviceObject) {\n    NTSTATUS Status;\n    MOUNTDEV_NAME mdn, *mdn2;\n    ULONG mdnsize;\n\n    Status = dev_ioctl(DeviceObject, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL);\n    if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {\n        ERR(\"IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\\n\", Status);\n        return false;\n    }\n\n    mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength;\n\n    mdn2 = ExAllocatePoolWithTag(PagedPool, mdnsize, ALLOC_TAG);\n    if (!mdn2) {\n        ERR(\"out of memory\\n\");\n        return false;\n    }\n\n    Status = dev_ioctl(DeviceObject, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, true, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\\n\", Status);\n        ExFreePool(mdn2);\n        return false;\n    }\n\n    if (mdn2->NameLength > (sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)) &&\n        RtlCompareMemory(mdn2->Name, BTRFS_VOLUME_PREFIX, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)) == sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)) {\n        ExFreePool(mdn2);\n        return true;\n    }\n\n    ExFreePool(mdn2);\n\n    return false;\n}\n\nstatic NTSTATUS get_device_pnp_name_guid(_In_ PDEVICE_OBJECT DeviceObject, _Out_ PUNICODE_STRING pnp_name, _In_ const GUID* guid) {\n    NTSTATUS Status;\n    WCHAR *list = NULL, *s;\n\n    Status = IoGetDeviceInterfaces((PVOID)guid, NULL, 0, &list);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoGetDeviceInterfaces returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    s = list;\n    while (s[0] != 0) {\n        PFILE_OBJECT FileObject;\n        PDEVICE_OBJECT devobj;\n        UNICODE_STRING name;\n\n        name.Length = name.MaximumLength = (USHORT)wcslen(s) * sizeof(WCHAR);\n        name.Buffer = s;\n\n        if (NT_SUCCESS(IoGetDeviceObjectPointer(&name, FILE_READ_ATTRIBUTES, &FileObject, &devobj))) {\n            if (DeviceObject == devobj || DeviceObject == FileObject->DeviceObject) {\n                ObDereferenceObject(FileObject);\n\n                pnp_name->Buffer = ExAllocatePoolWithTag(PagedPool, name.Length, ALLOC_TAG);\n                if (!pnp_name->Buffer) {\n                    ERR(\"out of memory\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto end;\n                }\n\n                RtlCopyMemory(pnp_name->Buffer, name.Buffer, name.Length);\n                pnp_name->Length = pnp_name->MaximumLength = name.Length;\n\n                Status = STATUS_SUCCESS;\n                goto end;\n            }\n\n            ObDereferenceObject(FileObject);\n        }\n\n        s = &s[wcslen(s) + 1];\n    }\n\n    pnp_name->Length = pnp_name->MaximumLength = 0;\n    pnp_name->Buffer = 0;\n\n    Status = STATUS_NOT_FOUND;\n\nend:\n    if (list)\n        ExFreePool(list);\n\n    return Status;\n}\n\nNTSTATUS get_device_pnp_name(_In_ PDEVICE_OBJECT DeviceObject, _Out_ PUNICODE_STRING pnp_name, _Out_ const GUID** guid) {\n    NTSTATUS Status;\n\n    Status = get_device_pnp_name_guid(DeviceObject, pnp_name, &GUID_DEVINTERFACE_VOLUME);\n    if (NT_SUCCESS(Status)) {\n        *guid = &GUID_DEVINTERFACE_VOLUME;\n        return Status;\n    }\n\n    Status = get_device_pnp_name_guid(DeviceObject, pnp_name, &GUID_DEVINTERFACE_HIDDEN_VOLUME);\n    if (NT_SUCCESS(Status)) {\n        *guid = &GUID_DEVINTERFACE_HIDDEN_VOLUME;\n        return Status;\n    }\n\n    Status = get_device_pnp_name_guid(DeviceObject, pnp_name, &GUID_DEVINTERFACE_DISK);\n    if (NT_SUCCESS(Status)) {\n        *guid = &GUID_DEVINTERFACE_DISK;\n        return Status;\n    }\n\n    return STATUS_NOT_FOUND;\n}\n\n_Success_(return>=0)\nstatic NTSTATUS check_mount_device(_In_ PDEVICE_OBJECT DeviceObject, _Out_ bool* pno_pnp) {\n    NTSTATUS Status;\n    ULONG to_read;\n    superblock* sb;\n    UNICODE_STRING pnp_name;\n    const GUID* guid;\n\n    to_read = DeviceObject->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), DeviceObject->SectorSize);\n\n    sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG);\n    if (!sb) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    Status = sync_read_phys(DeviceObject, NULL, superblock_addrs[0], to_read, (PUCHAR)sb, true);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"sync_read_phys returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (sb->magic != BTRFS_MAGIC) {\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    if (!check_superblock_checksum(sb)) {\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    DeviceObject->Flags &= ~DO_VERIFY_VOLUME;\n\n    pnp_name.Buffer = NULL;\n\n    Status = get_device_pnp_name(DeviceObject, &pnp_name, &guid);\n    if (!NT_SUCCESS(Status)) {\n        WARN(\"get_device_pnp_name returned %08lx\\n\", Status);\n        pnp_name.Length = 0;\n    }\n\n    *pno_pnp = pnp_name.Length == 0;\n\n    if (pnp_name.Buffer)\n        ExFreePool(pnp_name.Buffer);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExFreePool(sb);\n\n    return Status;\n}\n\nstatic bool still_has_superblock(_In_ PDEVICE_OBJECT device, _In_ PFILE_OBJECT fileobj) {\n    NTSTATUS Status;\n    ULONG to_read;\n    superblock* sb;\n\n    if (!device)\n        return false;\n\n    to_read = device->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), device->SectorSize);\n\n    sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG);\n    if (!sb) {\n        ERR(\"out of memory\\n\");\n        return false;\n    }\n\n    Status = sync_read_phys(device, fileobj, superblock_addrs[0], to_read, (PUCHAR)sb, true);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"Failed to read superblock: %08lx\\n\", Status);\n        ExFreePool(sb);\n        return false;\n    }\n\n    if (sb->magic != BTRFS_MAGIC) {\n        TRACE(\"not a BTRFS volume\\n\");\n        ExFreePool(sb);\n        return false;\n    } else {\n        if (!check_superblock_checksum(sb)) {\n            ExFreePool(sb);\n            return false;\n        }\n    }\n\n    ObReferenceObject(device);\n\n    while (device) {\n        PDEVICE_OBJECT device2 = IoGetLowerDeviceObject(device);\n\n        device->Flags &= ~DO_VERIFY_VOLUME;\n\n        ObDereferenceObject(device);\n\n        device = device2;\n    }\n\n    ExFreePool(sb);\n\n    return true;\n}\n\nstatic void calculate_sector_shift(device_extension* Vcb) {\n    uint32_t ss = Vcb->superblock.sector_size;\n\n    Vcb->sector_shift = 0;\n\n    while (!(ss & 1)) {\n        Vcb->sector_shift++;\n        ss >>= 1;\n    }\n}\n\nstatic NTSTATUS mount_vol(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp;\n    PDEVICE_OBJECT NewDeviceObject = NULL;\n    PDEVICE_OBJECT DeviceToMount, readobj;\n    PFILE_OBJECT fileobj;\n    NTSTATUS Status;\n    device_extension* Vcb = NULL;\n    LIST_ENTRY *le, batchlist;\n    KEY searchkey;\n    traverse_ptr tp;\n    fcb* root_fcb = NULL;\n    ccb* root_ccb = NULL;\n    bool init_lookaside = false;\n    device* dev;\n    volume_device_extension* vde = NULL;\n    pdo_device_extension* pdode = NULL;\n    volume_child* vc;\n    uint64_t readobjsize;\n    OBJECT_ATTRIBUTES oa;\n    device_extension* real_devext;\n    KIRQL irql;\n\n    TRACE(\"(%p, %p)\\n\", DeviceObject, Irp);\n\n    if (DeviceObject != master_devobj)\n        return STATUS_INVALID_DEVICE_REQUEST;\n\n    IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    DeviceToMount = IrpSp->Parameters.MountVolume.DeviceObject;\n\n    real_devext = IrpSp->Parameters.MountVolume.Vpb->RealDevice->DeviceExtension;\n\n    // Make sure we're not trying to mount the PDO\n    if (IrpSp->Parameters.MountVolume.Vpb->RealDevice->DriverObject == drvobj && real_devext->type == VCB_TYPE_PDO)\n        return STATUS_UNRECOGNIZED_VOLUME;\n\n    if (!is_btrfs_volume(DeviceToMount)) {\n        bool not_pnp = false;\n\n        Status = check_mount_device(DeviceToMount, &not_pnp);\n        if (!NT_SUCCESS(Status))\n            WARN(\"check_mount_device returned %08lx\\n\", Status);\n\n        if (!not_pnp) {\n            Status = STATUS_UNRECOGNIZED_VOLUME;\n            goto exit;\n        }\n    } else {\n        PDEVICE_OBJECT pdo;\n\n        pdo = DeviceToMount;\n\n        ObReferenceObject(pdo);\n\n        while (true) {\n            PDEVICE_OBJECT pdo2 = IoGetLowerDeviceObject(pdo);\n\n            ObDereferenceObject(pdo);\n\n            if (!pdo2)\n                break;\n            else\n                pdo = pdo2;\n        }\n\n        ExAcquireResourceSharedLite(&pdo_list_lock, true);\n\n        le = pdo_list.Flink;\n        while (le != &pdo_list) {\n            pdo_device_extension* pdode2 = CONTAINING_RECORD(le, pdo_device_extension, list_entry);\n\n            if (pdode2->pdo == pdo) {\n                vde = pdode2->vde;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        ExReleaseResourceLite(&pdo_list_lock);\n\n        if (!vde || vde->type != VCB_TYPE_VOLUME) {\n            vde = NULL;\n            Status = STATUS_UNRECOGNIZED_VOLUME;\n            goto exit;\n        }\n    }\n\n    if (vde) {\n        pdode = vde->pdode;\n\n        ExAcquireResourceExclusiveLite(&pdode->child_lock, true);\n\n        le = pdode->children.Flink;\n        while (le != &pdode->children) {\n            LIST_ENTRY* le2 = le->Flink;\n\n            vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry);\n\n            if (!still_has_superblock(vc->devobj, vc->fileobj)) {\n                remove_volume_child(vde, vc, false);\n\n                if (pdode->num_children == 0) {\n                    ERR(\"error - number of devices is zero\\n\");\n                    Status = STATUS_INTERNAL_ERROR;\n                    ExReleaseResourceLite(&pdode->child_lock);\n                    goto exit;\n                }\n\n                Status = STATUS_DEVICE_NOT_READY;\n                ExReleaseResourceLite(&pdode->child_lock);\n                goto exit;\n            }\n\n            le = le2;\n        }\n\n        if (pdode->num_children == 0 || pdode->children_loaded == 0) {\n            ERR(\"error - number of devices is zero\\n\");\n            Status = STATUS_INTERNAL_ERROR;\n            ExReleaseResourceLite(&pdode->child_lock);\n            goto exit;\n        }\n\n        ExConvertExclusiveToSharedLite(&pdode->child_lock);\n\n        vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry);\n\n        readobj = vc->devobj;\n        fileobj = vc->fileobj;\n        readobjsize = vc->size;\n\n        vde->device->Characteristics &= ~FILE_DEVICE_SECURE_OPEN;\n    } else {\n        GET_LENGTH_INFORMATION gli;\n\n        vc = NULL;\n        readobj = DeviceToMount;\n        fileobj = NULL;\n\n        Status = dev_ioctl(readobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0,\n                           &gli, sizeof(gli), true, NULL);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error reading length information: %08lx\\n\", Status);\n            goto exit;\n        }\n\n        readobjsize = gli.Length.QuadPart;\n    }\n\n    Status = IoCreateDevice(drvobj, sizeof(device_extension), NULL, FILE_DEVICE_DISK_FILE_SYSTEM, 0, false, &NewDeviceObject);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoCreateDevice returned %08lx\\n\", Status);\n        Status = STATUS_UNRECOGNIZED_VOLUME;\n\n        if (pdode)\n            ExReleaseResourceLite(&pdode->child_lock);\n\n        goto exit;\n    }\n\n    NewDeviceObject->Flags |= DO_DIRECT_IO;\n\n    // Some programs seem to expect that the sector size will be 512, for\n    // FILE_NO_INTERMEDIATE_BUFFERING and the like.\n    NewDeviceObject->SectorSize = min(DeviceToMount->SectorSize, 512);\n\n    Vcb = (PVOID)NewDeviceObject->DeviceExtension;\n    RtlZeroMemory(Vcb, sizeof(device_extension));\n    Vcb->type = VCB_TYPE_FS;\n    Vcb->vde = vde;\n\n    ExInitializeResourceLite(&Vcb->tree_lock);\n    Vcb->need_write = false;\n\n    ExInitializeResourceLite(&Vcb->fcb_lock);\n    ExInitializeResourceLite(&Vcb->fileref_lock);\n    ExInitializeResourceLite(&Vcb->chunk_lock);\n    ExInitializeResourceLite(&Vcb->dirty_fcbs_lock);\n    ExInitializeResourceLite(&Vcb->dirty_filerefs_lock);\n    ExInitializeResourceLite(&Vcb->dirty_subvols_lock);\n    ExInitializeResourceLite(&Vcb->scrub.stats_lock);\n\n    ExInitializeResourceLite(&Vcb->load_lock);\n    ExAcquireResourceExclusiveLite(&Vcb->load_lock, true);\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    DeviceToMount->Flags |= DO_DIRECT_IO;\n\n    Status = read_superblock(Vcb, readobj, fileobj, readobjsize);\n    if (!NT_SUCCESS(Status)) {\n        if (!IoIsErrorUserInduced(Status))\n            Status = STATUS_UNRECOGNIZED_VOLUME;\n        else if (Irp->Tail.Overlay.Thread)\n            IoSetHardErrorOrVerifyDevice(Irp, readobj);\n\n        if (pdode)\n            ExReleaseResourceLite(&pdode->child_lock);\n\n        goto exit;\n    }\n\n    if (!vde && Vcb->superblock.num_devices > 1) {\n        ERR(\"cannot mount multi-device FS with non-PNP device\\n\");\n        Status = STATUS_UNRECOGNIZED_VOLUME;\n        goto exit;\n    }\n\n    Status = registry_load_volume_options(Vcb);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"registry_load_volume_options returned %08lx\\n\", Status);\n\n        if (pdode)\n            ExReleaseResourceLite(&pdode->child_lock);\n\n        goto exit;\n    }\n\n    if (pdode) {\n        if (RtlCompareMemory(&boot_uuid, &pdode->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID) && boot_subvol != 0)\n            Vcb->options.subvol_id = boot_subvol;\n\n        if (pdode->children_loaded < pdode->num_children && (!Vcb->options.allow_degraded || !finished_probing || degraded_wait)) {\n            ERR(\"could not mount as %I64u device(s) missing\\n\", pdode->num_children - pdode->children_loaded);\n            Status = STATUS_DEVICE_NOT_READY;\n            ExReleaseResourceLite(&pdode->child_lock);\n            goto exit;\n        }\n\n        // Windows holds DeviceObject->DeviceLock, guaranteeing that mount_vol is serialized\n        ExReleaseResourceLite(&pdode->child_lock);\n    }\n\n    if (Vcb->options.ignore) {\n        TRACE(\"ignoring volume\\n\");\n        Status = STATUS_UNRECOGNIZED_VOLUME;\n        goto exit;\n    }\n\n    if (Vcb->superblock.incompat_flags & ~INCOMPAT_SUPPORTED) {\n        WARN(\"cannot mount because of unsupported incompat flags (%I64x)\\n\", Vcb->superblock.incompat_flags & ~INCOMPAT_SUPPORTED);\n        Status = STATUS_UNRECOGNIZED_VOLUME;\n        goto exit;\n    }\n\n    if (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_METADATA_UUID))\n        Vcb->superblock.metadata_uuid = Vcb->superblock.uuid;\n\n    Vcb->readonly = false;\n    if (Vcb->superblock.compat_ro_flags & ~COMPAT_RO_SUPPORTED) {\n        WARN(\"mounting read-only because of unsupported flags (%I64x)\\n\", Vcb->superblock.compat_ro_flags & ~COMPAT_RO_SUPPORTED);\n        Vcb->readonly = true;\n    }\n\n    if (Vcb->options.readonly)\n        Vcb->readonly = true;\n\n    calculate_sector_shift(Vcb);\n\n    Vcb->superblock.generation++;\n    Vcb->superblock.incompat_flags |= BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF;\n\n    if (Vcb->superblock.log_tree_addr != 0) {\n        FIXME(\"FIXME - replay transaction log (clearing for now)\\n\");\n        Vcb->superblock.log_tree_addr = 0;\n    }\n\n    switch (Vcb->superblock.csum_type) {\n        case CSUM_TYPE_CRC32C:\n            Vcb->csum_size = sizeof(uint32_t);\n            break;\n\n        case CSUM_TYPE_XXHASH:\n            Vcb->csum_size = sizeof(uint64_t);\n            break;\n\n        case CSUM_TYPE_SHA256:\n            Vcb->csum_size = SHA256_HASH_SIZE;\n            break;\n\n        case CSUM_TYPE_BLAKE2:\n            Vcb->csum_size = BLAKE2_HASH_SIZE;\n            break;\n\n        default:\n            ERR(\"unrecognized csum type %x\\n\", Vcb->superblock.csum_type);\n            break;\n    }\n\n    InitializeListHead(&Vcb->devices);\n    dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG);\n    if (!dev) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    dev->devobj = readobj;\n    dev->fileobj = fileobj;\n    RtlCopyMemory(&dev->devitem, &Vcb->superblock.dev_item, sizeof(DEV_ITEM));\n\n    if (dev->devitem.num_bytes > readobjsize) {\n        WARN(\"device %I64x: DEV_ITEM says %I64x bytes, but Windows only reports %I64x\\n\", dev->devitem.dev_id,\n                dev->devitem.num_bytes, readobjsize);\n\n        dev->devitem.num_bytes = readobjsize;\n    }\n\n    dev->seeding = Vcb->superblock.flags & BTRFS_SUPERBLOCK_FLAGS_SEEDING ? true : false;\n\n    init_device(Vcb, dev, true);\n\n    InsertTailList(&Vcb->devices, &dev->list_entry);\n    Vcb->devices_loaded = 1;\n\n    if (DeviceToMount->Flags & DO_SYSTEM_BOOT_PARTITION)\n        Vcb->disallow_dismount = true;\n\n    TRACE(\"DeviceToMount = %p\\n\", DeviceToMount);\n    TRACE(\"IrpSp->Parameters.MountVolume.Vpb = %p\\n\", IrpSp->Parameters.MountVolume.Vpb);\n\n    NewDeviceObject->StackSize = DeviceToMount->StackSize + 1;\n    NewDeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;\n\n    InitializeListHead(&Vcb->roots);\n    InitializeListHead(&Vcb->drop_roots);\n\n    Vcb->log_to_phys_loaded = false;\n\n    add_root(Vcb, BTRFS_ROOT_CHUNK, Vcb->superblock.chunk_tree_addr, Vcb->superblock.chunk_root_generation, NULL);\n\n    if (!Vcb->chunk_root) {\n        ERR(\"Could not load chunk root.\\n\");\n        Status = STATUS_INTERNAL_ERROR;\n        goto exit;\n    }\n\n    InitializeListHead(&Vcb->sys_chunks);\n    Status = load_sys_chunks(Vcb);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"load_sys_chunks returned %08lx\\n\", Status);\n        goto exit;\n    }\n\n    InitializeListHead(&Vcb->chunks);\n    InitializeListHead(&Vcb->trees);\n    InitializeListHead(&Vcb->trees_hash);\n    InitializeListHead(&Vcb->all_fcbs);\n    InitializeListHead(&Vcb->dirty_fcbs);\n    InitializeListHead(&Vcb->dirty_filerefs);\n    InitializeListHead(&Vcb->dirty_subvols);\n    InitializeListHead(&Vcb->send_ops);\n\n    ExInitializeFastMutex(&Vcb->trees_list_mutex);\n\n    InitializeListHead(&Vcb->DirNotifyList);\n    InitializeListHead(&Vcb->scrub.errors);\n\n    FsRtlNotifyInitializeSync(&Vcb->NotifySync);\n\n    ExInitializePagedLookasideList(&Vcb->tree_data_lookaside, NULL, NULL, 0, sizeof(tree_data), ALLOC_TAG, 0);\n    ExInitializePagedLookasideList(&Vcb->traverse_ptr_lookaside, NULL, NULL, 0, sizeof(traverse_ptr), ALLOC_TAG, 0);\n    ExInitializePagedLookasideList(&Vcb->batch_item_lookaside, NULL, NULL, 0, sizeof(batch_item), ALLOC_TAG, 0);\n    ExInitializePagedLookasideList(&Vcb->fileref_lookaside, NULL, NULL, 0, sizeof(file_ref), ALLOC_TAG, 0);\n    ExInitializePagedLookasideList(&Vcb->fcb_lookaside, NULL, NULL, 0, sizeof(fcb), ALLOC_TAG, 0);\n    ExInitializePagedLookasideList(&Vcb->name_bit_lookaside, NULL, NULL, 0, sizeof(name_bit), ALLOC_TAG, 0);\n    ExInitializeNPagedLookasideList(&Vcb->range_lock_lookaside, NULL, NULL, 0, sizeof(range_lock), ALLOC_TAG, 0);\n    ExInitializeNPagedLookasideList(&Vcb->fcb_np_lookaside, NULL, NULL, 0, sizeof(fcb_nonpaged), ALLOC_TAG, 0);\n    init_lookaside = true;\n\n    Vcb->Vpb = IrpSp->Parameters.MountVolume.Vpb;\n\n    Status = load_chunk_root(Vcb, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"load_chunk_root returned %08lx\\n\", Status);\n        goto exit;\n    }\n\n    if (Vcb->superblock.num_devices > 1) {\n        if (Vcb->devices_loaded < Vcb->superblock.num_devices && (!Vcb->options.allow_degraded || !finished_probing)) {\n            ERR(\"could not mount as %I64u device(s) missing\\n\", Vcb->superblock.num_devices - Vcb->devices_loaded);\n\n            IoRaiseInformationalHardError(IO_ERR_INTERNAL_ERROR, NULL, NULL);\n\n            Status = STATUS_INTERNAL_ERROR;\n            goto exit;\n        }\n\n        if (dev->readonly && !Vcb->readonly) {\n            Vcb->readonly = true;\n\n            le = Vcb->devices.Flink;\n            while (le != &Vcb->devices) {\n                device* dev2 = CONTAINING_RECORD(le, device, list_entry);\n\n                if (dev2->readonly && !dev2->seeding)\n                    break;\n\n                if (!dev2->readonly) {\n                    Vcb->readonly = false;\n                    break;\n                }\n\n                le = le->Flink;\n            }\n\n            if (Vcb->readonly)\n                WARN(\"setting volume to readonly\\n\");\n        }\n    } else {\n        if (dev->readonly) {\n            WARN(\"setting volume to readonly as device is readonly\\n\");\n            Vcb->readonly = true;\n        }\n    }\n\n    add_root(Vcb, BTRFS_ROOT_ROOT, Vcb->superblock.root_tree_addr, Vcb->superblock.generation - 1, NULL);\n\n    if (!Vcb->root_root) {\n        ERR(\"Could not load root of roots.\\n\");\n        Status = STATUS_INTERNAL_ERROR;\n        goto exit;\n    }\n\n    Status = look_for_roots(Vcb, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"look_for_roots returned %08lx\\n\", Status);\n        goto exit;\n    }\n\n    if (!Vcb->readonly) {\n        Status = find_chunk_usage(Vcb, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_chunk_usage returned %08lx\\n\", Status);\n            goto exit;\n        }\n    }\n\n    InitializeListHead(&batchlist);\n\n    // We've already increased the generation by one\n    if (!Vcb->readonly && (\n        Vcb->options.clear_cache ||\n        (!(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE) && Vcb->superblock.generation - 1 != Vcb->superblock.cache_generation) ||\n        (Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE && !(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID)))) {\n        if (Vcb->options.clear_cache)\n            WARN(\"ClearCache option was set, clearing cache...\\n\");\n        else if (Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE && !(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID))\n            WARN(\"clearing free-space tree created by buggy Linux driver\\n\");\n        else\n            WARN(\"generation was %I64x, free-space cache generation was %I64x; clearing cache...\\n\", Vcb->superblock.generation - 1, Vcb->superblock.cache_generation);\n\n        Status = clear_free_space_cache(Vcb, &batchlist, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"clear_free_space_cache returned %08lx\\n\", Status);\n            clear_batch_list(Vcb, &batchlist);\n            goto exit;\n        }\n    }\n\n    Status = commit_batch_list(Vcb, &batchlist, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"commit_batch_list returned %08lx\\n\", Status);\n        goto exit;\n    }\n\n    Vcb->volume_fcb = create_fcb(Vcb, NonPagedPool);\n    if (!Vcb->volume_fcb) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    Vcb->volume_fcb->Vcb = Vcb;\n    Vcb->volume_fcb->sd = NULL;\n\n    Vcb->dummy_fcb = create_fcb(Vcb, NonPagedPool);\n    if (!Vcb->dummy_fcb) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    Vcb->dummy_fcb->Vcb = Vcb;\n    Vcb->dummy_fcb->type = BTRFS_TYPE_DIRECTORY;\n    Vcb->dummy_fcb->inode = 2;\n    Vcb->dummy_fcb->subvol = Vcb->root_root;\n    Vcb->dummy_fcb->atts = FILE_ATTRIBUTE_DIRECTORY;\n    Vcb->dummy_fcb->inode_item.st_nlink = 1;\n    Vcb->dummy_fcb->inode_item.st_mode = __S_IFDIR;\n\n    Vcb->dummy_fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);\n    if (!Vcb->dummy_fcb->hash_ptrs) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    RtlZeroMemory(Vcb->dummy_fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256);\n\n    Vcb->dummy_fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);\n    if (!Vcb->dummy_fcb->hash_ptrs_uc) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    RtlZeroMemory(Vcb->dummy_fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256);\n\n    root_fcb = create_fcb(Vcb, NonPagedPool);\n    if (!root_fcb) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    root_fcb->Vcb = Vcb;\n    root_fcb->inode = SUBVOL_ROOT_INODE;\n    root_fcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&root_fcb->inode, sizeof(uint64_t));\n    root_fcb->type = BTRFS_TYPE_DIRECTORY;\n\n#ifdef DEBUG_FCB_REFCOUNTS\n    WARN(\"volume FCB = %p\\n\", Vcb->volume_fcb);\n    WARN(\"root FCB = %p\\n\", root_fcb);\n#endif\n\n    root_fcb->subvol = find_default_subvol(Vcb, Irp);\n\n    if (!root_fcb->subvol) {\n        ERR(\"could not find top subvol\\n\");\n        Status = STATUS_INTERNAL_ERROR;\n        goto exit;\n    }\n\n    Status = load_dir_children(Vcb, root_fcb, true, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"load_dir_children returned %08lx\\n\", Status);\n        goto exit;\n    }\n\n    searchkey.obj_id = root_fcb->inode;\n    searchkey.obj_type = TYPE_INODE_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, root_fcb->subvol, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        goto exit;\n    }\n\n    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n        ERR(\"couldn't find INODE_ITEM for root directory\\n\");\n        Status = STATUS_INTERNAL_ERROR;\n        goto exit;\n    }\n\n    if (tp.item->size > 0)\n        RtlCopyMemory(&root_fcb->inode_item, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size));\n\n    fcb_get_sd(root_fcb, NULL, true, Irp);\n\n    root_fcb->atts = get_file_attributes(Vcb, root_fcb->subvol, root_fcb->inode, root_fcb->type, false, false, Irp);\n\n    if (root_fcb->subvol->id == BTRFS_ROOT_FSTREE)\n        root_fcb->atts &= ~FILE_ATTRIBUTE_HIDDEN;\n\n    Vcb->root_fileref = create_fileref(Vcb);\n    if (!Vcb->root_fileref) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    Vcb->root_fileref->fcb = root_fcb;\n    InsertTailList(&root_fcb->subvol->fcbs, &root_fcb->list_entry);\n    InsertTailList(&Vcb->all_fcbs, &root_fcb->list_entry_all);\n\n    root_fcb->subvol->fcbs_ptrs[root_fcb->hash >> 24] = &root_fcb->list_entry;\n\n    root_fcb->fileref = Vcb->root_fileref;\n\n    root_ccb = ExAllocatePoolWithTag(PagedPool, sizeof(ccb), ALLOC_TAG);\n    if (!root_ccb) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    Vcb->root_file = IoCreateStreamFileObject(NULL, DeviceToMount);\n    Vcb->root_file->FsContext = root_fcb;\n    Vcb->root_file->SectionObjectPointer = &root_fcb->nonpaged->segment_object;\n    Vcb->root_file->Vpb = DeviceObject->Vpb;\n\n    RtlZeroMemory(root_ccb, sizeof(ccb));\n    root_ccb->NodeType = BTRFS_NODE_TYPE_CCB;\n    root_ccb->NodeSize = sizeof(ccb);\n\n    Vcb->root_file->FsContext2 = root_ccb;\n\n    try {\n        CcInitializeCacheMap(Vcb->root_file, (PCC_FILE_SIZES)(&root_fcb->Header.AllocationSize), false, &cache_callbacks, Vcb->root_file);\n    } except (EXCEPTION_EXECUTE_HANDLER) {\n        Status = GetExceptionCode();\n        goto exit;\n    }\n\n    le = Vcb->devices.Flink;\n    while (le != &Vcb->devices) {\n        device* dev2 = CONTAINING_RECORD(le, device, list_entry);\n\n        Status = find_disk_holes(Vcb, dev2, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_disk_holes returned %08lx\\n\", Status);\n            goto exit;\n        }\n\n        le = le->Flink;\n    }\n\n    IoAcquireVpbSpinLock(&irql);\n\n    NewDeviceObject->Vpb = IrpSp->Parameters.MountVolume.Vpb;\n    IrpSp->Parameters.MountVolume.Vpb->DeviceObject = NewDeviceObject;\n    IrpSp->Parameters.MountVolume.Vpb->Flags |= VPB_MOUNTED;\n    NewDeviceObject->Vpb->VolumeLabelLength = 4; // FIXME\n    NewDeviceObject->Vpb->VolumeLabel[0] = '?';\n    NewDeviceObject->Vpb->VolumeLabel[1] = 0;\n    NewDeviceObject->Vpb->ReferenceCount++;\n\n    IoReleaseVpbSpinLock(irql);\n\n    KeInitializeEvent(&Vcb->flush_thread_finished, NotificationEvent, false);\n\n    InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = PsCreateSystemThread(&Vcb->flush_thread_handle, 0, &oa, NULL, NULL, flush_thread, NewDeviceObject);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"PsCreateSystemThread returned %08lx\\n\", Status);\n        goto exit;\n    }\n\n    Status = create_calc_threads(NewDeviceObject);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"create_calc_threads returned %08lx\\n\", Status);\n        goto exit;\n    }\n\n    Status = registry_mark_volume_mounted(&Vcb->superblock.uuid);\n    if (!NT_SUCCESS(Status))\n        WARN(\"registry_mark_volume_mounted returned %08lx\\n\", Status);\n\n    Status = look_for_balance_item(Vcb);\n    if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND)\n        WARN(\"look_for_balance_item returned %08lx\\n\", Status);\n\n    Status = STATUS_SUCCESS;\n\n    if (vde)\n        vde->mounted_device = NewDeviceObject;\n\n    Vcb->devobj = NewDeviceObject;\n\n    ExInitializeResourceLite(&Vcb->send_load_lock);\n\nexit:\n    if (Vcb) {\n        ExReleaseResourceLite(&Vcb->tree_lock);\n        ExReleaseResourceLite(&Vcb->load_lock);\n    }\n\n    if (!NT_SUCCESS(Status)) {\n        if (Vcb) {\n            if (init_lookaside) {\n                ExDeletePagedLookasideList(&Vcb->tree_data_lookaside);\n                ExDeletePagedLookasideList(&Vcb->traverse_ptr_lookaside);\n                ExDeletePagedLookasideList(&Vcb->batch_item_lookaside);\n                ExDeletePagedLookasideList(&Vcb->fileref_lookaside);\n                ExDeletePagedLookasideList(&Vcb->fcb_lookaside);\n                ExDeletePagedLookasideList(&Vcb->name_bit_lookaside);\n                ExDeleteNPagedLookasideList(&Vcb->range_lock_lookaside);\n                ExDeleteNPagedLookasideList(&Vcb->fcb_np_lookaside);\n            }\n\n            if (Vcb->root_file)\n                ObDereferenceObject(Vcb->root_file);\n            else if (Vcb->root_fileref)\n                free_fileref(Vcb->root_fileref);\n            else if (root_fcb)\n                free_fcb(root_fcb);\n\n            if (root_fcb && root_fcb->refcount == 0)\n                reap_fcb(root_fcb);\n\n            if (Vcb->volume_fcb)\n                reap_fcb(Vcb->volume_fcb);\n\n            ExDeleteResourceLite(&Vcb->tree_lock);\n            ExDeleteResourceLite(&Vcb->load_lock);\n            ExDeleteResourceLite(&Vcb->fcb_lock);\n            ExDeleteResourceLite(&Vcb->fileref_lock);\n            ExDeleteResourceLite(&Vcb->chunk_lock);\n            ExDeleteResourceLite(&Vcb->dirty_fcbs_lock);\n            ExDeleteResourceLite(&Vcb->dirty_filerefs_lock);\n            ExDeleteResourceLite(&Vcb->dirty_subvols_lock);\n            ExDeleteResourceLite(&Vcb->scrub.stats_lock);\n\n            if (Vcb->devices.Flink) {\n                while (!IsListEmpty(&Vcb->devices)) {\n                    device* dev2 = CONTAINING_RECORD(RemoveHeadList(&Vcb->devices), device, list_entry);\n\n                    ExFreePool(dev2);\n                }\n            }\n        }\n\n        if (NewDeviceObject)\n            IoDeleteDevice(NewDeviceObject);\n    } else {\n        ExAcquireResourceExclusiveLite(&global_loading_lock, true);\n        InsertTailList(&VcbList, &Vcb->list_entry);\n        ExReleaseResourceLite(&global_loading_lock);\n\n        FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_MOUNT);\n    }\n\n    TRACE(\"mount_vol done (status: %lx)\\n\", Status);\n\n    return Status;\n}\n\nstatic NTSTATUS verify_device(_In_ device_extension* Vcb, _Inout_ device* dev) {\n    NTSTATUS Status;\n    superblock* sb;\n    ULONG to_read, cc;\n\n    if (!dev->devobj)\n        return STATUS_WRONG_VOLUME;\n\n    if (dev->removable) {\n        IO_STATUS_BLOCK iosb;\n\n        Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, &cc, sizeof(ULONG), true, &iosb);\n\n        if (IoIsErrorUserInduced(Status)) {\n            ERR(\"IOCTL_STORAGE_CHECK_VERIFY returned %08lx (user-induced)\\n\", Status);\n\n            if (Vcb->vde) {\n                pdo_device_extension* pdode = Vcb->vde->pdode;\n                LIST_ENTRY* le2;\n                bool changed = false;\n\n                ExAcquireResourceExclusiveLite(&pdode->child_lock, true);\n\n                le2 = pdode->children.Flink;\n                while (le2 != &pdode->children) {\n                    volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);\n\n                    if (vc->devobj == dev->devobj) {\n                        TRACE(\"removing device\\n\");\n\n                        remove_volume_child(Vcb->vde, vc, true);\n                        changed = true;\n\n                        break;\n                    }\n\n                    le2 = le2->Flink;\n                }\n\n                if (!changed)\n                    ExReleaseResourceLite(&pdode->child_lock);\n            }\n        } else if (!NT_SUCCESS(Status)) {\n            ERR(\"IOCTL_STORAGE_CHECK_VERIFY returned %08lx\\n\", Status);\n            return Status;\n        } else if (iosb.Information < sizeof(ULONG)) {\n            ERR(\"iosb.Information was too short\\n\");\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        dev->change_count = cc;\n    }\n\n    to_read = dev->devobj->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), dev->devobj->SectorSize);\n\n    sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG);\n    if (!sb) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    Status = sync_read_phys(dev->devobj, dev->fileobj, superblock_addrs[0], to_read, (PUCHAR)sb, true);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"Failed to read superblock: %08lx\\n\", Status);\n        ExFreePool(sb);\n        return Status;\n    }\n\n    if (sb->magic != BTRFS_MAGIC) {\n        ERR(\"not a BTRFS volume\\n\");\n        ExFreePool(sb);\n        return STATUS_WRONG_VOLUME;\n    }\n\n    if (!check_superblock_checksum(sb)) {\n        ExFreePool(sb);\n        return STATUS_WRONG_VOLUME;\n    }\n\n    if (RtlCompareMemory(&sb->uuid, &Vcb->superblock.uuid, sizeof(BTRFS_UUID)) != sizeof(BTRFS_UUID)) {\n        ERR(\"different UUIDs\\n\");\n        ExFreePool(sb);\n        return STATUS_WRONG_VOLUME;\n    }\n\n    ExFreePool(sb);\n\n    dev->devobj->Flags &= ~DO_VERIFY_VOLUME;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS verify_volume(_In_ PDEVICE_OBJECT devobj) {\n    device_extension* Vcb = devobj->DeviceExtension;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    uint64_t failed_devices = 0;\n    bool locked = false, remove = false;\n\n    if (!(Vcb->Vpb->Flags & VPB_MOUNTED))\n        return STATUS_WRONG_VOLUME;\n\n    if (!ExIsResourceAcquiredExclusive(&Vcb->tree_lock)) {\n        ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n        locked = true;\n    }\n\n    if (Vcb->removing) {\n        if (locked) ExReleaseResourceLite(&Vcb->tree_lock);\n        return STATUS_WRONG_VOLUME;\n    }\n\n    Status = STATUS_SUCCESS;\n\n    InterlockedIncrement(&Vcb->open_files); // so pnp_surprise_removal doesn't uninit the device while we're still using it\n\n    le = Vcb->devices.Flink;\n    while (le != &Vcb->devices) {\n        device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n        Status = verify_device(Vcb, dev);\n        if (!NT_SUCCESS(Status)) {\n            failed_devices++;\n\n            if (dev->devobj && Vcb->options.allow_degraded)\n                dev->devobj = NULL;\n        }\n\n        le = le->Flink;\n    }\n\n    InterlockedDecrement(&Vcb->open_files);\n\n    if (Vcb->removing && Vcb->open_files == 0)\n        remove = true;\n\n    if (locked)\n        ExReleaseResourceLite(&Vcb->tree_lock);\n\n    if (remove) {\n        uninit(Vcb);\n        return Status;\n    }\n\n    if (failed_devices == 0 || (Vcb->options.allow_degraded && failed_devices < Vcb->superblock.num_devices)) {\n        Vcb->Vpb->RealDevice->Flags &= ~DO_VERIFY_VOLUME;\n\n        return STATUS_SUCCESS;\n    }\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_FILE_SYSTEM_CONTROL)\n_Function_class_(DRIVER_DISPATCH)\nstatic NTSTATUS __stdcall drv_file_system_control(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp;\n    NTSTATUS Status;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"file system control\\n\");\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    } else if (!Vcb || (Vcb->type != VCB_TYPE_FS && Vcb->type != VCB_TYPE_CONTROL)) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    Status = STATUS_NOT_IMPLEMENTED;\n\n    IrpSp = IoGetCurrentIrpStackLocation( Irp );\n\n    Irp->IoStatus.Information = 0;\n\n    switch (IrpSp->MinorFunction) {\n        case IRP_MN_MOUNT_VOLUME:\n            TRACE(\"IRP_MN_MOUNT_VOLUME\\n\");\n\n            Status = mount_vol(DeviceObject, Irp);\n            break;\n\n        case IRP_MN_KERNEL_CALL:\n            TRACE(\"IRP_MN_KERNEL_CALL\\n\");\n\n            Status = fsctl_request(DeviceObject, &Irp, IrpSp->Parameters.FileSystemControl.FsControlCode);\n            break;\n\n        case IRP_MN_USER_FS_REQUEST:\n            TRACE(\"IRP_MN_USER_FS_REQUEST\\n\");\n\n            Status = fsctl_request(DeviceObject, &Irp, IrpSp->Parameters.FileSystemControl.FsControlCode);\n            break;\n\n        case IRP_MN_VERIFY_VOLUME:\n            TRACE(\"IRP_MN_VERIFY_VOLUME\\n\");\n\n            Status = verify_volume(DeviceObject);\n\n            if (!NT_SUCCESS(Status) && Vcb->Vpb->Flags & VPB_MOUNTED) {\n                ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n                Vcb->removing = true;\n                ExReleaseResourceLite(&Vcb->tree_lock);\n            }\n\n            break;\n\n        default:\n            break;\n    }\n\nend:\n    TRACE(\"returning %08lx\\n\", Status);\n\n    if (Irp) {\n        Irp->IoStatus.Status = Status;\n\n        IoCompleteRequest(Irp, IO_NO_INCREMENT);\n    }\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_LOCK_CONTROL)\n_Function_class_(DRIVER_DISPATCH)\nstatic NTSTATUS __stdcall drv_lock_control(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {\n    NTSTATUS Status;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    fcb* fcb = IrpSp->FileObject ? IrpSp->FileObject->FsContext : NULL;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n\n        Irp->IoStatus.Status = Status;\n        IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\n        goto exit;\n    }\n\n    TRACE(\"lock control\\n\");\n\n    if (!fcb) {\n        ERR(\"fcb was NULL\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto exit;\n    }\n\n    FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL);\n\n    Status = FsRtlProcessFileLock(&fcb->lock, Irp, NULL);\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n\nexit:\n    TRACE(\"returning %08lx\\n\", Status);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\nvoid do_shutdown(PIRP Irp) {\n    LIST_ENTRY* le;\n    bus_device_extension* bde;\n\n    shutting_down = true;\n    KeSetEvent(&mountmgr_thread_event, 0, false);\n\n    le = VcbList.Flink;\n    while (le != &VcbList) {\n        LIST_ENTRY* le2 = le->Flink;\n\n        device_extension* Vcb = CONTAINING_RECORD(le, device_extension, list_entry);\n        volume_device_extension* vde = Vcb->vde;\n        PDEVICE_OBJECT devobj = vde ? vde->device : NULL;\n\n        TRACE(\"shutting down Vcb %p\\n\", Vcb);\n\n        if (vde)\n            InterlockedIncrement(&vde->open_count);\n\n        if (devobj)\n            ObReferenceObject(devobj);\n\n        dismount_volume(Vcb, true, Irp);\n\n        if (vde) {\n            NTSTATUS Status;\n            UNICODE_STRING mmdevpath;\n            PDEVICE_OBJECT mountmgr;\n            PFILE_OBJECT mountmgrfo;\n            KIRQL irql;\n            PVPB newvpb;\n\n            RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);\n            Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &mountmgrfo, &mountmgr);\n            if (!NT_SUCCESS(Status))\n                ERR(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n            else {\n                remove_drive_letter(mountmgr, &vde->name);\n\n                ObDereferenceObject(mountmgrfo);\n            }\n\n            vde->removing = true;\n\n            newvpb = ExAllocatePoolWithTag(NonPagedPool, sizeof(VPB), ALLOC_TAG);\n            if (!newvpb) {\n                ERR(\"out of memory\\n\");\n                return;\n            }\n\n            RtlZeroMemory(newvpb, sizeof(VPB));\n\n            newvpb->Type = IO_TYPE_VPB;\n            newvpb->Size = sizeof(VPB);\n            newvpb->RealDevice = newvpb->DeviceObject = vde->device;\n            newvpb->Flags = VPB_DIRECT_WRITES_ALLOWED;\n\n            IoAcquireVpbSpinLock(&irql);\n            vde->device->Vpb = newvpb;\n            IoReleaseVpbSpinLock(irql);\n\n            if (InterlockedDecrement(&vde->open_count) == 0)\n                free_vol(vde);\n        }\n\n        if (devobj)\n            ObDereferenceObject(devobj);\n\n        le = le2;\n    }\n\n#ifdef _DEBUG\n    if (comfo) {\n        ObDereferenceObject(comfo);\n        comdo = NULL;\n        comfo = NULL;\n    }\n#endif\n\n    IoUnregisterFileSystem(master_devobj);\n\n    if (notification_entry2) {\n        if (fIoUnregisterPlugPlayNotificationEx)\n            fIoUnregisterPlugPlayNotificationEx(notification_entry2);\n        else\n            IoUnregisterPlugPlayNotification(notification_entry2);\n\n        notification_entry2 = NULL;\n    }\n\n    if (notification_entry3) {\n        if (fIoUnregisterPlugPlayNotificationEx)\n            fIoUnregisterPlugPlayNotificationEx(notification_entry3);\n        else\n            IoUnregisterPlugPlayNotification(notification_entry3);\n\n        notification_entry3 = NULL;\n    }\n\n    if (notification_entry) {\n        if (fIoUnregisterPlugPlayNotificationEx)\n            fIoUnregisterPlugPlayNotificationEx(notification_entry);\n        else\n            IoUnregisterPlugPlayNotification(notification_entry);\n\n        notification_entry = NULL;\n    }\n\n    bde = busobj->DeviceExtension;\n\n    if (bde->attached_device)\n        IoDetachDevice(bde->attached_device);\n\n    IoDeleteDevice(busobj);\n    IoDeleteDevice(master_devobj);\n}\n\n_Dispatch_type_(IRP_MJ_SHUTDOWN)\n_Function_class_(DRIVER_DISPATCH)\nstatic NTSTATUS __stdcall drv_shutdown(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {\n    NTSTATUS Status;\n    bool top_level;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"shutdown\\n\");\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    }\n\n    Status = STATUS_SUCCESS;\n\n    do_shutdown(Irp);\n\nend:\n    Irp->IoStatus.Status = Status;\n    Irp->IoStatus.Information = 0;\n\n    IoCompleteRequest( Irp, IO_NO_INCREMENT );\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\nstatic bool device_still_valid(device* dev, uint64_t expected_generation) {\n    NTSTATUS Status;\n    unsigned int to_read;\n    superblock* sb;\n\n    to_read = (unsigned int)(dev->devobj->SectorSize == 0 ? sizeof(superblock) : sector_align(sizeof(superblock), dev->devobj->SectorSize));\n\n    sb = ExAllocatePoolWithTag(NonPagedPool, to_read, ALLOC_TAG);\n    if (!sb) {\n        ERR(\"out of memory\\n\");\n        return false;\n    }\n\n    Status = sync_read_phys(dev->devobj, dev->fileobj, superblock_addrs[0], to_read, (PUCHAR)sb, false);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"sync_read_phys returned %08lx\\n\", Status);\n        ExFreePool(sb);\n        return false;\n    }\n\n    if (sb->magic != BTRFS_MAGIC) {\n        ERR(\"magic not found\\n\");\n        ExFreePool(sb);\n        return false;\n    }\n\n    if (!check_superblock_checksum(sb)) {\n        ExFreePool(sb);\n        return false;\n    }\n\n    if (sb->generation > expected_generation) {\n        ERR(\"generation was %I64x, expected %I64x\\n\", sb->generation, expected_generation);\n        ExFreePool(sb);\n        return false;\n    }\n\n    ExFreePool(sb);\n\n    return true;\n}\n\n_Function_class_(IO_WORKITEM_ROUTINE)\nstatic void __stdcall check_after_wakeup(PDEVICE_OBJECT DeviceObject, PVOID con) {\n    device_extension* Vcb = (device_extension*)con;\n    LIST_ENTRY* le;\n\n    UNUSED(DeviceObject);\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    le = Vcb->devices.Flink;\n\n    // FIXME - do reads in parallel?\n\n    while (le != &Vcb->devices) {\n        device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n        if (dev->devobj) {\n            if (!device_still_valid(dev, Vcb->superblock.generation - 1)) {\n                PDEVICE_OBJECT voldev = Vcb->Vpb->RealDevice;\n                KIRQL irql;\n                PVPB newvpb;\n\n                WARN(\"forcing remount\\n\");\n\n                newvpb = ExAllocatePoolWithTag(NonPagedPool, sizeof(VPB), ALLOC_TAG);\n                if (!newvpb) {\n                    ERR(\"out of memory\\n\");\n                    return;\n                }\n\n                RtlZeroMemory(newvpb, sizeof(VPB));\n\n                newvpb->Type = IO_TYPE_VPB;\n                newvpb->Size = sizeof(VPB);\n                newvpb->RealDevice = voldev;\n                newvpb->Flags = VPB_DIRECT_WRITES_ALLOWED;\n\n                Vcb->removing = true;\n\n                IoAcquireVpbSpinLock(&irql);\n                voldev->Vpb = newvpb;\n                IoReleaseVpbSpinLock(irql);\n\n                Vcb->vde = NULL;\n\n                ExReleaseResourceLite(&Vcb->tree_lock);\n\n                if (Vcb->open_files == 0)\n                    uninit(Vcb);\n                else { // remove from VcbList\n                    ExAcquireResourceExclusiveLite(&global_loading_lock, true);\n                    RemoveEntryList(&Vcb->list_entry);\n                    Vcb->list_entry.Flink = NULL;\n                    ExReleaseResourceLite(&global_loading_lock);\n                }\n\n                return;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n}\n\n_Dispatch_type_(IRP_MJ_POWER)\n_Function_class_(DRIVER_DISPATCH)\nstatic NTSTATUS __stdcall drv_power(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {\n    NTSTATUS Status;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    bool top_level;\n\n    // no need for FsRtlEnterFileSystem, as this only ever gets called in a system thread\n\n    top_level = is_top_level(Irp);\n\n    Irp->IoStatus.Information = 0;\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        volume_device_extension* vde = DeviceObject->DeviceExtension;\n\n        if (IrpSp->MinorFunction == IRP_MN_QUERY_POWER && IrpSp->Parameters.Power.Type == SystemPowerState &&\n            IrpSp->Parameters.Power.State.SystemState != PowerSystemWorking && vde->mounted_device) {\n            device_extension* Vcb2 = vde->mounted_device->DeviceExtension;\n\n            /* If power state is about to go to sleep or hibernate, do a flush. We do this on IRP_MJ_QUERY_POWER\n            * rather than IRP_MJ_SET_POWER because we know that the hard disks are still awake. */\n\n            if (Vcb2) {\n                ExAcquireResourceExclusiveLite(&Vcb2->tree_lock, true);\n\n                if (Vcb2->need_write && !Vcb2->readonly) {\n                    TRACE(\"doing protective flush on power state change\\n\");\n                    Status = do_write(Vcb2, NULL);\n                } else\n                    Status = STATUS_SUCCESS;\n\n                free_trees(Vcb2);\n\n                if (!NT_SUCCESS(Status))\n                    ERR(\"do_write returned %08lx\\n\", Status);\n\n                ExReleaseResourceLite(&Vcb2->tree_lock);\n            }\n        } else if (IrpSp->MinorFunction == IRP_MN_SET_POWER && IrpSp->Parameters.Power.Type == SystemPowerState &&\n            IrpSp->Parameters.Power.State.SystemState == PowerSystemWorking && vde->mounted_device) {\n            device_extension* Vcb2 = vde->mounted_device->DeviceExtension;\n\n            /* If waking up, make sure that the FS hasn't been changed while we've been out (e.g., by dual-boot Linux) */\n\n            if (Vcb2) {\n                PIO_WORKITEM work_item;\n\n                work_item = IoAllocateWorkItem(DeviceObject);\n                if (!work_item) {\n                    ERR(\"out of memory\\n\");\n                } else\n                    IoQueueWorkItem(work_item, check_after_wakeup, DelayedWorkQueue, Vcb2);\n            }\n        }\n\n        PoStartNextPowerIrp(Irp);\n        IoSkipCurrentIrpStackLocation(Irp);\n        Status = PoCallDriver(vde->attached_device, Irp);\n\n        goto exit;\n    } else if (Vcb && Vcb->type == VCB_TYPE_FS) {\n        IoSkipCurrentIrpStackLocation(Irp);\n\n        Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp);\n\n        goto exit;\n    } else if (Vcb && Vcb->type == VCB_TYPE_BUS) {\n        bus_device_extension* bde = DeviceObject->DeviceExtension;\n\n        PoStartNextPowerIrp(Irp);\n        IoSkipCurrentIrpStackLocation(Irp);\n        Status = PoCallDriver(bde->attached_device, Irp);\n\n        goto exit;\n    }\n\n    if (IrpSp->MinorFunction == IRP_MN_SET_POWER || IrpSp->MinorFunction == IRP_MN_QUERY_POWER)\n        Irp->IoStatus.Status = STATUS_SUCCESS;\n\n    Status = Irp->IoStatus.Status;\n\n    PoStartNextPowerIrp(Irp);\n\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\nexit:\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_SYSTEM_CONTROL)\n_Function_class_(DRIVER_DISPATCH)\nstatic NTSTATUS __stdcall drv_system_control(_In_ PDEVICE_OBJECT DeviceObject, _In_ PIRP Irp) {\n    NTSTATUS Status;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    top_level = is_top_level(Irp);\n\n    Irp->IoStatus.Information = 0;\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        volume_device_extension* vde = DeviceObject->DeviceExtension;\n\n        IoSkipCurrentIrpStackLocation(Irp);\n\n        Status = IoCallDriver(vde->attached_device, Irp);\n\n        goto exit;\n    } else if (Vcb && Vcb->type == VCB_TYPE_FS) {\n        IoSkipCurrentIrpStackLocation(Irp);\n\n        Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp);\n\n        goto exit;\n    } else if (Vcb && Vcb->type == VCB_TYPE_BUS) {\n        bus_device_extension* bde = DeviceObject->DeviceExtension;\n\n        IoSkipCurrentIrpStackLocation(Irp);\n\n        Status = IoCallDriver(bde->attached_device, Irp);\n\n        goto exit;\n    }\n\n    Status = Irp->IoStatus.Status;\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\nexit:\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\nNTSTATUS check_file_name_valid(_In_ PUNICODE_STRING us, _In_ bool posix, _In_ bool stream) {\n    ULONG i;\n\n    if (us->Length < sizeof(WCHAR))\n        return STATUS_OBJECT_NAME_INVALID;\n\n    if (us->Length > 255 * sizeof(WCHAR))\n        return STATUS_OBJECT_NAME_INVALID;\n\n    for (i = 0; i < us->Length / sizeof(WCHAR); i++) {\n        if (us->Buffer[i] == '/' || us->Buffer[i] == 0 ||\n            (!posix && (us->Buffer[i] == '/' || us->Buffer[i] == ':')) ||\n            (!posix && !stream && (us->Buffer[i] == '<' || us->Buffer[i] == '>' || us->Buffer[i] == '\"' ||\n            us->Buffer[i] == '|' || us->Buffer[i] == '?' || us->Buffer[i] == '*' || (us->Buffer[i] >= 1 && us->Buffer[i] <= 31))))\n            return STATUS_OBJECT_NAME_INVALID;\n\n        /* Don't allow unpaired surrogates (\"WTF-16\") */\n\n        if ((us->Buffer[i] & 0xfc00) == 0xdc00 && (i == 0 || ((us->Buffer[i-1] & 0xfc00) != 0xd800)))\n            return STATUS_OBJECT_NAME_INVALID;\n\n        if ((us->Buffer[i] & 0xfc00) == 0xd800 && (i == (us->Length / sizeof(WCHAR)) - 1 || ((us->Buffer[i+1] & 0xfc00) != 0xdc00)))\n            return STATUS_OBJECT_NAME_INVALID;\n    }\n\n    if (us->Buffer[0] == '.' && (us->Length == sizeof(WCHAR) || (us->Length == 2 * sizeof(WCHAR) && us->Buffer[1] == '.')))\n        return STATUS_OBJECT_NAME_INVALID;\n\n    /* The Linux driver expects filenames with a maximum length of 255 bytes - make sure\n     * that our UTF-8 length won't be longer than that. */\n    if (us->Length >= 85 * sizeof(WCHAR)) {\n        NTSTATUS Status;\n        ULONG utf8len;\n\n        Status = utf16_to_utf8(NULL, 0, &utf8len, us->Buffer, us->Length);\n        if (!NT_SUCCESS(Status))\n            return Status;\n\n        if (utf8len > 255)\n            return STATUS_OBJECT_NAME_INVALID;\n        else if (stream && utf8len > 250) // minus five bytes for \"user.\"\n            return STATUS_OBJECT_NAME_INVALID;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nvoid chunk_lock_range(_In_ device_extension* Vcb, _In_ chunk* c, _In_ uint64_t start, _In_ uint64_t length) {\n    LIST_ENTRY* le;\n    bool locked;\n    range_lock* rl;\n\n    rl = ExAllocateFromNPagedLookasideList(&Vcb->range_lock_lookaside);\n    if (!rl) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    rl->start = start;\n    rl->length = length;\n    rl->thread = PsGetCurrentThread();\n\n    while (true) {\n        locked = false;\n\n        ExAcquireResourceExclusiveLite(&c->range_locks_lock, true);\n\n        le = c->range_locks.Flink;\n        while (le != &c->range_locks) {\n            range_lock* rl2 = CONTAINING_RECORD(le, range_lock, list_entry);\n\n            if (rl2->start < start + length && rl2->start + rl2->length > start && rl2->thread != PsGetCurrentThread()) {\n                locked = true;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        if (!locked) {\n            InsertTailList(&c->range_locks, &rl->list_entry);\n\n            ExReleaseResourceLite(&c->range_locks_lock);\n            return;\n        }\n\n        KeClearEvent(&c->range_locks_event);\n\n        ExReleaseResourceLite(&c->range_locks_lock);\n\n        KeWaitForSingleObject(&c->range_locks_event, UserRequest, KernelMode, false, NULL);\n    }\n}\n\nvoid chunk_unlock_range(_In_ device_extension* Vcb, _In_ chunk* c, _In_ uint64_t start, _In_ uint64_t length) {\n    LIST_ENTRY* le;\n\n    ExAcquireResourceExclusiveLite(&c->range_locks_lock, true);\n\n    le = c->range_locks.Flink;\n    while (le != &c->range_locks) {\n        range_lock* rl = CONTAINING_RECORD(le, range_lock, list_entry);\n\n        if (rl->start == start && rl->length == length) {\n            RemoveEntryList(&rl->list_entry);\n            ExFreeToNPagedLookasideList(&Vcb->range_lock_lookaside, rl);\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    KeSetEvent(&c->range_locks_event, 0, false);\n\n    ExReleaseResourceLite(&c->range_locks_lock);\n}\n\nvoid log_device_error(_In_ device_extension* Vcb, _Inout_ device* dev, _In_ int error) {\n    dev->stats[error]++;\n    dev->stats_changed = true;\n    Vcb->stats_changed = true;\n}\n\n#ifdef _DEBUG\n_Function_class_(KSTART_ROUTINE)\nstatic void __stdcall serial_thread(void* context) {\n    LARGE_INTEGER due_time;\n    KTIMER timer;\n\n    UNUSED(context);\n\n    KeInitializeTimer(&timer);\n\n    due_time.QuadPart = (uint64_t)-10000000;\n\n    KeSetTimer(&timer, due_time, NULL);\n\n    while (true) {\n        KeWaitForSingleObject(&timer, Executive, KernelMode, false, NULL);\n\n        init_serial(false);\n\n        if (comdo)\n            break;\n\n        KeSetTimer(&timer, due_time, NULL);\n    }\n\n    KeCancelTimer(&timer);\n\n    PsTerminateSystemThread(STATUS_SUCCESS);\n\n    serial_thread_handle = NULL;\n}\n\nstatic void init_serial(bool first_time) {\n    NTSTATUS Status;\n\n    Status = IoGetDeviceObjectPointer(&log_device, FILE_WRITE_DATA, &comfo, &comdo);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n\n        if (first_time) {\n            OBJECT_ATTRIBUTES oa;\n\n            InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);\n\n            Status = PsCreateSystemThread(&serial_thread_handle, 0, &oa, NULL, NULL, serial_thread, NULL);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"PsCreateSystemThread returned %08lx\\n\", Status);\n                return;\n            }\n        }\n    }\n}\n#endif\n\n#if defined(_X86_) || defined(_AMD64_)\nstatic void check_cpu() {\n    bool have_sse2 = false, have_sse42 = false, have_avx2 = false;\n    int cpu_info[4];\n\n    __cpuid(cpu_info, 1);\n    have_sse42 = cpu_info[2] & (1 << 20);\n    have_sse2 = cpu_info[3] & (1 << 26);\n\n    __cpuidex(cpu_info, 7, 0);\n    have_avx2 = cpu_info[1] & (1 << 5);\n\n    if (have_avx2) {\n        // check Windows has enabled AVX2 - Windows 10 doesn't immediately\n\n        if (__readcr4() & (1 << 18)) {\n            uint32_t xcr0;\n\n#ifdef _MSC_VER\n            xcr0 = (uint32_t)_xgetbv(0);\n#else\n            __asm__(\"xgetbv\" : \"=a\" (xcr0) : \"c\" (0) : \"edx\");\n#endif\n\n            if ((xcr0 & 6) != 6)\n                have_avx2 = false;\n        } else\n            have_avx2 = false;\n    }\n\n    if (have_sse42) {\n        TRACE(\"SSE4.2 is supported\\n\");\n        calc_crc32c = calc_crc32c_hw;\n    } else\n        TRACE(\"SSE4.2 not supported\\n\");\n\n    if (have_sse2) {\n        TRACE(\"SSE2 is supported\\n\");\n\n        if (!have_avx2)\n            do_xor = do_xor_sse2;\n    } else\n        TRACE(\"SSE2 is not supported\\n\");\n\n    if (have_avx2) {\n        TRACE(\"AVX2 is supported\\n\");\n        do_xor = do_xor_avx2;\n    } else\n        TRACE(\"AVX2 is not supported\\n\");\n}\n#elif defined(_ARM64_)\nstatic void check_cpu() {\n    uint64_t reg = _ReadStatusReg(ARM64_ID_AA64ISAR0_EL1);\n\n    if ((reg & 0xf0000) >> 16 == 1)\n        calc_crc32c = calc_crc32c_hw;\n}\n#endif\n\n#ifdef _DEBUG\nstatic void init_logging() {\n    ExAcquireResourceExclusiveLite(&log_lock, true);\n\n    if (log_device.Length > 0)\n        init_serial(true);\n    else if (log_file.Length > 0) {\n        NTSTATUS Status;\n        OBJECT_ATTRIBUTES oa;\n        IO_STATUS_BLOCK iosb;\n        char* dateline;\n        LARGE_INTEGER time;\n        TIME_FIELDS tf;\n\n        InitializeObjectAttributes(&oa, &log_file, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n\n        Status = ZwCreateFile(&log_handle, FILE_WRITE_DATA, &oa, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ,\n                              FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_WRITE_THROUGH | FILE_SYNCHRONOUS_IO_ALERT, NULL, 0);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"ZwCreateFile returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        if (iosb.Information == FILE_OPENED) { // already exists\n            FILE_STANDARD_INFORMATION fsi;\n            FILE_POSITION_INFORMATION fpi;\n\n            static const char delim[] = \"\\n---\\n\";\n\n            // move to end of file\n\n            Status = ZwQueryInformationFile(log_handle, &iosb, &fsi, sizeof(FILE_STANDARD_INFORMATION), FileStandardInformation);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"ZwQueryInformationFile returned %08lx\\n\", Status);\n                goto end;\n            }\n\n            fpi.CurrentByteOffset = fsi.EndOfFile;\n\n            Status = ZwSetInformationFile(log_handle, &iosb, &fpi, sizeof(FILE_POSITION_INFORMATION), FilePositionInformation);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"ZwSetInformationFile returned %08lx\\n\", Status);\n                goto end;\n            }\n\n            Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, (void*)delim, sizeof(delim) - 1, NULL, NULL);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"ZwWriteFile returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        dateline = ExAllocatePoolWithTag(PagedPool, 256, ALLOC_TAG);\n\n        if (!dateline) {\n            ERR(\"out of memory\\n\");\n            goto end;\n        }\n\n        KeQuerySystemTime(&time);\n\n        RtlTimeToTimeFields(&time, &tf);\n\n        sprintf(dateline, \"Starting logging at %04i-%02i-%02i %02i:%02i:%02i\\n\", tf.Year, tf.Month, tf.Day, tf.Hour, tf.Minute, tf.Second);\n\n        Status = ZwWriteFile(log_handle, NULL, NULL, NULL, &iosb, dateline, (ULONG)strlen(dateline), NULL, NULL);\n\n        ExFreePool(dateline);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"ZwWriteFile returned %08lx\\n\", Status);\n            goto end;\n        }\n    }\n\nend:\n    ExReleaseResourceLite(&log_lock);\n}\n#endif\n\n_Function_class_(KSTART_ROUTINE)\nstatic void __stdcall degraded_wait_thread(_In_ void* context) {\n    KTIMER timer;\n    LARGE_INTEGER delay;\n\n    UNUSED(context);\n\n    KeInitializeTimer(&timer);\n\n    delay.QuadPart = -30000000; // wait three seconds\n    KeSetTimer(&timer, delay, NULL);\n    KeWaitForSingleObject(&timer, Executive, KernelMode, false, NULL);\n\n    TRACE(\"timer expired\\n\");\n\n    degraded_wait = false;\n\n    ZwClose(degraded_wait_handle);\n    degraded_wait_handle = NULL;\n\n    PsTerminateSystemThread(STATUS_SUCCESS);\n}\n\n_Function_class_(DRIVER_ADD_DEVICE)\nNTSTATUS __stdcall AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT PhysicalDeviceObject) {\n    LIST_ENTRY* le;\n    NTSTATUS Status;\n    UNICODE_STRING volname;\n    ULONG i;\n    WCHAR* s;\n    pdo_device_extension* pdode = NULL;\n    PDEVICE_OBJECT voldev;\n    volume_device_extension* vde;\n    UNICODE_STRING arc_name_us;\n    WCHAR* anp;\n\n    static const WCHAR arc_name_prefix[] = L\"\\\\ArcName\\\\btrfs(\";\n\n    WCHAR arc_name[(sizeof(arc_name_prefix) / sizeof(WCHAR)) - 1 + 37];\n\n    TRACE(\"(%p, %p)\\n\", DriverObject, PhysicalDeviceObject);\n\n    UNUSED(DriverObject);\n\n    ExAcquireResourceSharedLite(&pdo_list_lock, true);\n\n    le = pdo_list.Flink;\n    while (le != &pdo_list) {\n        pdo_device_extension* pdode2 = CONTAINING_RECORD(le, pdo_device_extension, list_entry);\n\n        if (pdode2->pdo == PhysicalDeviceObject) {\n            pdode = pdode2;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!pdode) {\n        WARN(\"unrecognized PDO %p\\n\", PhysicalDeviceObject);\n        Status = STATUS_NOT_SUPPORTED;\n        goto end;\n    }\n\n    ExAcquireResourceExclusiveLite(&pdode->child_lock, true);\n\n    if (pdode->vde) { // if already done, return success\n        Status = STATUS_SUCCESS;\n        goto end2;\n    }\n\n    volname.Length = volname.MaximumLength = (sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR)) + ((36 + 1) * sizeof(WCHAR));\n    volname.Buffer = ExAllocatePoolWithTag(PagedPool, volname.MaximumLength, ALLOC_TAG); // FIXME - when do we free this?\n\n    if (!volname.Buffer) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end2;\n    }\n\n    RtlCopyMemory(volname.Buffer, BTRFS_VOLUME_PREFIX, sizeof(BTRFS_VOLUME_PREFIX) - sizeof(WCHAR));\n    RtlCopyMemory(arc_name, arc_name_prefix, sizeof(arc_name_prefix) - sizeof(WCHAR));\n\n    anp = &arc_name[(sizeof(arc_name_prefix) / sizeof(WCHAR)) - 1];\n    s = &volname.Buffer[(sizeof(BTRFS_VOLUME_PREFIX) / sizeof(WCHAR)) - 1];\n\n    for (i = 0; i < 16; i++) {\n        *s = *anp = hex_digit(pdode->uuid.uuid[i] >> 4);\n        s++;\n        anp++;\n\n        *s = *anp = hex_digit(pdode->uuid.uuid[i] & 0xf);\n        s++;\n        anp++;\n\n        if (i == 3 || i == 5 || i == 7 || i == 9) {\n            *s = *anp = '-';\n            s++;\n            anp++;\n        }\n    }\n\n    *s = '}';\n    *anp = ')';\n\n    Status = IoCreateDevice(drvobj, sizeof(volume_device_extension), &volname, FILE_DEVICE_DISK,\n                            is_windows_8 ? FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL : 0, false, &voldev);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoCreateDevice returned %08lx\\n\", Status);\n        goto end2;\n    }\n\n    arc_name_us.Buffer = arc_name;\n    arc_name_us.Length = arc_name_us.MaximumLength = sizeof(arc_name);\n\n    Status = IoCreateSymbolicLink(&arc_name_us, &volname);\n    if (!NT_SUCCESS(Status))\n        WARN(\"IoCreateSymbolicLink returned %08lx\\n\", Status);\n\n    voldev->SectorSize = PhysicalDeviceObject->SectorSize;\n    voldev->Flags |= DO_DIRECT_IO;\n\n    vde = voldev->DeviceExtension;\n    vde->type = VCB_TYPE_VOLUME;\n    vde->name = volname;\n    vde->device = voldev;\n    vde->mounted_device = NULL;\n    vde->pdo = PhysicalDeviceObject;\n    vde->pdode = pdode;\n    vde->removing = false;\n    vde->dead = false;\n    vde->open_count = 0;\n\n    Status = IoRegisterDeviceInterface(PhysicalDeviceObject, &GUID_DEVINTERFACE_VOLUME, NULL, &vde->bus_name);\n    if (!NT_SUCCESS(Status))\n        WARN(\"IoRegisterDeviceInterface returned %08lx\\n\", Status);\n\n    vde->attached_device = IoAttachDeviceToDeviceStack(voldev, PhysicalDeviceObject);\n\n    pdode->vde = vde;\n\n    if (pdode->removable)\n        voldev->Characteristics |= FILE_REMOVABLE_MEDIA;\n\n    if (RtlCompareMemory(&boot_uuid, &pdode->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n        voldev->Flags |= DO_SYSTEM_BOOT_PARTITION;\n        PhysicalDeviceObject->Flags |= DO_SYSTEM_BOOT_PARTITION;\n    }\n\n    voldev->Flags &= ~DO_DEVICE_INITIALIZING;\n\n    Status = IoSetDeviceInterfaceState(&vde->bus_name, true);\n    if (!NT_SUCCESS(Status))\n        WARN(\"IoSetDeviceInterfaceState returned %08lx\\n\", Status);\n\n    Status = STATUS_SUCCESS;\n\nend2:\n    ExReleaseResourceLite(&pdode->child_lock);\n\nend:\n    ExReleaseResourceLite(&pdo_list_lock);\n\n    return Status;\n}\n\n_Function_class_(DRIVER_INITIALIZE)\nNTSTATUS __stdcall DriverEntry(_In_ PDRIVER_OBJECT DriverObject, _In_ PUNICODE_STRING RegistryPath) {\n    NTSTATUS Status;\n    PDEVICE_OBJECT DeviceObject;\n    UNICODE_STRING device_nameW;\n    UNICODE_STRING dosdevice_nameW;\n    control_device_extension* cde;\n    bus_device_extension* bde;\n    HANDLE regh;\n    OBJECT_ATTRIBUTES oa, system_thread_attributes;\n    ULONG dispos;\n    RTL_OSVERSIONINFOW ver;\n\n    ver.dwOSVersionInfoSize = sizeof(RTL_OSVERSIONINFOW);\n\n    Status = RtlGetVersion(&ver);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlGetVersion returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    is_windows_8 = ver.dwMajorVersion > 6 || (ver.dwMajorVersion == 6 && ver.dwMinorVersion >= 2);\n\n    KeInitializeSpinLock(&fve_data_lock);\n\n    InitializeListHead(&uid_map_list);\n    InitializeListHead(&gid_map_list);\n\n#ifdef _DEBUG\n    ExInitializeResourceLite(&log_lock);\n#endif\n    ExInitializeResourceLite(&mapping_lock);\n\n    log_device.Buffer = NULL;\n    log_device.Length = log_device.MaximumLength = 0;\n    log_file.Buffer = NULL;\n    log_file.Length = log_file.MaximumLength = 0;\n\n    registry_path.Length = registry_path.MaximumLength = RegistryPath->Length;\n    registry_path.Buffer = ExAllocatePoolWithTag(PagedPool, registry_path.Length, ALLOC_TAG);\n\n    if (!registry_path.Buffer) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(registry_path.Buffer, RegistryPath->Buffer, registry_path.Length);\n\n    read_registry(&registry_path, false);\n\n#ifdef _DEBUG\n    if (debug_log_level > 0)\n        init_logging();\n\n    log_started = true;\n#endif\n\n    TRACE(\"DriverEntry\\n\");\n\n#if defined(_X86_) || defined(_AMD64_) || defined(_ARM64_)\n    check_cpu();\n#endif\n\n    if (ver.dwMajorVersion > 6 || (ver.dwMajorVersion == 6 && ver.dwMinorVersion >= 2)) { // Windows 8 or above\n        UNICODE_STRING name;\n        tPsIsDiskCountersEnabled fPsIsDiskCountersEnabled;\n\n        RtlInitUnicodeString(&name, L\"PsIsDiskCountersEnabled\");\n        fPsIsDiskCountersEnabled = (tPsIsDiskCountersEnabled)MmGetSystemRoutineAddress(&name);\n\n        if (fPsIsDiskCountersEnabled) {\n            diskacc = fPsIsDiskCountersEnabled();\n\n            RtlInitUnicodeString(&name, L\"PsUpdateDiskCounters\");\n            fPsUpdateDiskCounters = (tPsUpdateDiskCounters)MmGetSystemRoutineAddress(&name);\n\n            if (!fPsUpdateDiskCounters)\n                diskacc = false;\n\n            RtlInitUnicodeString(&name, L\"FsRtlUpdateDiskCounters\");\n            fFsRtlUpdateDiskCounters = (tFsRtlUpdateDiskCounters)MmGetSystemRoutineAddress(&name);\n        }\n\n        RtlInitUnicodeString(&name, L\"CcCopyReadEx\");\n        fCcCopyReadEx = (tCcCopyReadEx)MmGetSystemRoutineAddress(&name);\n\n        RtlInitUnicodeString(&name, L\"CcCopyWriteEx\");\n        fCcCopyWriteEx = (tCcCopyWriteEx)MmGetSystemRoutineAddress(&name);\n\n        RtlInitUnicodeString(&name, L\"CcSetAdditionalCacheAttributesEx\");\n        fCcSetAdditionalCacheAttributesEx = (tCcSetAdditionalCacheAttributesEx)MmGetSystemRoutineAddress(&name);\n\n        RtlInitUnicodeString(&name, L\"FsRtlCheckLockForOplockRequest\");\n        fFsRtlCheckLockForOplockRequest = (tFsRtlCheckLockForOplockRequest)MmGetSystemRoutineAddress(&name);\n    } else {\n        fPsUpdateDiskCounters = NULL;\n        fCcCopyReadEx = NULL;\n        fCcCopyWriteEx = NULL;\n        fCcSetAdditionalCacheAttributesEx = NULL;\n        fFsRtlUpdateDiskCounters = NULL;\n        fFsRtlCheckLockForOplockRequest = NULL;\n    }\n\n    if (ver.dwMajorVersion > 6 || (ver.dwMajorVersion == 6 && ver.dwMinorVersion >= 1)) { // Windows 7 or above\n        UNICODE_STRING name;\n\n        RtlInitUnicodeString(&name, L\"IoUnregisterPlugPlayNotificationEx\");\n        fIoUnregisterPlugPlayNotificationEx = (tIoUnregisterPlugPlayNotificationEx)MmGetSystemRoutineAddress(&name);\n\n        RtlInitUnicodeString(&name, L\"FsRtlAreThereCurrentOrInProgressFileLocks\");\n        fFsRtlAreThereCurrentOrInProgressFileLocks = (tFsRtlAreThereCurrentOrInProgressFileLocks)MmGetSystemRoutineAddress(&name);\n    } else {\n        fIoUnregisterPlugPlayNotificationEx = NULL;\n        fFsRtlAreThereCurrentOrInProgressFileLocks = NULL;\n    }\n\n    if (ver.dwMajorVersion >= 6) { // Windows Vista or above\n        UNICODE_STRING name;\n\n        RtlInitUnicodeString(&name, L\"FsRtlGetEcpListFromIrp\");\n        fFsRtlGetEcpListFromIrp = (tFsRtlGetEcpListFromIrp)MmGetSystemRoutineAddress(&name);\n\n        RtlInitUnicodeString(&name, L\"FsRtlGetNextExtraCreateParameter\");\n        fFsRtlGetNextExtraCreateParameter = (tFsRtlGetNextExtraCreateParameter)MmGetSystemRoutineAddress(&name);\n\n        RtlInitUnicodeString(&name, L\"FsRtlValidateReparsePointBuffer\");\n        fFsRtlValidateReparsePointBuffer = (tFsRtlValidateReparsePointBuffer)MmGetSystemRoutineAddress(&name);\n    } else {\n        fFsRtlGetEcpListFromIrp = NULL;\n        fFsRtlGetNextExtraCreateParameter = NULL;\n        fFsRtlValidateReparsePointBuffer = compat_FsRtlValidateReparsePointBuffer;\n    }\n\n    drvobj = DriverObject;\n\n    DriverObject->DriverUnload = DriverUnload;\n\n    DriverObject->DriverExtension->AddDevice = AddDevice;\n\n    DriverObject->MajorFunction[IRP_MJ_CREATE]                   = drv_create;\n    DriverObject->MajorFunction[IRP_MJ_CLOSE]                    = drv_close;\n    DriverObject->MajorFunction[IRP_MJ_READ]                     = drv_read;\n    DriverObject->MajorFunction[IRP_MJ_WRITE]                    = drv_write;\n    DriverObject->MajorFunction[IRP_MJ_QUERY_INFORMATION]        = drv_query_information;\n    DriverObject->MajorFunction[IRP_MJ_SET_INFORMATION]          = drv_set_information;\n    DriverObject->MajorFunction[IRP_MJ_QUERY_EA]                 = drv_query_ea;\n    DriverObject->MajorFunction[IRP_MJ_SET_EA]                   = drv_set_ea;\n    DriverObject->MajorFunction[IRP_MJ_FLUSH_BUFFERS]            = drv_flush_buffers;\n    DriverObject->MajorFunction[IRP_MJ_QUERY_VOLUME_INFORMATION] = drv_query_volume_information;\n    DriverObject->MajorFunction[IRP_MJ_SET_VOLUME_INFORMATION]   = drv_set_volume_information;\n    DriverObject->MajorFunction[IRP_MJ_DIRECTORY_CONTROL]        = drv_directory_control;\n    DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL]      = drv_file_system_control;\n    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL]           = drv_device_control;\n    DriverObject->MajorFunction[IRP_MJ_SHUTDOWN]                 = drv_shutdown;\n    DriverObject->MajorFunction[IRP_MJ_LOCK_CONTROL]             = drv_lock_control;\n    DriverObject->MajorFunction[IRP_MJ_CLEANUP]                  = drv_cleanup;\n    DriverObject->MajorFunction[IRP_MJ_QUERY_SECURITY]           = drv_query_security;\n    DriverObject->MajorFunction[IRP_MJ_SET_SECURITY]             = drv_set_security;\n    DriverObject->MajorFunction[IRP_MJ_POWER]                    = drv_power;\n    DriverObject->MajorFunction[IRP_MJ_SYSTEM_CONTROL]           = drv_system_control;\n    DriverObject->MajorFunction[IRP_MJ_PNP]                      = drv_pnp;\n\n    init_fast_io_dispatch(&DriverObject->FastIoDispatch);\n\n    device_nameW.Buffer = (WCHAR*)device_name;\n    device_nameW.Length = device_nameW.MaximumLength = sizeof(device_name) - sizeof(WCHAR);\n    dosdevice_nameW.Buffer = (WCHAR*)dosdevice_name;\n    dosdevice_nameW.Length = dosdevice_nameW.MaximumLength = sizeof(dosdevice_name) - sizeof(WCHAR);\n\n    Status = IoCreateDevice(DriverObject, sizeof(control_device_extension), &device_nameW, FILE_DEVICE_DISK_FILE_SYSTEM,\n                            FILE_DEVICE_SECURE_OPEN, false, &DeviceObject);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoCreateDevice returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    master_devobj = DeviceObject;\n    cde = (control_device_extension*)master_devobj->DeviceExtension;\n\n    RtlZeroMemory(cde, sizeof(control_device_extension));\n\n    cde->type = VCB_TYPE_CONTROL;\n\n    DeviceObject->Flags &= ~DO_DEVICE_INITIALIZING;\n\n    Status = IoCreateSymbolicLink(&dosdevice_nameW, &device_nameW);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoCreateSymbolicLink returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    init_cache();\n\n    InitializeListHead(&VcbList);\n    ExInitializeResourceLite(&global_loading_lock);\n    ExInitializeResourceLite(&pdo_list_lock);\n\n    InitializeListHead(&pdo_list);\n\n    InitializeObjectAttributes(&oa, RegistryPath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n    Status = ZwCreateKey(&regh, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS | KEY_NOTIFY, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ZwCreateKey returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    watch_registry(regh);\n\n    Status = IoCreateDevice(DriverObject, sizeof(bus_device_extension), NULL, FILE_DEVICE_UNKNOWN,\n                            FILE_DEVICE_SECURE_OPEN, false, &busobj);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoCreateDevice returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    bde = (bus_device_extension*)busobj->DeviceExtension;\n\n    RtlZeroMemory(bde, sizeof(bus_device_extension));\n\n    bde->type = VCB_TYPE_BUS;\n\n    Status = IoReportDetectedDevice(drvobj, InterfaceTypeUndefined, 0xFFFFFFFF, 0xFFFFFFFF,\n                                    NULL, NULL, 0, &bde->buspdo);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoReportDetectedDevice returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    Status = IoRegisterDeviceInterface(bde->buspdo, &BtrfsBusInterface, NULL, &bde->bus_name);\n    if (!NT_SUCCESS(Status))\n        WARN(\"IoRegisterDeviceInterface returned %08lx\\n\", Status);\n\n    bde->attached_device = IoAttachDeviceToDeviceStack(busobj, bde->buspdo);\n\n    busobj->Flags &= ~DO_DEVICE_INITIALIZING;\n\n    Status = IoSetDeviceInterfaceState(&bde->bus_name, true);\n    if (!NT_SUCCESS(Status))\n        WARN(\"IoSetDeviceInterfaceState returned %08lx\\n\", Status);\n\n    IoInvalidateDeviceRelations(bde->buspdo, BusRelations);\n\n    InitializeObjectAttributes(&system_thread_attributes, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = PsCreateSystemThread(&degraded_wait_handle, 0, &system_thread_attributes, NULL, NULL, degraded_wait_thread, NULL);\n    if (!NT_SUCCESS(Status))\n        WARN(\"PsCreateSystemThread returned %08lx\\n\", Status);\n\n    ExInitializeResourceLite(&boot_lock);\n\n    Status = IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,\n                                            (PVOID)&GUID_DEVINTERFACE_VOLUME, DriverObject, volume_notification, NULL, &notification_entry2);\n    if (!NT_SUCCESS(Status))\n        ERR(\"IoRegisterPlugPlayNotification returned %08lx\\n\", Status);\n\n    Status = IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,\n                                            (PVOID)&GUID_DEVINTERFACE_HIDDEN_VOLUME, DriverObject, volume_notification, NULL, &notification_entry3);\n    if (!NT_SUCCESS(Status))\n        ERR(\"IoRegisterPlugPlayNotification returned %08lx\\n\", Status);\n\n    Status = IoRegisterPlugPlayNotification(EventCategoryDeviceInterfaceChange, PNPNOTIFY_DEVICE_INTERFACE_INCLUDE_EXISTING_INTERFACES,\n                                            (PVOID)&GUID_DEVINTERFACE_DISK, DriverObject, pnp_notification, DriverObject, &notification_entry);\n    if (!NT_SUCCESS(Status))\n        ERR(\"IoRegisterPlugPlayNotification returned %08lx\\n\", Status);\n\n    finished_probing = true;\n\n    KeInitializeEvent(&mountmgr_thread_event, NotificationEvent, false);\n\n    Status = PsCreateSystemThread(&mountmgr_thread_handle, 0, &system_thread_attributes, NULL, NULL, mountmgr_thread, NULL);\n    if (!NT_SUCCESS(Status))\n        WARN(\"PsCreateSystemThread returned %08lx\\n\", Status);\n\n    IoRegisterFileSystem(DeviceObject);\n\n    check_system_root();\n\n    return STATUS_SUCCESS;\n}\n"
  },
  {
    "path": "src/btrfs.cdf",
    "content": "[CatalogHeader]\nName=btrfs.cat\nCatalogVersion=1\nHashAlgorithms=SHA1\nPageHashes=true\nEncodingType=0x00010001\nCATATTR1=0x10010001:HWID1:btrfsvolume\nCATATTR2=0x10010001:HWID2:root\\btrfs\nCATATTR3=0x10010001:OS:XPX86,XPX64,VistaX86,VistaX64,7X86,7X64,8X86,8X64,8ARM,_v63,_v63_X64,_v63_ARM,_v100,_v100_X64,_v100_X64_22H2,_v100_ARM64_22H2\n\n[CatalogFiles]\n<HASH>btrfs.inf=btrfs.inf\n<HASH>btrfs.infATTR1=0x10010001:File:btrfs.inf\n<HASH>btrfs.infATTR2=0x10010001:OSAttr:2:5.1,2:5.2,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0\n<HASH>btrfs-vol.inf=btrfs-vol.inf\n<HASH>btrfs-vol.infATTR1=0x10010001:File:btrfs-vol.inf\n<HASH>btrfs-vol.infATTR2=0x10010001:OSAttr:2:5.1,2:5.2,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0\n<HASH>aarch64_btrfs.sys=aarch64\\btrfs.sys\n<HASH>aarch64_btrfs.sysATTR1=0x10010001:File:btrfs.sys\n<HASH>aarch64_btrfs.sysATTR2=0x10010001:OSAttr:2:5.1,2:5.2,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0\n<HASH>aarch64_mkbtrfs.exe=aarch64\\mkbtrfs.exe\n<HASH>aarch64_mkbtrfs.exeATTR1=0x10010001:File:mkbtrfs.exe\n<HASH>aarch64_mkbtrfs.exeATTR2=0x10010001:OSAttr:2:10.0\n<HASH>aarch64_shellbtrfs.dll=aarch64\\shellbtrfs.dll\n<HASH>aarch64_shellbtrfs.dllATTR1=0x10010001:File:shellbtrfs.dll\n<HASH>aarch64_shellbtrfs.dllATTR2=0x10010001:OSAttr:2:10.0\n<HASH>aarch64_ubtrfs.dll=aarch64\\ubtrfs.dll\n<HASH>aarch64_ubtrfs.dllATTR1=0x10010001:File:ubtrfs.dll\n<HASH>aarch64_ubtrfs.dllATTR2=0x10010001:OSAttr:2:10.0\n<HASH>amd64_btrfs.sys=amd64\\btrfs.sys\n<HASH>amd64_btrfs.sysATTR1=0x10010001:File:btrfs.sys\n<HASH>amd64_btrfs.sysATTR2=0x10010001:OSAttr:2:5.1,2:5.2,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0\n<HASH>amd64_mkbtrfs.exe=amd64\\mkbtrfs.exe\n<HASH>amd64_mkbtrfs.exeATTR1=0x10010001:File:mkbtrfs.exe\n<HASH>amd64_mkbtrfs.exeATTR2=0x10010001:OSAttr:2:5.1,2:5.2,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0\n<HASH>amd64_shellbtrfs.dll=amd64\\shellbtrfs.dll\n<HASH>amd64_shellbtrfs.dllATTR1=0x10010001:File:shellbtrfs.dll\n<HASH>amd64_shellbtrfs.dllATTR2=0x10010001:OSAttr:2:5.1,2:5.2,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0\n<HASH>amd64_ubtrfs.dll=amd64\\ubtrfs.dll\n<HASH>amd64_ubtrfs.dllATTR1=0x10010001:File:ubtrfs.dll\n<HASH>amd64_ubtrfs.dllATTR2=0x10010001:OSAttr:2:5.1,2:5.2,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0\n<HASH>arm_btrfs.sys=arm\\btrfs.sys\n<HASH>arm_btrfs.sysATTR1=0x10010001:File:btrfs.sys\n<HASH>arm_btrfs.sysATTR2=0x10010001:OSAttr:2:5.1,2:5.2,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0\n<HASH>arm_mkbtrfs.exe=arm\\mkbtrfs.exe\n<HASH>arm_mkbtrfs.exeATTR1=0x10010001:File:mkbtrfs.exe\n<HASH>arm_mkbtrfs.exeATTR2=0x10010001:OSAttr:2:6.2,2:6.3\n<HASH>arm_shellbtrfs.dll=arm\\shellbtrfs.dll\n<HASH>arm_shellbtrfs.dllATTR1=0x10010001:File:shellbtrfs.dll\n<HASH>arm_shellbtrfs.dllATTR2=0x10010001:OSAttr:2:6.2,2:6.3\n<HASH>arm_ubtrfs.dll=arm\\ubtrfs.dll\n<HASH>arm_ubtrfs.dllATTR1=0x10010001:File:ubtrfs.dll\n<HASH>arm_ubtrfs.dllATTR2=0x10010001:OSAttr:2:6.2,2:6.3\n<HASH>x86_btrfs.sys=x86\\btrfs.sys\n<HASH>x86_btrfs.sysATTR1=0x10010001:File:btrfs.sys\n<HASH>x86_btrfs.sysATTR2=0x10010001:OSAttr:2:5.1,2:5.2,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0\n<HASH>x86_mkbtrfs.exe=x86\\mkbtrfs.exe\n<HASH>x86_mkbtrfs.exeATTR1=0x10010001:File:mkbtrfs.exe\n<HASH>x86_mkbtrfs.exeATTR2=0x10010001:OSAttr:2:5.1,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0\n<HASH>x86_shellbtrfs.dll=x86\\shellbtrfs.dll\n<HASH>x86_shellbtrfs.dllATTR1=0x10010001:File:shellbtrfs.dll\n<HASH>x86_shellbtrfs.dllATTR2=0x10010001:OSAttr:2:5.1,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0\n<HASH>x86_ubtrfs.dll=x86\\ubtrfs.dll\n<HASH>x86_ubtrfs.dllATTR1=0x10010001:File:ubtrfs.dll\n<HASH>x86_ubtrfs.dllATTR2=0x10010001:OSAttr:2:5.1,2:6.0,2:6.1,2:6.2,2:6.3,2:10.0\n"
  },
  {
    "path": "src/btrfs.h",
    "content": "/* btrfs.h\n * Generic btrfs header file. Thanks to whoever it was who wrote\n * https://btrfs.wiki.kernel.org/index.php/On-disk_Format - you saved me a lot of time!\n *\n * I release this file, and this file only, into the public domain - do whatever\n * you want with it. You don't have to, but I'd appreciate if you let me know if you\n * use it anything cool - mark@harmstone.com. */\n\n#pragma once\n\n#include <stdint.h>\n#include <assert.h>\n\nstatic const uint64_t superblock_addrs[] = { 0x10000, 0x4000000, 0x4000000000, 0x4000000000000, 0 };\n\n#define BTRFS_MAGIC         0x4d5f53665248425f\n#define MAX_LABEL_SIZE      0x100\n#define SUBVOL_ROOT_INODE   0x100\n#define BTRFS_LAST_FREE_OBJECTID    0xffffffffffffff00\n\n#define TYPE_INODE_ITEM        0x01\n#define TYPE_INODE_REF         0x0C\n#define TYPE_INODE_EXTREF      0x0D\n#define TYPE_XATTR_ITEM        0x18\n#define TYPE_ORPHAN_INODE      0x30\n#define TYPE_DIR_ITEM          0x54\n#define TYPE_DIR_INDEX         0x60\n#define TYPE_EXTENT_DATA       0x6C\n#define TYPE_EXTENT_CSUM       0x80\n#define TYPE_ROOT_ITEM         0x84\n#define TYPE_ROOT_BACKREF      0x90\n#define TYPE_ROOT_REF          0x9C\n#define TYPE_EXTENT_ITEM       0xA8\n#define TYPE_METADATA_ITEM     0xA9\n#define TYPE_TREE_BLOCK_REF    0xB0\n#define TYPE_EXTENT_DATA_REF   0xB2\n#define TYPE_EXTENT_REF_V0     0xB4\n#define TYPE_SHARED_BLOCK_REF  0xB6\n#define TYPE_SHARED_DATA_REF   0xB8\n#define TYPE_BLOCK_GROUP_ITEM  0xC0\n#define TYPE_FREE_SPACE_INFO   0xC6\n#define TYPE_FREE_SPACE_EXTENT 0xC7\n#define TYPE_FREE_SPACE_BITMAP 0xC8\n#define TYPE_DEV_EXTENT        0xCC\n#define TYPE_DEV_ITEM          0xD8\n#define TYPE_CHUNK_ITEM        0xE4\n#define TYPE_TEMP_ITEM         0xF8\n#define TYPE_DEV_STATS         0xF9\n#define TYPE_SUBVOL_UUID       0xFB\n#define TYPE_SUBVOL_REC_UUID   0xFC\n\n#define BTRFS_ROOT_ROOT         1\n#define BTRFS_ROOT_EXTENT       2\n#define BTRFS_ROOT_CHUNK        3\n#define BTRFS_ROOT_DEVTREE      4\n#define BTRFS_ROOT_FSTREE       5\n#define BTRFS_ROOT_TREEDIR      6\n#define BTRFS_ROOT_CHECKSUM     7\n#define BTRFS_ROOT_UUID         9\n#define BTRFS_ROOT_FREE_SPACE   0xa\n#define BTRFS_ROOT_BLOCK_GROUP  0xb\n#define BTRFS_ROOT_RAID_STRIPE  0xc\n#define BTRFS_ROOT_DATA_RELOC   0xFFFFFFFFFFFFFFF7\n\n#define BTRFS_COMPRESSION_NONE  0\n#define BTRFS_COMPRESSION_ZLIB  1\n#define BTRFS_COMPRESSION_LZO   2\n#define BTRFS_COMPRESSION_ZSTD  3\n\n#define BTRFS_ENCRYPTION_NONE   0\n\n#define BTRFS_ENCODING_NONE     0\n\n#define EXTENT_TYPE_INLINE      0\n#define EXTENT_TYPE_REGULAR     1\n#define EXTENT_TYPE_PREALLOC    2\n\n#define BLOCK_FLAG_DATA         0x001\n#define BLOCK_FLAG_SYSTEM       0x002\n#define BLOCK_FLAG_METADATA     0x004\n#define BLOCK_FLAG_RAID0        0x008\n#define BLOCK_FLAG_RAID1        0x010\n#define BLOCK_FLAG_DUPLICATE    0x020\n#define BLOCK_FLAG_RAID10       0x040\n#define BLOCK_FLAG_RAID5        0x080\n#define BLOCK_FLAG_RAID6        0x100\n#define BLOCK_FLAG_RAID1C3      0x200\n#define BLOCK_FLAG_RAID1C4      0x400\n\n#define FREE_SPACE_CACHE_ID     0xFFFFFFFFFFFFFFF5\n#define EXTENT_CSUM_ID          0xFFFFFFFFFFFFFFF6\n#define BALANCE_ITEM_ID         0xFFFFFFFFFFFFFFFC\n\n#define BTRFS_INODE_NODATASUM   0x001\n#define BTRFS_INODE_NODATACOW   0x002\n#define BTRFS_INODE_READONLY    0x004\n#define BTRFS_INODE_NOCOMPRESS  0x008\n#define BTRFS_INODE_PREALLOC    0x010\n#define BTRFS_INODE_SYNC        0x020\n#define BTRFS_INODE_IMMUTABLE   0x040\n#define BTRFS_INODE_APPEND      0x080\n#define BTRFS_INODE_NODUMP      0x100\n#define BTRFS_INODE_NOATIME     0x200\n#define BTRFS_INODE_DIRSYNC     0x400\n#define BTRFS_INODE_COMPRESS    0x800\n\n#define BTRFS_INODE_RO_VERITY   0x1\n\n#define BTRFS_SUBVOL_READONLY   0x1\n\n#define BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE          0x1\n#define BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID    0x2\n#define BTRFS_COMPAT_RO_FLAGS_VERITY                    0x4\n#define BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE          0x8\n\n#define BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF      0x0001\n#define BTRFS_INCOMPAT_FLAGS_DEFAULT_SUBVOL     0x0002\n#define BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS       0x0004\n#define BTRFS_INCOMPAT_FLAGS_COMPRESS_LZO       0x0008\n#define BTRFS_INCOMPAT_FLAGS_COMPRESS_ZSTD      0x0010\n#define BTRFS_INCOMPAT_FLAGS_BIG_METADATA       0x0020\n#define BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF      0x0040\n#define BTRFS_INCOMPAT_FLAGS_RAID56             0x0080\n#define BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA    0x0100\n#define BTRFS_INCOMPAT_FLAGS_NO_HOLES           0x0200\n#define BTRFS_INCOMPAT_FLAGS_METADATA_UUID      0x0400\n#define BTRFS_INCOMPAT_FLAGS_RAID1C34           0x0800\n#define BTRFS_INCOMPAT_FLAGS_ZONED              0x1000\n#define BTRFS_INCOMPAT_FLAGS_EXTENT_TREE_V2     0x2000\n#define BTRFS_INCOMPAT_FLAGS_RAID_STRIPE_TREE   0x4000\n#define BTRFS_INCOMPAT_FLAGS_SIMPLE_QUOTA       0x10000\n\n#define BTRFS_SUPERBLOCK_FLAGS_SEEDING   0x100000000\n\n#define BTRFS_ORPHAN_INODE_OBJID         0xFFFFFFFFFFFFFFFB\n\n#define CSUM_TYPE_CRC32C        0\n#define CSUM_TYPE_XXHASH        1\n#define CSUM_TYPE_SHA256        2\n#define CSUM_TYPE_BLAKE2        3\n\n#pragma pack(push, 1)\n\ntypedef struct {\n    uint8_t uuid[16];\n} BTRFS_UUID;\n\ntypedef struct {\n    uint64_t obj_id;\n    uint8_t obj_type;\n    uint64_t offset;\n} KEY;\n\n#define HEADER_FLAG_WRITTEN         0x000000000000001\n#define HEADER_FLAG_SHARED_BACKREF  0x000000000000002\n#define HEADER_FLAG_MIXED_BACKREF   0x100000000000000\n\ntypedef struct {\n    uint8_t csum[32];\n    BTRFS_UUID fs_uuid;\n    uint64_t address;\n    uint64_t flags;\n    BTRFS_UUID chunk_tree_uuid;\n    uint64_t generation;\n    uint64_t tree_id;\n    uint32_t num_items;\n    uint8_t level;\n} tree_header;\n\ntypedef struct {\n    KEY key;\n    uint32_t offset;\n    uint32_t size;\n} leaf_node;\n\ntypedef struct {\n    KEY key;\n    uint64_t address;\n    uint64_t generation;\n} internal_node;\n\ntypedef struct {\n    uint64_t dev_id;\n    uint64_t num_bytes;\n    uint64_t bytes_used;\n    uint32_t optimal_io_align;\n    uint32_t optimal_io_width;\n    uint32_t minimal_io_size;\n    uint64_t type;\n    uint64_t generation;\n    uint64_t start_offset;\n    uint32_t dev_group;\n    uint8_t seek_speed;\n    uint8_t bandwidth;\n    BTRFS_UUID device_uuid;\n    BTRFS_UUID fs_uuid;\n} DEV_ITEM;\n\n#define SYS_CHUNK_ARRAY_SIZE 0x800\n#define BTRFS_NUM_BACKUP_ROOTS 4\n\ntypedef struct {\n    uint64_t root_tree_addr;\n    uint64_t root_tree_generation;\n    uint64_t chunk_tree_addr;\n    uint64_t chunk_tree_generation;\n    uint64_t extent_tree_addr;\n    uint64_t extent_tree_generation;\n    uint64_t fs_tree_addr;\n    uint64_t fs_tree_generation;\n    uint64_t dev_root_addr;\n    uint64_t dev_root_generation;\n    uint64_t csum_root_addr;\n    uint64_t csum_root_generation;\n    uint64_t total_bytes;\n    uint64_t bytes_used;\n    uint64_t num_devices;\n    uint64_t reserved[4];\n    uint8_t root_level;\n    uint8_t chunk_root_level;\n    uint8_t extent_root_level;\n    uint8_t fs_root_level;\n    uint8_t dev_root_level;\n    uint8_t csum_root_level;\n    uint8_t reserved2[10];\n} superblock_backup;\n\ntypedef struct {\n    uint8_t checksum[32];\n    BTRFS_UUID uuid;\n    uint64_t sb_phys_addr;\n    uint64_t flags;\n    uint64_t magic;\n    uint64_t generation;\n    uint64_t root_tree_addr;\n    uint64_t chunk_tree_addr;\n    uint64_t log_tree_addr;\n    uint64_t log_root_transid;\n    uint64_t total_bytes;\n    uint64_t bytes_used;\n    uint64_t root_dir_objectid;\n    uint64_t num_devices;\n    uint32_t sector_size;\n    uint32_t node_size;\n    uint32_t leaf_size;\n    uint32_t stripe_size;\n    uint32_t n;\n    uint64_t chunk_root_generation;\n    uint64_t compat_flags;\n    uint64_t compat_ro_flags;\n    uint64_t incompat_flags;\n    uint16_t csum_type;\n    uint8_t root_level;\n    uint8_t chunk_root_level;\n    uint8_t log_root_level;\n    DEV_ITEM dev_item;\n    char label[MAX_LABEL_SIZE];\n    uint64_t cache_generation;\n    uint64_t uuid_tree_generation;\n    BTRFS_UUID metadata_uuid;\n    uint64_t reserved[28];\n    uint8_t sys_chunk_array[SYS_CHUNK_ARRAY_SIZE];\n    superblock_backup backup[BTRFS_NUM_BACKUP_ROOTS];\n    uint8_t reserved2[565];\n} superblock;\n\n#define BTRFS_TYPE_UNKNOWN   0\n#define BTRFS_TYPE_FILE      1\n#define BTRFS_TYPE_DIRECTORY 2\n#define BTRFS_TYPE_CHARDEV   3\n#define BTRFS_TYPE_BLOCKDEV  4\n#define BTRFS_TYPE_FIFO      5\n#define BTRFS_TYPE_SOCKET    6\n#define BTRFS_TYPE_SYMLINK   7\n#define BTRFS_TYPE_EA        8\n\ntypedef struct {\n    KEY key;\n    uint64_t transid;\n    uint16_t m;\n    uint16_t n;\n    uint8_t type;\n    char name[1];\n} DIR_ITEM;\n\ntypedef struct {\n    uint64_t seconds;\n    uint32_t nanoseconds;\n} BTRFS_TIME;\n\ntypedef struct {\n    uint64_t generation;\n    uint64_t transid;\n    uint64_t st_size;\n    uint64_t st_blocks;\n    uint64_t block_group;\n    uint32_t st_nlink;\n    uint32_t st_uid;\n    uint32_t st_gid;\n    uint32_t st_mode;\n    uint64_t st_rdev;\n    uint32_t flags;\n    uint32_t flags_ro;\n    uint64_t sequence;\n    uint8_t reserved[32];\n    BTRFS_TIME st_atime;\n    BTRFS_TIME st_ctime;\n    BTRFS_TIME st_mtime;\n    BTRFS_TIME otime;\n} INODE_ITEM;\n\nstatic_assert(sizeof(INODE_ITEM) == 0xa0, \"INODE_ITEM has wrong size\");\n\ntypedef struct {\n    INODE_ITEM inode;\n    uint64_t generation;\n    uint64_t objid;\n    uint64_t block_number;\n    uint64_t byte_limit;\n    uint64_t bytes_used;\n    uint64_t last_snapshot_generation;\n    uint64_t flags;\n    uint32_t num_references;\n    KEY drop_progress;\n    uint8_t drop_level;\n    uint8_t root_level;\n    uint64_t generation2;\n    BTRFS_UUID uuid;\n    BTRFS_UUID parent_uuid;\n    BTRFS_UUID received_uuid;\n    uint64_t ctransid;\n    uint64_t otransid;\n    uint64_t stransid;\n    uint64_t rtransid;\n    BTRFS_TIME ctime;\n    BTRFS_TIME otime;\n    BTRFS_TIME stime;\n    BTRFS_TIME rtime;\n    uint64_t reserved[8];\n} ROOT_ITEM;\n\ntypedef struct {\n    uint64_t size;\n    uint64_t root_id;\n    uint64_t stripe_length;\n    uint64_t type;\n    uint32_t opt_io_alignment;\n    uint32_t opt_io_width;\n    uint32_t sector_size;\n    uint16_t num_stripes;\n    uint16_t sub_stripes;\n} CHUNK_ITEM;\n\ntypedef struct {\n    uint64_t dev_id;\n    uint64_t offset;\n    BTRFS_UUID dev_uuid;\n} CHUNK_ITEM_STRIPE;\n\ntypedef struct {\n    uint64_t generation;\n    uint64_t decoded_size;\n    uint8_t compression;\n    uint8_t encryption;\n    uint16_t encoding;\n    uint8_t type;\n    uint8_t data[1];\n} EXTENT_DATA;\n\ntypedef struct {\n    uint64_t address;\n    uint64_t size;\n    uint64_t offset;\n    uint64_t num_bytes;\n} EXTENT_DATA2;\n\ntypedef struct {\n    uint64_t index;\n    uint16_t n;\n    char name[1];\n} INODE_REF;\n\ntypedef struct {\n    uint64_t dir;\n    uint64_t index;\n    uint16_t n;\n    char name[1];\n} INODE_EXTREF;\n\n#define EXTENT_ITEM_DATA            0x001\n#define EXTENT_ITEM_TREE_BLOCK      0x002\n#define EXTENT_ITEM_SHARED_BACKREFS 0x100\n\ntypedef struct {\n    uint64_t refcount;\n    uint64_t generation;\n    uint64_t flags;\n} EXTENT_ITEM;\n\ntypedef struct {\n    KEY firstitem;\n    uint8_t level;\n} EXTENT_ITEM2;\n\ntypedef struct {\n    uint32_t refcount;\n} EXTENT_ITEM_V0;\n\ntypedef struct {\n    EXTENT_ITEM extent_item;\n    KEY firstitem;\n    uint8_t level;\n} EXTENT_ITEM_TREE;\n\ntypedef struct {\n    uint64_t offset;\n} TREE_BLOCK_REF;\n\ntypedef struct {\n    uint64_t root;\n    uint64_t objid;\n    uint64_t offset;\n    uint32_t count;\n} EXTENT_DATA_REF;\n\ntypedef struct {\n    uint64_t used;\n    uint64_t chunk_tree;\n    uint64_t flags;\n} BLOCK_GROUP_ITEM;\n\ntypedef struct {\n    uint64_t root;\n    uint64_t gen;\n    uint64_t objid;\n    uint32_t count;\n} EXTENT_REF_V0;\n\ntypedef struct {\n    uint64_t offset;\n} SHARED_BLOCK_REF;\n\ntypedef struct {\n    uint64_t offset;\n    uint32_t count;\n} SHARED_DATA_REF;\n\n#define FREE_SPACE_EXTENT 1\n#define FREE_SPACE_BITMAP 2\n\ntypedef struct {\n    uint64_t offset;\n    uint64_t size;\n    uint8_t type;\n} FREE_SPACE_ENTRY;\n\ntypedef struct {\n    KEY key;\n    uint64_t generation;\n    uint64_t num_entries;\n    uint64_t num_bitmaps;\n} FREE_SPACE_ITEM;\n\ntypedef struct {\n    uint64_t dir;\n    uint64_t index;\n    uint16_t n;\n    char name[1];\n} ROOT_REF;\n\ntypedef struct {\n    uint64_t chunktree;\n    uint64_t objid;\n    uint64_t address;\n    uint64_t length;\n    BTRFS_UUID chunktree_uuid;\n} DEV_EXTENT;\n\n#define BALANCE_FLAGS_DATA          0x1\n#define BALANCE_FLAGS_SYSTEM        0x2\n#define BALANCE_FLAGS_METADATA      0x4\n\n#define BALANCE_ARGS_FLAGS_PROFILES         0x001\n#define BALANCE_ARGS_FLAGS_USAGE            0x002\n#define BALANCE_ARGS_FLAGS_DEVID            0x004\n#define BALANCE_ARGS_FLAGS_DRANGE           0x008\n#define BALANCE_ARGS_FLAGS_VRANGE           0x010\n#define BALANCE_ARGS_FLAGS_LIMIT            0x020\n#define BALANCE_ARGS_FLAGS_LIMIT_RANGE      0x040\n#define BALANCE_ARGS_FLAGS_STRIPES_RANGE    0x080\n#define BALANCE_ARGS_FLAGS_CONVERT          0x100\n#define BALANCE_ARGS_FLAGS_SOFT             0x200\n#define BALANCE_ARGS_FLAGS_USAGE_RANGE      0x400\n\ntypedef struct {\n    uint64_t profiles;\n\n    union {\n            uint64_t usage;\n            struct {\n                    uint32_t usage_start;\n                    uint32_t usage_end;\n            };\n    };\n\n    uint64_t devid;\n    uint64_t drange_start;\n    uint64_t drange_end;\n    uint64_t vrange_start;\n    uint64_t vrange_end;\n    uint64_t convert;\n    uint64_t flags;\n\n    union {\n            uint64_t limit;\n            struct {\n                    uint32_t limit_start;\n                    uint32_t limit_end;\n            };\n    };\n\n    uint32_t stripes_start;\n    uint32_t stripes_end;\n    uint8_t reserved[48];\n} BALANCE_ARGS;\n\ntypedef struct {\n    uint64_t flags;\n    BALANCE_ARGS data;\n    BALANCE_ARGS metadata;\n    BALANCE_ARGS system;\n    uint8_t reserved[32];\n} BALANCE_ITEM;\n\n#define BTRFS_FREE_SPACE_USING_BITMAPS      1\n\ntypedef struct {\n    uint32_t count;\n    uint32_t flags;\n} FREE_SPACE_INFO;\n\n#define BTRFS_DEV_STAT_WRITE_ERRORS          0\n#define BTRFS_DEV_STAT_READ_ERRORS           1\n#define BTRFS_DEV_STAT_FLUSH_ERRORS          2\n#define BTRFS_DEV_STAT_CORRUPTION_ERRORS     3\n#define BTRFS_DEV_STAT_GENERATION_ERRORS     4\n\n#define BTRFS_SEND_CMD_SUBVOL          1\n#define BTRFS_SEND_CMD_SNAPSHOT        2\n#define BTRFS_SEND_CMD_MKFILE          3\n#define BTRFS_SEND_CMD_MKDIR           4\n#define BTRFS_SEND_CMD_MKNOD           5\n#define BTRFS_SEND_CMD_MKFIFO          6\n#define BTRFS_SEND_CMD_MKSOCK          7\n#define BTRFS_SEND_CMD_SYMLINK         8\n#define BTRFS_SEND_CMD_RENAME          9\n#define BTRFS_SEND_CMD_LINK           10\n#define BTRFS_SEND_CMD_UNLINK         11\n#define BTRFS_SEND_CMD_RMDIR          12\n#define BTRFS_SEND_CMD_SET_XATTR      13\n#define BTRFS_SEND_CMD_REMOVE_XATTR   14\n#define BTRFS_SEND_CMD_WRITE          15\n#define BTRFS_SEND_CMD_CLONE          16\n#define BTRFS_SEND_CMD_TRUNCATE       17\n#define BTRFS_SEND_CMD_CHMOD          18\n#define BTRFS_SEND_CMD_CHOWN          19\n#define BTRFS_SEND_CMD_UTIMES         20\n#define BTRFS_SEND_CMD_END            21\n#define BTRFS_SEND_CMD_UPDATE_EXTENT  22\n\n#define BTRFS_SEND_TLV_UUID             1\n#define BTRFS_SEND_TLV_TRANSID          2\n#define BTRFS_SEND_TLV_INODE            3\n#define BTRFS_SEND_TLV_SIZE             4\n#define BTRFS_SEND_TLV_MODE             5\n#define BTRFS_SEND_TLV_UID              6\n#define BTRFS_SEND_TLV_GID              7\n#define BTRFS_SEND_TLV_RDEV             8\n#define BTRFS_SEND_TLV_CTIME            9\n#define BTRFS_SEND_TLV_MTIME           10\n#define BTRFS_SEND_TLV_ATIME           11\n#define BTRFS_SEND_TLV_OTIME           12\n#define BTRFS_SEND_TLV_XATTR_NAME      13\n#define BTRFS_SEND_TLV_XATTR_DATA      14\n#define BTRFS_SEND_TLV_PATH            15\n#define BTRFS_SEND_TLV_PATH_TO         16\n#define BTRFS_SEND_TLV_PATH_LINK       17\n#define BTRFS_SEND_TLV_OFFSET          18\n#define BTRFS_SEND_TLV_DATA            19\n#define BTRFS_SEND_TLV_CLONE_UUID      20\n#define BTRFS_SEND_TLV_CLONE_CTRANSID  21\n#define BTRFS_SEND_TLV_CLONE_PATH      22\n#define BTRFS_SEND_TLV_CLONE_OFFSET    23\n#define BTRFS_SEND_TLV_CLONE_LENGTH    24\n\n#define BTRFS_SEND_MAGIC \"btrfs-stream\"\n\ntypedef struct {\n    uint8_t magic[13];\n    uint32_t version;\n} btrfs_send_header;\n\ntypedef struct {\n    uint32_t length;\n    uint16_t cmd;\n    uint32_t csum;\n} btrfs_send_command;\n\ntypedef struct {\n    uint16_t type;\n    uint16_t length;\n} btrfs_send_tlv;\n\n#pragma pack(pop)\n"
  },
  {
    "path": "src/btrfs.inf",
    "content": ";;;\r\n;;; WinBtrfs\r\n;;;\r\n;;;\r\n;;; Copyright (c) 2016-24 Mark Harmstone\r\n;;;\r\n\r\n[Version]\r\nSignature   = \"$Windows NT$\"\r\nClass       = Volume\r\nClassGuid   = {71a27cdd-812a-11d0-bec7-08002be2092f}\r\nProvider    = %Me%\r\nDriverVer   = 03/15/2024,1.9.0.0\r\nCatalogFile = btrfs.cat\r\n\r\n[DestinationDirs]\r\nBtrfs.DriverFiles       = 12            ;%windir%\\system32\\drivers\r\nBtrfs.DllFiles          = 11            ;%windir%\\system32\r\n\r\n;;\r\n;; Default install sections\r\n;;\r\n\r\n[DefaultInstall.NTamd64]\r\nOptionDesc  = %ServiceDescription%\r\nCopyFiles   = Btrfs.DriverFiles,Btrfs.DllFiles\r\nAddReg      = shellbtrfs_AddReg\r\nCopyINF     = btrfs-vol.inf\r\n\r\n[DefaultInstall.NTx86]\r\nOptionDesc  = %ServiceDescription%\r\nCopyFiles   = Btrfs.DriverFiles,Btrfs.DllFiles\r\nAddReg      = shellbtrfs_AddReg\r\nCopyINF     = btrfs-vol.inf\r\n\r\n[DefaultInstall.NTarm]\r\nOptionDesc  = %ServiceDescription%\r\nCopyFiles   = Btrfs.DriverFiles,Btrfs.DllFiles\r\nAddReg      = shellbtrfs_AddReg\r\nCopyINF     = btrfs-vol.inf\r\n\r\n[DefaultInstall.NTarm64]\r\nOptionDesc  = %ServiceDescription%\r\nCopyFiles   = Btrfs.DriverFiles,Btrfs.DllFiles\r\nAddReg      = shellbtrfs_AddReg\r\nCopyINF     = btrfs-vol.inf\r\n\r\n[DefaultInstall.NTamd64.Services]\r\nAddService  = %ServiceName%,0x802,Btrfs.Service\r\n\r\n[DefaultInstall.NTx86.Services]\r\nAddService  = %ServiceName%,0x802,Btrfs.Service\r\n\r\n[DefaultInstall.NTarm.Services]\r\nAddService  = %ServiceName%,0x802,Btrfs.Service\r\n\r\n[DefaultInstall.NTarm64.Services]\r\nAddService  = %ServiceName%,0x802,Btrfs.Service\r\n\r\n;\r\n; Services Section\r\n;\r\n\r\n[Btrfs.Service]\r\nDisplayName      = %ServiceName%\r\nDescription      = %ServiceDescription%\r\nServiceBinary    = %12%\\%DriverName%.sys    ;%windir%\\system32\\drivers\\\r\nServiceType      = 1\r\nStartType        = 1                        ;SERVICE_SYSTEM_START\r\nErrorControl     = 1\r\nLoadOrderGroup   = \"File System\"\r\n\r\n;\r\n; Copy Files\r\n;\r\n\r\n[Btrfs.DriverFiles]\r\n%DriverName%.sys\r\n\r\n[Btrfs.DllFiles]\r\nshellbtrfs.dll\r\nubtrfs.dll\r\nmkbtrfs.exe\r\n\r\n[SourceDisksFiles]\r\nbtrfs.sys = 1,,\r\nshellbtrfs.dll = 1,,\r\nubtrfs.dll = 1,,\r\nmkbtrfs.exe = 1,,\r\n\r\n[SourceDisksNames.x86]\r\n1 = %DiskId1%,,,\\x86\r\n\r\n[SourceDisksNames.amd64]\r\n1 = %DiskId1%,,,\\amd64\r\n\r\n[SourceDisksNames.arm]\r\n1 = %DiskId1%,,,\\arm\r\n\r\n[SourceDisksNames.arm64]\r\n1 = %DiskId1%,,,\\aarch64\r\n\r\n[shellbtrfs_AddReg]\r\nHKCR,*\\ShellEx\\PropertySheetHandlers\\WinBtrfs,,,\"{2690B74F-F353-422D-BB12-401581EEF8F2}\"\r\nHKCR,CLSID\\{2690B74F-F353-422D-BB12-401581EEF8F0},,,\"WinBtrfs shell extension (icon handler)\"\r\nHKCR,CLSID\\{2690B74F-F353-422D-BB12-401581EEF8F0}\\InprocServer32,,%REG_EXPAND_SZ%,\"%%SystemRoot%%\\System32\\shellbtrfs.dll\"\r\nHKCR,CLSID\\{2690B74F-F353-422D-BB12-401581EEF8F0}\\InprocServer32,ThreadingModel,,\"Apartment\"\r\nHKCR,CLSID\\{2690B74F-F353-422D-BB12-401581EEF8F1},,,\"WinBtrfs shell extension (context menu)\"\r\nHKCR,CLSID\\{2690B74F-F353-422D-BB12-401581EEF8F1}\\InprocServer32,,%REG_EXPAND_SZ%,\"%%SystemRoot%%\\System32\\shellbtrfs.dll\"\r\nHKCR,CLSID\\{2690B74F-F353-422D-BB12-401581EEF8F1}\\InprocServer32,ThreadingModel,,\"Apartment\"\r\nHKCR,CLSID\\{2690B74F-F353-422D-BB12-401581EEF8F2},,,\"WinBtrfs shell extension (property sheet)\"\r\nHKCR,CLSID\\{2690B74F-F353-422D-BB12-401581EEF8F2}\\InprocServer32,,%REG_EXPAND_SZ%,\"%%SystemRoot%%\\System32\\shellbtrfs.dll\"\r\nHKCR,CLSID\\{2690B74F-F353-422D-BB12-401581EEF8F2}\\InprocServer32,ThreadingModel,,\"Apartment\"\r\nHKCR,CLSID\\{2690B74F-F353-422D-BB12-401581EEF8F3},,,\"WinBtrfs shell extension (volume property sheet)\"\r\nHKCR,CLSID\\{2690B74F-F353-422D-BB12-401581EEF8F3}\\InprocServer32,,%REG_EXPAND_SZ%,\"%%SystemRoot%%\\System32\\shellbtrfs.dll\"\r\nHKCR,CLSID\\{2690B74F-F353-422D-BB12-401581EEF8F3}\\InprocServer32,ThreadingModel,,\"Apartment\"\r\nHKCR,Directory\\Background\\ShellEx\\ContextMenuHandlers\\WinBtrfs,,,\"{2690B74F-F353-422D-BB12-401581EEF8F1}\"\r\nHKCR,Drive\\ShellEx\\PropertySheetHandlers\\WinBtrfs,,,\"{2690B74F-F353-422D-BB12-401581EEF8F3}\"\r\nHKCR,Folder\\ShellEx\\ContextMenuHandlers\\WinBtrfs,,,\"{2690B74F-F353-422D-BB12-401581EEF8F1}\"\r\nHKCR,Folder\\ShellEx\\PropertySheetHandlers\\WinBtrfs,,,\"{2690B74F-F353-422D-BB12-401581EEF8F2}\"\r\n;HKLM,Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\ShellIconOverlayIdentifiers\\WinBtrfs,,,\"{2690B74F-F353-422D-BB12-401581EEF8F0}\"\r\n\r\n;;\r\n;; String Section\r\n;;\r\n\r\n[Strings]\r\nMe                      = \"Mark Harmstone\"\r\nServiceDescription      = \"Btrfs driver\"\r\nServiceName             = \"btrfs\"\r\nDriverName              = \"btrfs\"\r\nDiskId1                 = \"Btrfs Device Installation Disk\"\r\nVolumeName              = \"Btrfs volume\"\r\nControllerName          = \"Btrfs controller\"\r\nREG_EXPAND_SZ           = 0x00020000\r\n"
  },
  {
    "path": "src/btrfs.rc.in",
    "content": "// Microsoft Visual C++ generated resource script.\n//\n#include \"@CMAKE_CURRENT_SOURCE_DIR@/src/resource.h\"\n\n#define APSTUDIO_READONLY_SYMBOLS\n/////////////////////////////////////////////////////////////////////////////\n//\n// Generated from the TEXTINCLUDE 2 resource.\n//\n#include <winresrc.h>\n\n/////////////////////////////////////////////////////////////////////////////\n#undef APSTUDIO_READONLY_SYMBOLS\n\n/////////////////////////////////////////////////////////////////////////////\n// English (United Kingdom) resources\n\n#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)\nLANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK\n#pragma code_page(1252)\n\n#ifdef APSTUDIO_INVOKED\n/////////////////////////////////////////////////////////////////////////////\n//\n// TEXTINCLUDE\n//\n\n1 TEXTINCLUDE\nBEGIN\n    \"resource.h\\0\"\nEND\n\n2 TEXTINCLUDE\nBEGIN\n    \"#include \"\"winres.h\"\"\\r\\n\"\n    \"\\0\"\nEND\n\n3 TEXTINCLUDE\nBEGIN\n    \"\\r\\n\"\n    \"\\0\"\nEND\n\n#endif    // APSTUDIO_INVOKED\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// Version\n//\n\nVS_VERSION_INFO VERSIONINFO\n FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0\n PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0\n FILEFLAGSMASK 0x17L\n#ifdef _DEBUG\n FILEFLAGS 0x1L\n#else\n FILEFLAGS 0x0L\n#endif\n FILEOS 0x4L\n FILETYPE 0x1L\n FILESUBTYPE 0x0L\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"080904b0\"\n        BEGIN\n            VALUE \"FileDescription\", \"WinBtrfs\"\n            VALUE \"FileVersion\", \"@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@\"\n            VALUE \"InternalName\", \"btrfs\"\n            VALUE \"LegalCopyright\", \"Copyright (c) Mark Harmstone 2016-24\"\n            VALUE \"OriginalFilename\", \"btrfs.sys\"\n            VALUE \"ProductName\", \"WinBtrfs\"\n            VALUE \"ProductVersion\", \"@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x809, 1200\n    END\nEND\n\n#endif    // English (United Kingdom) resources\n/////////////////////////////////////////////////////////////////////////////\n\n\n\n#ifndef APSTUDIO_INVOKED\n/////////////////////////////////////////////////////////////////////////////\n//\n// Generated from the TEXTINCLUDE 3 resource.\n//\n\n\n/////////////////////////////////////////////////////////////////////////////\n#endif    // not APSTUDIO_INVOKED\n\n"
  },
  {
    "path": "src/btrfs_drv.h",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\n#undef _WIN32_WINNT\n#undef NTDDI_VERSION\n\n#define _WIN32_WINNT 0x0601\n#define NTDDI_VERSION 0x06020000 // Win 8\n#define _CRT_SECURE_NO_WARNINGS\n#define _NO_CRT_STDIO_INLINE\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable:4163)\n#pragma warning(disable:4311)\n#pragma warning(disable:4312)\n#else\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wsign-compare\"\n#pragma GCC diagnostic ignored \"-Wsign-conversion\"\n#endif\n\n#include <ntifs.h>\n#include <ntddk.h>\n#include <mountmgr.h>\n#include <windef.h>\n#include <wdm.h>\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#else\n#pragma GCC diagnostic pop\n#endif\n\n#include <stdio.h>\n#include <stdarg.h>\n#include <stddef.h>\n#include <stdint.h>\n#include <stdbool.h>\n#include \"btrfs.h\"\n#include \"btrfsioctl.h\"\n\n#ifdef _DEBUG\n// #define DEBUG_FCB_REFCOUNTS\n// #define DEBUG_LONG_MESSAGES\n// #define DEBUG_FLUSH_TIMES\n// #define DEBUG_CHUNK_LOCKS\n// #define DEBUG_TRIM_EMULATION\n#define DEBUG_PARANOID\n#endif\n\n#define UNUSED(x) (void)(x)\n\n#define BTRFS_NODE_TYPE_CCB 0x2295\n#define BTRFS_NODE_TYPE_FCB 0x2296\n\n#define ALLOC_TAG 0x7442484D //'MHBt'\n#define ALLOC_TAG_ZLIB 0x7A42484D //'MHBz'\n\n#define UID_NOBODY 65534\n#define GID_NOBODY 65534\n\n#define EA_NTACL \"security.NTACL\"\n#define EA_NTACL_HASH 0x45922146\n\n#define EA_DOSATTRIB \"user.DOSATTRIB\"\n#define EA_DOSATTRIB_HASH 0x914f9939\n\n#define EA_REPARSE \"user.reparse\"\n#define EA_REPARSE_HASH 0xfabad1fe\n\n#define EA_EA \"user.EA\"\n#define EA_EA_HASH 0x8270dd43\n\n#define EA_CASE_SENSITIVE \"user.casesensitive\"\n#define EA_CASE_SENSITIVE_HASH 0x1a9d97d4\n\n#define EA_PROP_COMPRESSION \"btrfs.compression\"\n#define EA_PROP_COMPRESSION_HASH 0x20ccdf69\n\n#define MAX_EXTENT_SIZE 0x8000000 // 128 MB\n#define COMPRESSED_EXTENT_SIZE 0x20000 // 128 KB\n\n#define READ_AHEAD_GRANULARITY COMPRESSED_EXTENT_SIZE // really ought to be a multiple of COMPRESSED_EXTENT_SIZE\n\n#ifndef IO_REPARSE_TAG_LX_SYMLINK\n\n#define IO_REPARSE_TAG_LX_SYMLINK 0xa000001d\n\n#define IO_REPARSE_TAG_AF_UNIX          0x80000023\n#define IO_REPARSE_TAG_LX_FIFO          0x80000024\n#define IO_REPARSE_TAG_LX_CHR           0x80000025\n#define IO_REPARSE_TAG_LX_BLK           0x80000026\n\n#endif\n\n#define BTRFS_VOLUME_PREFIX L\"\\\\Device\\\\Btrfs{\"\n\n#if defined(_MSC_VER) || defined(__clang__)\n#define try __try\n#define except __except\n#define finally __finally\n#define leave __leave\n#else\n#define try if (1)\n#define except(x) if (0 && (x))\n#define finally if (1)\n#define leave\n#endif\n\n#ifndef InterlockedIncrement64\n#define InterlockedIncrement64(a) __sync_add_and_fetch(a, 1)\n#endif\n\n#ifndef FILE_SUPPORTS_BLOCK_REFCOUNTING\n#define FILE_SUPPORTS_BLOCK_REFCOUNTING 0x08000000\n#endif\n\n#ifndef FILE_SUPPORTS_POSIX_UNLINK_RENAME\n#define FILE_SUPPORTS_POSIX_UNLINK_RENAME 0x00000400\n#endif\n\n#ifndef FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL\n#define FILE_DEVICE_ALLOW_APPCONTAINER_TRAVERSAL 0x00020000\n#endif\n\n#ifndef _MSC_VER\ntypedef struct _FILE_ID_128 {\n    UCHAR Identifier[16];\n} FILE_ID_128, *PFILE_ID_128;\n\n#define FILE_CS_FLAG_CASE_SENSITIVE_DIR                 1\n#endif\n\ntypedef struct _DUPLICATE_EXTENTS_DATA {\n    HANDLE FileHandle;\n    LARGE_INTEGER SourceFileOffset;\n    LARGE_INTEGER TargetFileOffset;\n    LARGE_INTEGER ByteCount;\n} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA;\n\n#define FSCTL_DUPLICATE_EXTENTS_TO_FILE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_ACCESS)\n\ntypedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {\n    WORD ChecksumAlgorithm;\n    WORD Reserved;\n    DWORD Flags;\n    DWORD ChecksumChunkSizeInBytes;\n    DWORD ClusterSizeInBytes;\n} FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER;\n\ntypedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {\n    WORD ChecksumAlgorithm;\n    WORD Reserved;\n    DWORD Flags;\n} FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER;\n\n#define FSCTL_GET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 159, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define FSCTL_SET_INTEGRITY_INFORMATION CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 160, METHOD_BUFFERED, FILE_READ_DATA | FILE_WRITE_DATA)\n\n#ifndef _MSC_VER\n#define __drv_aliasesMem\n#define _Dispatch_type_(a)\n#define _Lock_level_order_(a,b)\n#endif\n\n_Create_lock_level_(tree_lock)\n_Create_lock_level_(fcb_lock)\n_Lock_level_order_(tree_lock, fcb_lock)\n\n#define MAX_HASH_SIZE 32\n\nstruct _device_extension;\n\ntypedef struct _fcb_nonpaged {\n    FAST_MUTEX HeaderMutex;\n    SECTION_OBJECT_POINTERS segment_object;\n    ERESOURCE resource;\n    ERESOURCE paging_resource;\n    ERESOURCE dir_children_lock;\n} fcb_nonpaged;\n\nstruct _root;\n\ntypedef struct {\n    uint64_t offset;\n    uint16_t datalen;\n    bool unique;\n    bool ignore;\n    bool inserted;\n    void* csum;\n\n    LIST_ENTRY list_entry;\n\n    EXTENT_DATA extent_data;\n} extent;\n\ntypedef struct {\n    uint64_t parent;\n    uint64_t index;\n    UNICODE_STRING name;\n    ANSI_STRING utf8;\n    LIST_ENTRY list_entry;\n} hardlink;\n\nstruct _file_ref;\n\ntypedef struct {\n    KEY key;\n    uint64_t index;\n    uint8_t type;\n    ANSI_STRING utf8;\n    uint32_t hash;\n    UNICODE_STRING name;\n    uint32_t hash_uc;\n    UNICODE_STRING name_uc;\n    ULONG size;\n    struct _file_ref* fileref;\n    bool root_dir;\n    LIST_ENTRY list_entry_index;\n    LIST_ENTRY list_entry_hash;\n    LIST_ENTRY list_entry_hash_uc;\n} dir_child;\n\nenum prop_compression_type {\n    PropCompression_None,\n    PropCompression_Zlib,\n    PropCompression_LZO,\n    PropCompression_ZSTD\n};\n\ntypedef struct {\n    LIST_ENTRY list_entry;\n    USHORT namelen;\n    USHORT valuelen;\n    bool dirty;\n    char data[1];\n} xattr;\n\ntypedef struct _fcb {\n    FSRTL_ADVANCED_FCB_HEADER Header;\n    struct _fcb_nonpaged* nonpaged;\n    LONG refcount;\n    POOL_TYPE pool_type;\n    struct _device_extension* Vcb;\n    struct _root* subvol;\n    uint64_t inode;\n    uint32_t hash;\n    uint8_t type;\n    INODE_ITEM inode_item;\n    SECURITY_DESCRIPTOR* sd;\n    FILE_LOCK lock;\n    bool deleted;\n    PKTHREAD lazy_writer_thread;\n    ULONG atts;\n    SHARE_ACCESS share_access;\n    bool csum_loaded;\n    LIST_ENTRY extents;\n    ANSI_STRING reparse_xattr;\n    ANSI_STRING ea_xattr;\n    ULONG ealen;\n    LIST_ENTRY hardlinks;\n    struct _file_ref* fileref;\n    bool inode_item_changed;\n    enum prop_compression_type prop_compression;\n    LIST_ENTRY xattrs;\n    bool marked_as_orphan;\n    bool case_sensitive;\n    bool case_sensitive_set;\n    OPLOCK oplock;\n\n    LIST_ENTRY dir_children_index;\n    LIST_ENTRY dir_children_hash;\n    LIST_ENTRY dir_children_hash_uc;\n    LIST_ENTRY** hash_ptrs;\n    LIST_ENTRY** hash_ptrs_uc;\n\n    bool dirty;\n    bool sd_dirty, sd_deleted;\n    bool atts_changed, atts_deleted;\n    bool extents_changed;\n    bool reparse_xattr_changed;\n    bool ea_changed;\n    bool prop_compression_changed;\n    bool xattrs_changed;\n    bool created;\n\n    bool ads;\n    uint32_t adshash;\n    ULONG adsmaxlen;\n    ANSI_STRING adsxattr;\n    ANSI_STRING adsdata;\n\n    LIST_ENTRY list_entry;\n    LIST_ENTRY list_entry_all;\n    LIST_ENTRY list_entry_dirty;\n} fcb;\n\ntypedef struct _file_ref {\n    fcb* fcb;\n    ANSI_STRING oldutf8;\n    uint64_t oldindex;\n    bool delete_on_close;\n    bool posix_delete;\n    bool deleted;\n    bool created;\n    LIST_ENTRY children;\n    LONG refcount;\n    LONG open_count;\n    struct _file_ref* parent;\n    dir_child* dc;\n\n    bool dirty;\n\n    LIST_ENTRY list_entry;\n    LIST_ENTRY list_entry_dirty;\n} file_ref;\n\ntypedef struct {\n    HANDLE thread;\n    struct _ccb* ccb;\n    void* context;\n    KEVENT cleared_event;\n    bool cancelling;\n    LIST_ENTRY list_entry;\n} send_info;\n\ntypedef struct _ccb {\n    USHORT NodeType;\n    CSHORT NodeSize;\n    ULONG disposition;\n    ULONG options;\n    uint64_t query_dir_offset;\n    UNICODE_STRING query_string;\n    bool has_wildcard;\n    bool specific_file;\n    bool manage_volume_privilege;\n    bool allow_extended_dasd_io;\n    bool reserving;\n    ACCESS_MASK access;\n    file_ref* fileref;\n    UNICODE_STRING filename;\n    ULONG ea_index;\n    bool case_sensitive;\n    bool user_set_creation_time;\n    bool user_set_access_time;\n    bool user_set_write_time;\n    bool user_set_change_time;\n    bool lxss;\n    send_info* send;\n    NTSTATUS send_status;\n} ccb;\n\nstruct _device_extension;\n\ntypedef struct {\n    uint64_t address;\n    uint64_t generation;\n    struct _tree* tree;\n} tree_holder;\n\ntypedef struct _tree_data {\n    KEY key;\n    LIST_ENTRY list_entry;\n    bool ignore;\n    bool inserted;\n\n    union {\n        tree_holder treeholder;\n\n        struct {\n            uint16_t size;\n            uint8_t* data;\n        };\n    };\n} tree_data;\n\ntypedef struct {\n    FAST_MUTEX mutex;\n} tree_nonpaged;\n\ntypedef struct _tree {\n    tree_nonpaged* nonpaged;\n    tree_header header;\n    uint32_t hash;\n    bool has_address;\n    uint32_t size;\n    struct _device_extension* Vcb;\n    struct _tree* parent;\n    tree_data* paritem;\n    struct _root* root;\n    LIST_ENTRY itemlist;\n    LIST_ENTRY list_entry;\n    LIST_ENTRY list_entry_hash;\n    uint64_t new_address;\n    bool has_new_address;\n    bool updated_extents;\n    bool write;\n    bool is_unique;\n    bool uniqueness_determined;\n    uint8_t* buf;\n} tree;\n\ntypedef struct {\n    ERESOURCE load_tree_lock;\n} root_nonpaged;\n\ntypedef struct _root {\n    uint64_t id;\n    LONGLONG lastinode; // signed so we can use InterlockedIncrement64\n    tree_holder treeholder;\n    root_nonpaged* nonpaged;\n    ROOT_ITEM root_item;\n    bool dirty;\n    bool received;\n    PEPROCESS reserved;\n    uint64_t parent;\n    LONG send_ops;\n    uint64_t fcbs_version;\n    bool checked_for_orphans;\n    bool dropped;\n    LIST_ENTRY fcbs;\n    LIST_ENTRY* fcbs_ptrs[256];\n    LIST_ENTRY list_entry;\n    LIST_ENTRY list_entry_dirty;\n} root;\n\nenum batch_operation {\n    Batch_Delete,\n    Batch_DeleteInode,\n    Batch_DeleteDirItem,\n    Batch_DeleteInodeRef,\n    Batch_DeleteInodeExtRef,\n    Batch_DeleteXattr,\n    Batch_DeleteExtentData,\n    Batch_DeleteFreeSpace,\n    Batch_Insert,\n    Batch_SetXattr,\n    Batch_DirItem,\n    Batch_InodeRef,\n    Batch_InodeExtRef,\n};\n\ntypedef struct {\n    KEY key;\n    void* data;\n    uint16_t datalen;\n    enum batch_operation operation;\n    LIST_ENTRY list_entry;\n} batch_item;\n\ntypedef struct {\n    KEY key;\n    LIST_ENTRY items;\n    unsigned int num_items;\n    LIST_ENTRY list_entry;\n} batch_item_ind;\n\ntypedef struct {\n    root* r;\n    LIST_ENTRY items_ind;\n    LIST_ENTRY list_entry;\n} batch_root;\n\ntypedef struct {\n    tree* tree;\n    tree_data* item;\n} traverse_ptr;\n\ntypedef struct _root_cache {\n    root* root;\n    struct _root_cache* next;\n} root_cache;\n\ntypedef struct {\n    uint64_t address;\n    uint64_t size;\n    LIST_ENTRY list_entry;\n    LIST_ENTRY list_entry_size;\n} space;\n\ntypedef struct {\n    PDEVICE_OBJECT devobj;\n    PFILE_OBJECT fileobj;\n    DEV_ITEM devitem;\n    bool removable;\n    bool seeding;\n    bool readonly;\n    bool reloc;\n    bool trim;\n    bool can_flush;\n    ULONG change_count;\n    ULONG disk_num;\n    ULONG part_num;\n    uint64_t stats[5];\n    bool stats_changed;\n    LIST_ENTRY space;\n    LIST_ENTRY list_entry;\n    ULONG num_trim_entries;\n    LIST_ENTRY trim_list;\n} device;\n\ntypedef struct {\n    uint64_t start;\n    uint64_t length;\n    PETHREAD thread;\n    LIST_ENTRY list_entry;\n} range_lock;\n\ntypedef struct {\n    uint64_t address;\n    ULONG* bmparr;\n    ULONG bmplen;\n    RTL_BITMAP bmp;\n    LIST_ENTRY list_entry;\n    uint8_t data[1];\n} partial_stripe;\n\ntypedef struct {\n    CHUNK_ITEM* chunk_item;\n    uint16_t size;\n    uint64_t offset;\n    uint64_t used;\n    uint64_t oldused;\n    device** devices;\n    fcb* cache;\n    fcb* old_cache;\n    LIST_ENTRY space;\n    LIST_ENTRY space_size;\n    LIST_ENTRY deleting;\n    LIST_ENTRY changed_extents;\n    LIST_ENTRY range_locks;\n    ERESOURCE range_locks_lock;\n    KEVENT range_locks_event;\n    ERESOURCE lock;\n    ERESOURCE changed_extents_lock;\n    bool created;\n    bool readonly;\n    bool reloc;\n    bool last_alloc_set;\n    bool cache_loaded;\n    bool changed;\n    bool space_changed;\n    uint64_t last_alloc;\n    uint16_t last_stripe;\n    LIST_ENTRY partial_stripes;\n    ERESOURCE partial_stripes_lock;\n    ULONG balance_num;\n\n    LIST_ENTRY list_entry;\n    LIST_ENTRY list_entry_balance;\n} chunk;\n\ntypedef struct {\n    uint64_t address;\n    uint64_t size;\n    uint64_t old_size;\n    uint64_t count;\n    uint64_t old_count;\n    bool no_csum;\n    bool superseded;\n    LIST_ENTRY refs;\n    LIST_ENTRY old_refs;\n    LIST_ENTRY list_entry;\n} changed_extent;\n\ntypedef struct {\n    uint8_t type;\n\n    union {\n        EXTENT_DATA_REF edr;\n        SHARED_DATA_REF sdr;\n    };\n\n    LIST_ENTRY list_entry;\n} changed_extent_ref;\n\ntypedef struct {\n    KEY key;\n    void* data;\n    USHORT size;\n    LIST_ENTRY list_entry;\n} sys_chunk;\n\nenum calc_thread_type {\n    calc_thread_crc32c,\n    calc_thread_xxhash,\n    calc_thread_sha256,\n    calc_thread_blake2,\n    calc_thread_decomp_zlib,\n    calc_thread_decomp_lzo,\n    calc_thread_decomp_zstd,\n    calc_thread_comp_zlib,\n    calc_thread_comp_lzo,\n    calc_thread_comp_zstd,\n};\n\ntypedef struct {\n    LIST_ENTRY list_entry;\n    void* in;\n    void* out;\n    unsigned int inlen, outlen, off, space_left;\n    LONG left, not_started;\n    KEVENT event;\n    enum calc_thread_type type;\n    NTSTATUS Status;\n} calc_job;\n\ntypedef struct {\n    PDEVICE_OBJECT DeviceObject;\n    HANDLE handle;\n    KEVENT finished;\n    unsigned int number;\n    bool quit;\n} drv_calc_thread;\n\ntypedef struct {\n    ULONG num_threads;\n    LIST_ENTRY job_list;\n    KSPIN_LOCK spinlock;\n    drv_calc_thread* threads;\n    KEVENT event;\n} drv_calc_threads;\n\ntypedef struct {\n    bool ignore;\n    bool compress;\n    bool compress_force;\n    uint8_t compress_type;\n    bool readonly;\n    uint32_t zlib_level;\n    uint32_t zstd_level;\n    uint32_t flush_interval;\n    uint32_t max_inline;\n    uint64_t subvol_id;\n    bool skip_balance;\n    bool no_barrier;\n    bool no_trim;\n    bool clear_cache;\n    bool allow_degraded;\n    bool no_root_dir;\n    bool nodatacow;\n} mount_options;\n\n#define VCB_TYPE_FS         1\n#define VCB_TYPE_CONTROL    2\n#define VCB_TYPE_VOLUME     3\n#define VCB_TYPE_PDO        4\n#define VCB_TYPE_BUS        5\n\n#define BALANCE_OPTS_DATA       0\n#define BALANCE_OPTS_METADATA   1\n#define BALANCE_OPTS_SYSTEM     2\n\ntypedef struct {\n    HANDLE thread;\n    uint64_t total_chunks;\n    uint64_t chunks_left;\n    btrfs_balance_opts opts[3];\n    bool paused;\n    bool stopping;\n    bool removing;\n    bool shrinking;\n    bool dev_readonly;\n    ULONG balance_num;\n    NTSTATUS status;\n    KEVENT event;\n    KEVENT finished;\n} balance_info;\n\ntypedef struct {\n    uint64_t address;\n    uint64_t device;\n    bool recovered;\n    bool is_metadata;\n    bool parity;\n    LIST_ENTRY list_entry;\n\n    union {\n        struct {\n            uint64_t subvol;\n            uint64_t offset;\n            uint16_t filename_length;\n            WCHAR filename[1];\n        } data;\n\n        struct {\n            uint64_t root;\n            uint8_t level;\n            KEY firstitem;\n        } metadata;\n    };\n} scrub_error;\n\ntypedef struct {\n    HANDLE thread;\n    ERESOURCE stats_lock;\n    KEVENT event;\n    KEVENT finished;\n    bool stopping;\n    bool paused;\n    LARGE_INTEGER start_time;\n    LARGE_INTEGER finish_time;\n    LARGE_INTEGER resume_time;\n    LARGE_INTEGER duration;\n    uint64_t total_chunks;\n    uint64_t chunks_left;\n    uint64_t data_scrubbed;\n    NTSTATUS error;\n    ULONG num_errors;\n    LIST_ENTRY errors;\n} scrub_info;\n\nstruct _volume_device_extension;\n\ntypedef struct _device_extension {\n    uint32_t type;\n    mount_options options;\n    PVPB Vpb;\n    PDEVICE_OBJECT devobj;\n    struct _volume_device_extension* vde;\n    LIST_ENTRY devices;\n#ifdef DEBUG_CHUNK_LOCKS\n    LONG chunk_locks_held;\n#endif\n    uint64_t devices_loaded;\n    superblock superblock;\n    unsigned int sector_shift;\n    unsigned int csum_size;\n    bool readonly;\n    bool removing;\n    bool locked;\n    bool lock_paused_balance;\n    bool disallow_dismount;\n    LONG page_file_count;\n    bool trim;\n    PFILE_OBJECT locked_fileobj;\n    fcb* volume_fcb;\n    fcb* dummy_fcb;\n    file_ref* root_fileref;\n    LONG open_files;\n    _Has_lock_level_(fcb_lock) ERESOURCE fcb_lock;\n    ERESOURCE fileref_lock;\n    ERESOURCE load_lock;\n    _Has_lock_level_(tree_lock) ERESOURCE tree_lock;\n    PNOTIFY_SYNC NotifySync;\n    LIST_ENTRY DirNotifyList;\n    bool need_write;\n    bool stats_changed;\n    uint64_t data_flags;\n    uint64_t metadata_flags;\n    uint64_t system_flags;\n    LIST_ENTRY roots;\n    LIST_ENTRY drop_roots;\n    root* chunk_root;\n    root* root_root;\n    root* extent_root;\n    root* checksum_root;\n    root* dev_root;\n    root* uuid_root;\n    root* data_reloc_root;\n    root* space_root;\n    root* block_group_root;\n    bool log_to_phys_loaded;\n    bool chunk_usage_found;\n    LIST_ENTRY sys_chunks;\n    LIST_ENTRY chunks;\n    LIST_ENTRY trees;\n    LIST_ENTRY trees_hash;\n    LIST_ENTRY* trees_ptrs[256];\n    FAST_MUTEX trees_list_mutex;\n    LIST_ENTRY all_fcbs;\n    LIST_ENTRY dirty_fcbs;\n    ERESOURCE dirty_fcbs_lock;\n    LIST_ENTRY dirty_filerefs;\n    ERESOURCE dirty_filerefs_lock;\n    LIST_ENTRY dirty_subvols;\n    ERESOURCE dirty_subvols_lock;\n    ERESOURCE chunk_lock;\n    HANDLE flush_thread_handle;\n    KTIMER flush_thread_timer;\n    KEVENT flush_thread_finished;\n    drv_calc_threads calcthreads;\n    balance_info balance;\n    scrub_info scrub;\n    ERESOURCE send_load_lock;\n    LONG running_sends;\n    LIST_ENTRY send_ops;\n    PFILE_OBJECT root_file;\n    PAGED_LOOKASIDE_LIST tree_data_lookaside;\n    PAGED_LOOKASIDE_LIST traverse_ptr_lookaside;\n    PAGED_LOOKASIDE_LIST batch_item_lookaside;\n    PAGED_LOOKASIDE_LIST fileref_lookaside;\n    PAGED_LOOKASIDE_LIST fcb_lookaside;\n    PAGED_LOOKASIDE_LIST name_bit_lookaside;\n    NPAGED_LOOKASIDE_LIST range_lock_lookaside;\n    NPAGED_LOOKASIDE_LIST fcb_np_lookaside;\n    LIST_ENTRY list_entry;\n} device_extension;\n\ntypedef struct {\n    uint32_t type;\n} control_device_extension;\n\ntypedef struct {\n    uint32_t type;\n    PDEVICE_OBJECT buspdo;\n    PDEVICE_OBJECT attached_device;\n    UNICODE_STRING bus_name;\n} bus_device_extension;\n\ntypedef struct {\n    BTRFS_UUID uuid;\n    uint64_t devid;\n    uint64_t generation;\n    PDEVICE_OBJECT devobj;\n    PFILE_OBJECT fileobj;\n    UNICODE_STRING pnp_name;\n    uint64_t size;\n    bool seeding;\n    bool had_drive_letter;\n    void* notification_entry;\n    ULONG disk_num;\n    ULONG part_num;\n    bool boot_volume;\n    LIST_ENTRY list_entry;\n} volume_child;\n\nstruct pdo_device_extension;\n\ntypedef struct _volume_device_extension {\n    uint32_t type;\n    UNICODE_STRING name;\n    PDEVICE_OBJECT device;\n    PDEVICE_OBJECT mounted_device;\n    PDEVICE_OBJECT pdo;\n    struct pdo_device_extension* pdode;\n    UNICODE_STRING bus_name;\n    PDEVICE_OBJECT attached_device;\n    bool removing;\n    bool dead;\n    LONG open_count;\n} volume_device_extension;\n\ntypedef struct pdo_device_extension {\n    uint32_t type;\n    BTRFS_UUID uuid;\n    volume_device_extension* vde;\n    PDEVICE_OBJECT pdo;\n    bool removable;\n    bool dont_report;\n\n    uint64_t num_children;\n    uint64_t children_loaded;\n    ERESOURCE child_lock;\n    LIST_ENTRY children;\n\n    LIST_ENTRY list_entry;\n} pdo_device_extension;\n\ntypedef struct {\n    LIST_ENTRY listentry;\n    PSID sid;\n    uint32_t uid;\n} uid_map;\n\ntypedef struct {\n    LIST_ENTRY listentry;\n    PSID sid;\n    uint32_t gid;\n} gid_map;\n\nenum write_data_status {\n    WriteDataStatus_Pending,\n    WriteDataStatus_Success,\n    WriteDataStatus_Error,\n    WriteDataStatus_Cancelling,\n    WriteDataStatus_Cancelled,\n    WriteDataStatus_Ignore\n};\n\nstruct _write_data_context;\n\ntypedef struct {\n    struct _write_data_context* context;\n    uint8_t* buf;\n    PMDL mdl;\n    device* device;\n    PIRP Irp;\n    IO_STATUS_BLOCK iosb;\n    enum write_data_status status;\n    LIST_ENTRY list_entry;\n} write_data_stripe;\n\ntypedef struct _write_data_context {\n    KEVENT Event;\n    LIST_ENTRY stripes;\n    LONG stripes_left;\n    bool need_wait;\n    uint8_t *parity1, *parity2, *scratch;\n    PMDL mdl, parity1_mdl, parity2_mdl;\n} write_data_context;\n\ntypedef struct {\n    uint64_t address;\n    uint32_t length;\n    uint8_t* data;\n    chunk* c;\n    bool allocated;\n    LIST_ENTRY list_entry;\n} tree_write;\n\ntypedef struct {\n    UNICODE_STRING us;\n    LIST_ENTRY list_entry;\n} name_bit;\n\n_Requires_lock_not_held_(Vcb->fcb_lock)\n_Acquires_shared_lock_(Vcb->fcb_lock)\nstatic __inline void acquire_fcb_lock_shared(device_extension* Vcb) {\n    ExAcquireResourceSharedLite(&Vcb->fcb_lock, true);\n}\n\n_Requires_lock_not_held_(Vcb->fcb_lock)\n_Acquires_exclusive_lock_(Vcb->fcb_lock)\nstatic __inline void acquire_fcb_lock_exclusive(device_extension* Vcb) {\n    ExAcquireResourceExclusiveLite(&Vcb->fcb_lock, true);\n}\n\n_Requires_lock_held_(Vcb->fcb_lock)\n_Releases_lock_(Vcb->fcb_lock)\nstatic __inline void release_fcb_lock(device_extension* Vcb) {\n    ExReleaseResourceLite(&Vcb->fcb_lock);\n}\n\nstatic __inline void* map_user_buffer(PIRP Irp, ULONG priority) {\n    if (!Irp->MdlAddress) {\n        return Irp->UserBuffer;\n    } else {\n        return MmGetSystemAddressForMdlSafe(Irp->MdlAddress, priority);\n    }\n}\n\nstatic __inline uint64_t unix_time_to_win(BTRFS_TIME* t) {\n    return (t->seconds * 10000000) + (t->nanoseconds / 100) + 116444736000000000;\n}\n\nstatic __inline void win_time_to_unix(LARGE_INTEGER t, BTRFS_TIME* out) {\n    ULONGLONG l = (ULONGLONG)t.QuadPart - 116444736000000000;\n\n    out->seconds = l / 10000000;\n    out->nanoseconds = (uint32_t)((l % 10000000) * 100);\n}\n\n_Post_satisfies_(*stripe>=0&&*stripe<num_stripes)\nstatic __inline void get_raid0_offset(_In_ uint64_t off, _In_ uint64_t stripe_length, _In_ uint16_t num_stripes, _Out_ uint64_t* stripeoff, _Out_ uint16_t* stripe) {\n    uint64_t initoff, startoff;\n\n    startoff = off % (num_stripes * stripe_length);\n    initoff = (off / (num_stripes * stripe_length)) * stripe_length;\n\n    *stripe = (uint16_t)(startoff / stripe_length);\n    *stripeoff = initoff + startoff - (*stripe * stripe_length);\n}\n\n/* We only have 64 bits for a file ID, which isn't technically enough to be\n * unique on Btrfs. We fudge it by having three bytes for the subvol and\n * five for the inode, which should be good enough.\n * Inodes are also 64 bits on Linux, but the Linux driver seems to get round\n * this by tricking it into thinking subvols are separate volumes. */\nstatic __inline uint64_t make_file_id(root* r, uint64_t inode) {\n    return (r->id << 40) | (inode & 0xffffffffff);\n}\n\n#define keycmp(key1, key2)\\\n    ((key1.obj_id < key2.obj_id) ? -1 :\\\n    ((key1.obj_id > key2.obj_id) ? 1 :\\\n    ((key1.obj_type < key2.obj_type) ? -1 :\\\n    ((key1.obj_type > key2.obj_type) ? 1 :\\\n    ((key1.offset < key2.offset) ? -1 :\\\n    ((key1.offset > key2.offset) ? 1 :\\\n    0))))))\n\n_Post_satisfies_(return>=n)\n__inline static uint64_t sector_align(_In_ uint64_t n, _In_ uint64_t a) {\n    if (n & (a - 1))\n        n = (n + a) & ~(a - 1);\n\n    return n;\n}\n\n__inline static bool is_subvol_readonly(root* r, PIRP Irp) {\n    if (!(r->root_item.flags & BTRFS_SUBVOL_READONLY))\n        return false;\n\n    if (!r->reserved)\n        return true;\n\n    return (!Irp || Irp->RequestorMode == UserMode) && PsGetCurrentProcess() != r->reserved ? true : false;\n}\n\n__inline static uint16_t get_extent_data_len(uint8_t type) {\n    switch (type) {\n        case TYPE_TREE_BLOCK_REF:\n            return sizeof(TREE_BLOCK_REF);\n\n        case TYPE_EXTENT_DATA_REF:\n            return sizeof(EXTENT_DATA_REF);\n\n        case TYPE_EXTENT_REF_V0:\n            return sizeof(EXTENT_REF_V0);\n\n        case TYPE_SHARED_BLOCK_REF:\n            return sizeof(SHARED_BLOCK_REF);\n\n        case TYPE_SHARED_DATA_REF:\n            return sizeof(SHARED_DATA_REF);\n\n        default:\n            return 0;\n    }\n}\n\n__inline static uint32_t get_extent_data_refcount(uint8_t type, void* data) {\n    switch (type) {\n        case TYPE_TREE_BLOCK_REF:\n            return 1;\n\n        case TYPE_EXTENT_DATA_REF:\n        {\n            EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data;\n            return edr->count;\n        }\n\n        case TYPE_EXTENT_REF_V0:\n        {\n            EXTENT_REF_V0* erv0 = (EXTENT_REF_V0*)data;\n            return erv0->count;\n        }\n\n        case TYPE_SHARED_BLOCK_REF:\n            return 1;\n\n        case TYPE_SHARED_DATA_REF:\n        {\n            SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data;\n            return sdr->count;\n        }\n\n        default:\n            return 0;\n    }\n}\n\n// in xor-gas.S\n#if defined(_X86_) || defined(_AMD64_)\nvoid __stdcall do_xor_sse2(uint8_t* buf1, uint8_t* buf2, uint32_t len);\nvoid __stdcall do_xor_avx2(uint8_t* buf1, uint8_t* buf2, uint32_t len);\n#endif\n\n// in btrfs.c\n_Ret_maybenull_\ndevice* find_device_from_uuid(_In_ device_extension* Vcb, _In_ BTRFS_UUID* uuid);\n\n_Success_(return)\nbool get_file_attributes_from_xattr(_In_reads_bytes_(len) char* val, _In_ uint16_t len, _Out_ ULONG* atts);\n\nULONG get_file_attributes(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _In_ uint64_t inode,\n                          _In_ uint8_t type, _In_ bool dotfile, _In_ bool ignore_xa, _In_opt_ PIRP Irp);\n\n_Success_(return)\nbool get_xattr(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* subvol, _In_ uint64_t inode, _In_z_ char* name, _In_ uint32_t crc32,\n               _Out_ uint8_t** data, _Out_ uint16_t* datalen, _In_opt_ PIRP Irp);\n\n#ifndef DEBUG_FCB_REFCOUNTS\nvoid free_fcb(_Inout_ fcb* fcb);\n#endif\nvoid free_fileref(_Inout_ file_ref* fr);\nvoid protect_superblocks(_Inout_ chunk* c);\nbool is_top_level(_In_ PIRP Irp);\nNTSTATUS create_root(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ uint64_t id,\n                     _Out_ root** rootptr, _In_ bool no_tree, _In_ uint64_t offset, _In_opt_ PIRP Irp);\nvoid uninit(_In_ device_extension* Vcb);\nNTSTATUS dev_ioctl(_In_ PDEVICE_OBJECT DeviceObject, _In_ ULONG ControlCode, _In_reads_bytes_opt_(InputBufferSize) PVOID InputBuffer, _In_ ULONG InputBufferSize,\n                   _Out_writes_bytes_opt_(OutputBufferSize) PVOID OutputBuffer, _In_ ULONG OutputBufferSize, _In_ bool Override, _Out_opt_ IO_STATUS_BLOCK* iosb);\nNTSTATUS check_file_name_valid(_In_ PUNICODE_STRING us, _In_ bool posix, _In_ bool stream);\nvoid send_notification_fileref(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream);\nvoid queue_notification_fcb(_In_ file_ref* fileref, _In_ ULONG filter_match, _In_ ULONG action, _In_opt_ PUNICODE_STRING stream);\n\ntypedef void (__stdcall *xor_func)(uint8_t* buf1, uint8_t* buf2, uint32_t len);\n\nextern xor_func do_xor;\n\n#ifdef DEBUG_CHUNK_LOCKS\n#define acquire_chunk_lock(c, Vcb) { ExAcquireResourceExclusiveLite(&c->lock, true); InterlockedIncrement(&Vcb->chunk_locks_held); }\n#define release_chunk_lock(c, Vcb) { InterlockedDecrement(&Vcb->chunk_locks_held); ExReleaseResourceLite(&c->lock); }\n#else\n#define acquire_chunk_lock(c, Vcb) ExAcquireResourceExclusiveLite(&(c)->lock, true)\n#define release_chunk_lock(c, Vcb) ExReleaseResourceLite(&(c)->lock)\n#endif\n\nvoid mark_fcb_dirty(_In_ fcb* fcb);\nvoid mark_fileref_dirty(_In_ file_ref* fileref);\nNTSTATUS delete_fileref(_In_ file_ref* fileref, _In_opt_ PFILE_OBJECT FileObject, _In_ bool make_orphan, _In_opt_ PIRP Irp, _In_ LIST_ENTRY* rollback);\nvoid chunk_lock_range(_In_ device_extension* Vcb, _In_ chunk* c, _In_ uint64_t start, _In_ uint64_t length);\nvoid chunk_unlock_range(_In_ device_extension* Vcb, _In_ chunk* c, _In_ uint64_t start, _In_ uint64_t length);\nvoid init_device(_In_ device_extension* Vcb, _Inout_ device* dev, _In_ bool get_nums);\nvoid init_file_cache(_In_ PFILE_OBJECT FileObject, _In_ CC_FILE_SIZES* ccfs);\nNTSTATUS sync_read_phys(_In_ PDEVICE_OBJECT DeviceObject, _In_ PFILE_OBJECT FileObject, _In_ uint64_t StartingOffset, _In_ ULONG Length,\n                        _Out_writes_bytes_(Length) PUCHAR Buffer, _In_ bool override);\nNTSTATUS get_device_pnp_name(_In_ PDEVICE_OBJECT DeviceObject, _Out_ PUNICODE_STRING pnp_name, _Out_ const GUID** guid);\nvoid log_device_error(_In_ device_extension* Vcb, _Inout_ device* dev, _In_ int error);\nNTSTATUS find_chunk_usage(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp);\n\n_Function_class_(DRIVER_ADD_DEVICE)\nNTSTATUS __stdcall AddDevice(PDRIVER_OBJECT DriverObject, PDEVICE_OBJECT PhysicalDeviceObject);\n\nvoid reap_fcb(fcb* fcb);\nvoid reap_fcbs(device_extension* Vcb);\nvoid reap_fileref(device_extension* Vcb, file_ref* fr);\nvoid reap_filerefs(device_extension* Vcb, file_ref* fr);\nNTSTATUS utf8_to_utf16(WCHAR* dest, ULONG dest_max, ULONG* dest_len, char* src, ULONG src_len);\nNTSTATUS utf16_to_utf8(char* dest, ULONG dest_max, ULONG* dest_len, WCHAR* src, ULONG src_len);\nuint32_t get_num_of_processors();\n\n_Ret_maybenull_\nroot* find_default_subvol(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_opt_ PIRP Irp);\n\nvoid do_shutdown(PIRP Irp);\nbool check_superblock_checksum(superblock* sb);\n\n#ifdef _MSC_VER\n#define funcname __FUNCTION__\n#else\n#define funcname __func__\n#endif\n\nextern uint32_t mount_compress;\nextern uint32_t mount_compress_force;\nextern uint32_t mount_compress_type;\nextern uint32_t mount_zlib_level;\nextern uint32_t mount_zstd_level;\nextern uint32_t mount_flush_interval;\nextern uint32_t mount_max_inline;\nextern uint32_t mount_skip_balance;\nextern uint32_t mount_no_barrier;\nextern uint32_t mount_no_trim;\nextern uint32_t mount_clear_cache;\nextern uint32_t mount_allow_degraded;\nextern uint32_t mount_readonly;\nextern uint32_t mount_no_root_dir;\nextern uint32_t mount_nodatacow;\nextern uint32_t no_pnp;\n\n#ifndef __GNUC__\n#define __attribute__(x)\n#endif\n\n#ifdef _DEBUG\n\nextern bool log_started;\nextern uint32_t debug_log_level;\n\n#ifdef DEBUG_LONG_MESSAGES\n\n#define MSG(fn, file, line, s, level, ...) (!log_started || level <= debug_log_level) ? _debug_message(fn, file, line, s, ##__VA_ARGS__) : (void)0\n\n#define TRACE(s, ...) MSG(funcname, __FILE__, __LINE__, s, 3, ##__VA_ARGS__)\n#define WARN(s, ...) MSG(funcname, __FILE__, __LINE__, s, 2, ##__VA_ARGS__)\n#define FIXME(s, ...) MSG(funcname, __FILE__, __LINE__, s, 1, ##__VA_ARGS__)\n#define ERR(s, ...) MSG(funcname, __FILE__, __LINE__, s, 1, ##__VA_ARGS__)\n\nvoid _debug_message(_In_ const char* func, _In_ const char* file, _In_ unsigned int line, _In_ char* s, ...) __attribute__((format(printf, 4, 5)));\n\n#else\n\n#define MSG(fn, s, level, ...) (!log_started || level <= debug_log_level) ? _debug_message(fn, s, ##__VA_ARGS__) : (void)0\n\n#define TRACE(s, ...) MSG(funcname, s, 3, ##__VA_ARGS__)\n#define WARN(s, ...) MSG(funcname, s, 2, ##__VA_ARGS__)\n#define FIXME(s, ...) MSG(funcname, s, 1, ##__VA_ARGS__)\n#define ERR(s, ...) MSG(funcname, s, 1, ##__VA_ARGS__)\n\nvoid _debug_message(_In_ const char* func, _In_ char* s, ...) __attribute__((format(printf, 2, 3)));\n\n#endif\n\n#else\n\n#define TRACE(s, ...) do { } while(0)\n#define WARN(s, ...) do { } while(0)\n#define FIXME(s, ...) DbgPrint(\"Btrfs FIXME : %s : \" s, funcname, ##__VA_ARGS__)\n#define ERR(s, ...) DbgPrint(\"Btrfs ERR : %s : \" s, funcname, ##__VA_ARGS__)\n\n#endif\n\n#ifdef DEBUG_FCB_REFCOUNTS\nvoid _free_fcb(_Inout_ fcb* fcb, _In_ const char* func);\n#define free_fcb(fcb) _free_fcb(fcb, funcname)\n#endif\n\n// in fastio.c\nvoid init_fast_io_dispatch(FAST_IO_DISPATCH** fiod);\n\n// in sha256.c\nvoid calc_sha256(uint8_t* hash, const void* input, size_t len);\n#define SHA256_HASH_SIZE 32\n\n// in blake2b-ref.c\nvoid blake2b(void *out, size_t outlen, const void* in, size_t inlen);\n#define BLAKE2_HASH_SIZE 32\n\ntypedef struct {\n    LIST_ENTRY* list;\n    LIST_ENTRY* list_size;\n    uint64_t address;\n    uint64_t length;\n    chunk* chunk;\n} rollback_space;\n\ntypedef struct {\n    fcb* fcb;\n    extent* ext;\n} rollback_extent;\n\nenum rollback_type {\n    ROLLBACK_INSERT_EXTENT,\n    ROLLBACK_DELETE_EXTENT,\n    ROLLBACK_ADD_SPACE,\n    ROLLBACK_SUBTRACT_SPACE\n};\n\ntypedef struct {\n    enum rollback_type type;\n    void* ptr;\n    LIST_ENTRY list_entry;\n} rollback_item;\n\ntypedef struct {\n    ANSI_STRING name;\n    ANSI_STRING value;\n    UCHAR flags;\n    LIST_ENTRY list_entry;\n} ea_item;\n\nstatic const char lxuid[] = \"$LXUID\";\nstatic const char lxgid[] = \"$LXGID\";\nstatic const char lxmod[] = \"$LXMOD\";\nstatic const char lxdev[] = \"$LXDEV\";\n\n// in treefuncs.c\nNTSTATUS find_item(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _Out_ traverse_ptr* tp,\n                   _In_ const KEY* searchkey, _In_ bool ignore, _In_opt_ PIRP Irp) __attribute__((nonnull(1,2,3,4)));\nNTSTATUS find_item_to_level(device_extension* Vcb, root* r, traverse_ptr* tp, const KEY* searchkey, bool ignore,\n                            uint8_t level, PIRP Irp) __attribute__((nonnull(1,2,3,4)));\nbool find_next_item(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, const traverse_ptr* tp,\n                    traverse_ptr* next_tp, bool ignore, PIRP Irp) __attribute__((nonnull(1,2,3)));\nbool find_prev_item(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, const traverse_ptr* tp,\n                    traverse_ptr* prev_tp, PIRP Irp) __attribute__((nonnull(1,2,3)));\nvoid free_trees(device_extension* Vcb) __attribute__((nonnull(1)));\nNTSTATUS insert_tree_item(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _In_ uint64_t obj_id,\n                          _In_ uint8_t obj_type, _In_ uint64_t offset, _In_reads_bytes_opt_(size) _When_(return >= 0, __drv_aliasesMem) void* data,\n                          _In_ uint16_t size, _Out_opt_ traverse_ptr* ptp, _In_opt_ PIRP Irp) __attribute__((nonnull(1,2)));\nNTSTATUS delete_tree_item(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb,\n                          _Inout_ traverse_ptr* tp) __attribute__((nonnull(1,2)));\nvoid free_tree(tree* t) __attribute__((nonnull(1)));\nNTSTATUS load_tree(device_extension* Vcb, uint64_t addr, uint8_t* buf, root* r, tree** pt) __attribute__((nonnull(1,3,4,5)));\nNTSTATUS do_load_tree(device_extension* Vcb, tree_holder* th, root* r, tree* t, tree_data* td, PIRP Irp) __attribute__((nonnull(1,2,3)));\nvoid clear_rollback(LIST_ENTRY* rollback) __attribute__((nonnull(1)));\nvoid do_rollback(device_extension* Vcb, LIST_ENTRY* rollback) __attribute__((nonnull(1,2)));\nvoid free_trees_root(device_extension* Vcb, root* r) __attribute__((nonnull(1,2)));\nvoid add_rollback(_In_ LIST_ENTRY* rollback, _In_ enum rollback_type type, _In_ __drv_aliasesMem void* ptr) __attribute__((nonnull(1,3)));\nNTSTATUS commit_batch_list(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb,\n                           LIST_ENTRY* batchlist, PIRP Irp) __attribute__((nonnull(1,2)));\nvoid clear_batch_list(device_extension* Vcb, LIST_ENTRY* batchlist) __attribute__((nonnull(1,2)));\nNTSTATUS skip_to_difference(device_extension* Vcb, traverse_ptr* tp, traverse_ptr* tp2, bool* ended1, bool* ended2) __attribute__((nonnull(1,2,3,4,5)));\n\n// in search.c\nNTSTATUS remove_drive_letter(PDEVICE_OBJECT mountmgr, PUNICODE_STRING devpath);\n\n_Function_class_(KSTART_ROUTINE)\nvoid __stdcall mountmgr_thread(_In_ void* context);\n\n_Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE)\nNTSTATUS __stdcall pnp_notification(PVOID NotificationStructure, PVOID Context);\n\nvoid disk_arrival(PUNICODE_STRING devpath);\nbool volume_arrival(PUNICODE_STRING devpath, bool fve_callback);\nvoid volume_removal(PUNICODE_STRING devpath);\n\n_Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE)\nNTSTATUS __stdcall volume_notification(PVOID NotificationStructure, PVOID Context);\n\nvoid remove_volume_child(_Inout_ _Requires_exclusive_lock_held_(_Curr_->child_lock) _Releases_exclusive_lock_(_Curr_->child_lock) _In_ volume_device_extension* vde,\n                         _In_ volume_child* vc, _In_ bool skip_dev);\nextern KSPIN_LOCK fve_data_lock;\n\n// in cache.c\nvoid init_cache();\nextern CACHE_MANAGER_CALLBACKS cache_callbacks;\n\n// in write.c\nNTSTATUS write_file(device_extension* Vcb, PIRP Irp, bool wait, bool deferred_write) __attribute__((nonnull(1,2)));\nNTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void* buf, ULONG* length, bool paging_io, bool no_cache,\n                     bool wait, bool deferred_write, bool write_irp, LIST_ENTRY* rollback) __attribute__((nonnull(1,2,4,5,11)));\nNTSTATUS truncate_file(fcb* fcb, uint64_t end, PIRP Irp, LIST_ENTRY* rollback) __attribute__((nonnull(1,4)));\nNTSTATUS extend_file(fcb* fcb, file_ref* fileref, uint64_t end, bool prealloc, PIRP Irp, LIST_ENTRY* rollback) __attribute__((nonnull(1,6)));\nNTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, uint64_t start_data, uint64_t end_data, PIRP Irp, LIST_ENTRY* rollback) __attribute__((nonnull(1,2,6)));\nchunk* get_chunk_from_address(device_extension* Vcb, uint64_t address) __attribute__((nonnull(1)));\nNTSTATUS alloc_chunk(device_extension* Vcb, uint64_t flags, chunk** pc, bool full_size) __attribute__((nonnull(1,3)));\nNTSTATUS write_data(_In_ device_extension* Vcb, _In_ uint64_t address, _In_reads_bytes_(length) void* data, _In_ uint32_t length, _In_ write_data_context* wtc,\n                    _In_opt_ PIRP Irp, _In_opt_ chunk* c, _In_ bool file_write, _In_ uint64_t irp_offset, _In_ ULONG priority) __attribute__((nonnull(1,3,5)));\nNTSTATUS write_data_complete(device_extension* Vcb, uint64_t address, void* data, uint32_t length, PIRP Irp, chunk* c, bool file_write,\n                             uint64_t irp_offset, ULONG priority) __attribute__((nonnull(1,3)));\nvoid free_write_data_stripes(write_data_context* wtc) __attribute__((nonnull(1)));\n\n_Dispatch_type_(IRP_MJ_WRITE)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) __attribute__((nonnull(1,2)));\n\n_Requires_lock_held_(c->lock)\n_When_(return != 0, _Releases_lock_(c->lock))\nbool insert_extent_chunk(_In_ device_extension* Vcb, _In_ fcb* fcb, _In_ chunk* c, _In_ uint64_t start_data, _In_ uint64_t length, _In_ bool prealloc,\n                         _In_opt_ void* data, _In_opt_ PIRP Irp, _In_ LIST_ENTRY* rollback, _In_ uint8_t compression, _In_ uint64_t decoded_size,\n                         _In_ bool file_write, _In_ uint64_t irp_offset) __attribute__((nonnull(1,2,3,9)));\n\nNTSTATUS do_write_file(fcb* fcb, uint64_t start_data, uint64_t end_data, void* data, PIRP Irp, bool file_write, uint32_t irp_offset, LIST_ENTRY* rollback) __attribute__((nonnull(1, 4)));\nbool find_data_address_in_chunk(device_extension* Vcb, chunk* c, uint64_t length, uint64_t* address) __attribute__((nonnull(1, 2, 4)));\nvoid get_raid56_lock_range(chunk* c, uint64_t address, uint64_t length, uint64_t* lockaddr, uint64_t* locklen) __attribute__((nonnull(1,4,5)));\nNTSTATUS add_extent_to_fcb(_In_ fcb* fcb, _In_ uint64_t offset, _In_reads_bytes_(edsize) EXTENT_DATA* ed, _In_ uint16_t edsize,\n                           _In_ bool unique, _In_opt_ _When_(return >= 0, __drv_aliasesMem) void* csum, _In_ LIST_ENTRY* rollback) __attribute__((nonnull(1,3,7)));\nvoid add_extent(_In_ fcb* fcb, _In_ LIST_ENTRY* prevextle, _In_ __drv_aliasesMem extent* newext) __attribute__((nonnull(1,2,3)));\n\n// in dirctrl.c\n\n_Dispatch_type_(IRP_MJ_DIRECTORY_CONTROL)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_directory_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\n\nULONG get_reparse_tag(device_extension* Vcb, root* subvol, uint64_t inode, uint8_t type, ULONG atts, bool lxss, PIRP Irp);\nULONG get_reparse_tag_fcb(fcb* fcb);\n\n// in security.c\n\n_Dispatch_type_(IRP_MJ_QUERY_SECURITY)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_query_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\n\n_Dispatch_type_(IRP_MJ_SET_SECURITY)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_set_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\n\nvoid fcb_get_sd(fcb* fcb, struct _fcb* parent, bool look_for_xattr, PIRP Irp);\nvoid add_user_mapping(WCHAR* sidstring, ULONG sidstringlength, uint32_t uid);\nvoid add_group_mapping(WCHAR* sidstring, ULONG sidstringlength, uint32_t gid);\nuint32_t sid_to_uid(PSID sid);\nNTSTATUS uid_to_sid(uint32_t uid, PSID* sid);\nNTSTATUS fcb_get_new_sd(fcb* fcb, file_ref* parfileref, ACCESS_STATE* as);\nvoid find_gid(struct _fcb* fcb, struct _fcb* parfcb, PSECURITY_SUBJECT_CONTEXT subjcont);\n\n// in fileinfo.c\n\n_Dispatch_type_(IRP_MJ_SET_INFORMATION)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_set_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\n\n_Dispatch_type_(IRP_MJ_QUERY_INFORMATION)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_query_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\n\n_Dispatch_type_(IRP_MJ_QUERY_EA)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_query_ea(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\n\n_Dispatch_type_(IRP_MJ_SET_EA)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_set_ea(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\n\nbool has_open_children(file_ref* fileref);\nNTSTATUS stream_set_end_of_file_information(device_extension* Vcb, uint16_t end, fcb* fcb, file_ref* fileref, bool advance_only);\nNTSTATUS fileref_get_filename(file_ref* fileref, PUNICODE_STRING fn, USHORT* name_offset, ULONG* preqlen);\nvoid insert_dir_child_into_hash_lists(fcb* fcb, dir_child* dc);\nvoid remove_dir_child_from_hash_lists(fcb* fcb, dir_child* dc);\nvoid add_fcb_to_subvol(_In_ _Requires_exclusive_lock_held_(_Curr_->Vcb->fcb_lock) fcb* fcb);\nvoid remove_fcb_from_subvol(_In_ _Requires_exclusive_lock_held_(_Curr_->Vcb->fcb_lock) fcb* fcb);\n\n// in reparse.c\nNTSTATUS get_reparse_point(PFILE_OBJECT FileObject, void* buffer, DWORD buflen, ULONG_PTR* retlen);\nNTSTATUS set_reparse_point2(fcb* fcb, REPARSE_DATA_BUFFER* rdb, ULONG buflen, ccb* ccb, file_ref* fileref, PIRP Irp, LIST_ENTRY* rollback);\nNTSTATUS set_reparse_point(PIRP Irp);\nNTSTATUS delete_reparse_point(PIRP Irp);\n\n// in create.c\n\n_Dispatch_type_(IRP_MJ_CREATE)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\n\nNTSTATUS open_fileref(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb, _Out_ file_ref** pfr,\n                      _In_ PUNICODE_STRING fnus, _In_opt_ file_ref* related, _In_ bool parent, _Out_opt_ USHORT* parsed, _Out_opt_ ULONG* fn_offset, _In_ POOL_TYPE pooltype,\n                      _In_ bool case_sensitive, _In_opt_ PIRP Irp);\nNTSTATUS open_fcb(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb,\n                  root* subvol, uint64_t inode, uint8_t type, PANSI_STRING utf8, bool always_add_hl, fcb* parent, fcb** pfcb, POOL_TYPE pooltype, PIRP Irp);\nNTSTATUS load_csum(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, void* csum, uint64_t start, uint64_t length, PIRP Irp);\nNTSTATUS load_dir_children(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, fcb* fcb, bool ignore_size, PIRP Irp);\nNTSTATUS add_dir_child(fcb* fcb, uint64_t inode, bool subvol, PANSI_STRING utf8, PUNICODE_STRING name, uint8_t type, dir_child** pdc);\nNTSTATUS open_fileref_child(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb,\n                            _In_ file_ref* sf, _In_ PUNICODE_STRING name, _In_ bool case_sensitive, _In_ bool lastpart, _In_ bool streampart,\n                            _In_ POOL_TYPE pooltype, _Out_ file_ref** psf2, _In_opt_ PIRP Irp);\nfcb* create_fcb(device_extension* Vcb, POOL_TYPE pool_type);\nNTSTATUS find_file_in_dir(PUNICODE_STRING filename, fcb* fcb, root** subvol, uint64_t* inode, dir_child** pdc, bool case_sensitive);\nuint32_t inherit_mode(fcb* parfcb, bool is_dir);\nfile_ref* create_fileref(device_extension* Vcb);\nNTSTATUS open_fileref_by_inode(_Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb, root* subvol, uint64_t inode, file_ref** pfr, PIRP Irp);\n\n// in fsctl.c\nNTSTATUS fsctl_request(PDEVICE_OBJECT DeviceObject, PIRP* Pirp, uint32_t type);\nvoid do_unlock_volume(device_extension* Vcb);\nvoid trim_whole_device(device* dev);\nvoid flush_subvol_fcbs(root* subvol);\nbool fcb_is_inline(fcb* fcb);\nNTSTATUS dismount_volume(device_extension* Vcb, bool shutdown, PIRP Irp);\n\n// in flushthread.c\n\n_Function_class_(KSTART_ROUTINE)\nvoid __stdcall flush_thread(void* context);\n\nNTSTATUS do_write(device_extension* Vcb, PIRP Irp);\nNTSTATUS get_tree_new_address(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback);\nNTSTATUS flush_fcb(fcb* fcb, bool cache, LIST_ENTRY* batchlist, PIRP Irp);\nNTSTATUS write_data_phys(_In_ PDEVICE_OBJECT device, _In_ PFILE_OBJECT fileobj, _In_ uint64_t address,\n                         _In_reads_bytes_(length) void* data, _In_ uint32_t length);\nbool is_tree_unique(device_extension* Vcb, tree* t, PIRP Irp);\nNTSTATUS do_tree_writes(device_extension* Vcb, LIST_ENTRY* tree_writes, bool no_free);\nvoid add_checksum_entry(device_extension* Vcb, uint64_t address, ULONG length, void* csum, PIRP Irp);\nbool find_metadata_address_in_chunk(device_extension* Vcb, chunk* c, uint64_t* address);\nvoid add_trim_entry_avoid_sb(device_extension* Vcb, device* dev, uint64_t address, uint64_t size);\nNTSTATUS flush_partial_stripe(device_extension* Vcb, chunk* c, partial_stripe* ps);\nNTSTATUS update_dev_item(device_extension* Vcb, device* device, PIRP Irp);\nvoid calc_tree_checksum(device_extension* Vcb, tree_header* th);\n\n// in read.c\n\n_Dispatch_type_(IRP_MJ_READ)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_read(PDEVICE_OBJECT DeviceObject, PIRP Irp);\n\nNTSTATUS read_data(_In_ device_extension* Vcb, _In_ uint64_t addr, _In_ uint32_t length, _In_reads_bytes_opt_(length*sizeof(uint32_t)/Vcb->superblock.sector_size) void* csum,\n                   _In_ bool is_tree, _Out_writes_bytes_(length) uint8_t* buf, _In_opt_ chunk* c, _Out_opt_ chunk** pc, _In_opt_ PIRP Irp, _In_ uint64_t generation, _In_ bool file_read,\n                   _In_ ULONG priority);\nNTSTATUS read_file(fcb* fcb, uint8_t* data, uint64_t start, uint64_t length, ULONG* pbr, PIRP Irp) __attribute__((nonnull(1, 2)));\nNTSTATUS read_stream(fcb* fcb, uint8_t* data, uint64_t start, ULONG length, ULONG* pbr) __attribute__((nonnull(1, 2)));\nNTSTATUS do_read(PIRP Irp, bool wait, ULONG* bytes_read);\nNTSTATUS check_csum(device_extension* Vcb, uint8_t* data, uint32_t sectors, void* csum);\nvoid raid6_recover2(uint8_t* sectors, uint16_t num_stripes, ULONG sector_size, uint16_t missing1, uint16_t missing2, uint8_t* out);\nvoid get_tree_checksum(device_extension* Vcb, tree_header* th, void* csum);\nbool check_tree_checksum(device_extension* Vcb, tree_header* th);\nvoid get_sector_csum(device_extension* Vcb, void* buf, void* csum);\nbool check_sector_csum(device_extension* Vcb, void* buf, void* csum);\n\n// in pnp.c\n\n_Dispatch_type_(IRP_MJ_PNP)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_pnp(PDEVICE_OBJECT DeviceObject, PIRP Irp);\n\nNTSTATUS pnp_surprise_removal(PDEVICE_OBJECT DeviceObject, PIRP Irp);\nNTSTATUS pnp_query_remove_device(PDEVICE_OBJECT DeviceObject, PIRP Irp);\n\n// in free-space.c\nNTSTATUS load_cache_chunk(device_extension* Vcb, chunk* c, PIRP Irp);\nNTSTATUS clear_free_space_cache(device_extension* Vcb, LIST_ENTRY* batchlist, PIRP Irp);\nNTSTATUS allocate_cache(device_extension* Vcb, bool* changed, PIRP Irp, LIST_ENTRY* rollback);\nNTSTATUS update_chunk_caches(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback);\nNTSTATUS update_chunk_caches_tree(device_extension* Vcb, PIRP Irp);\nNTSTATUS add_space_entry(LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t offset, uint64_t size);\nvoid space_list_add(chunk* c, uint64_t address, uint64_t length, LIST_ENTRY* rollback);\nvoid space_list_add2(LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t address, uint64_t length, chunk* c, LIST_ENTRY* rollback);\nvoid space_list_subtract(chunk* c, uint64_t address, uint64_t length, LIST_ENTRY* rollback);\nvoid space_list_subtract2(LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t address, uint64_t length, chunk* c, LIST_ENTRY* rollback);\nvoid space_list_merge(LIST_ENTRY* spacelist, LIST_ENTRY* spacelist_size, LIST_ENTRY* deleting);\nNTSTATUS load_stored_free_space_cache(device_extension* Vcb, chunk* c, bool load_only, PIRP Irp);\n\n// in extent-tree.c\nNTSTATUS increase_extent_refcount_data(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint64_t inode, uint64_t offset, uint32_t refcount, PIRP Irp);\nNTSTATUS decrease_extent_refcount_data(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint64_t inode, uint64_t offset,\n                                       uint32_t refcount, bool superseded, PIRP Irp);\nNTSTATUS decrease_extent_refcount_tree(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint8_t level, PIRP Irp);\nuint64_t get_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, PIRP Irp);\nbool is_extent_unique(device_extension* Vcb, uint64_t address, uint64_t size, PIRP Irp);\nNTSTATUS increase_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, uint8_t type, void* data, KEY* firstitem, uint8_t level, PIRP Irp);\nuint64_t get_extent_flags(device_extension* Vcb, uint64_t address, PIRP Irp);\nvoid update_extent_flags(device_extension* Vcb, uint64_t address, uint64_t flags, PIRP Irp);\nNTSTATUS update_changed_extent_ref(device_extension* Vcb, chunk* c, uint64_t address, uint64_t size, uint64_t root, uint64_t objid, uint64_t offset,\n                                   int32_t count, bool no_csum, bool superseded, PIRP Irp);\nvoid add_changed_extent_ref(chunk* c, uint64_t address, uint64_t size, uint64_t root, uint64_t objid, uint64_t offset, uint32_t count, bool no_csum);\nuint64_t find_extent_shared_tree_refcount(device_extension* Vcb, uint64_t address, uint64_t parent, PIRP Irp);\nuint32_t find_extent_shared_data_refcount(device_extension* Vcb, uint64_t address, uint64_t parent, PIRP Irp);\nNTSTATUS decrease_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, uint8_t type, void* data, KEY* firstitem,\n                                  uint8_t level, uint64_t parent, bool superseded, PIRP Irp);\nuint64_t get_extent_data_ref_hash2(uint64_t root, uint64_t objid, uint64_t offset);\n\n// in worker-thread.c\nNTSTATUS do_read_job(PIRP Irp);\nNTSTATUS do_write_job(device_extension* Vcb, PIRP Irp);\nbool add_thread_job(device_extension* Vcb, PIRP Irp);\n\n// in registry.c\nvoid read_registry(PUNICODE_STRING regpath, bool refresh);\nNTSTATUS registry_mark_volume_mounted(BTRFS_UUID* uuid);\nNTSTATUS registry_mark_volume_unmounted(BTRFS_UUID* uuid);\nNTSTATUS registry_load_volume_options(device_extension* Vcb);\nvoid watch_registry(HANDLE regh);\n\n// in compress.c\nNTSTATUS zlib_decompress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen);\nNTSTATUS lzo_decompress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, uint32_t inpageoff);\nNTSTATUS zstd_decompress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen);\nNTSTATUS write_compressed(fcb* fcb, uint64_t start_data, uint64_t end_data, void* data, PIRP Irp, LIST_ENTRY* rollback);\nNTSTATUS zlib_compress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, unsigned int level, unsigned int* space_left);\nNTSTATUS lzo_compress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, unsigned int* space_left);\nNTSTATUS zstd_compress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, uint32_t level, unsigned int* space_left);\n\n// in galois.c\nvoid galois_double(uint8_t* data, uint32_t len);\nvoid galois_divpower(uint8_t* data, uint8_t div, uint32_t readlen);\nuint8_t gpow2(uint8_t e);\nuint8_t gmul(uint8_t a, uint8_t b);\nuint8_t gdiv(uint8_t a, uint8_t b);\n\n// in devctrl.c\n\n_Dispatch_type_(IRP_MJ_DEVICE_CONTROL)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_device_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\n\n// in calcthread.c\n\n_Function_class_(KSTART_ROUTINE)\nvoid __stdcall calc_thread(void* context);\n\nvoid do_calc_job(device_extension* Vcb, uint8_t* data, uint32_t sectors, void* csum);\nNTSTATUS add_calc_job_decomp(device_extension* Vcb, uint8_t compression, void* in, unsigned int inlen,\n                             void* out, unsigned int outlen, unsigned int off, calc_job** pcj);\nNTSTATUS add_calc_job_comp(device_extension* Vcb, uint8_t compression, void* in, unsigned int inlen,\n                           void* out, unsigned int outlen, calc_job** pcj);\nvoid calc_thread_main(device_extension* Vcb, calc_job* cj);\n\n// in balance.c\nNTSTATUS start_balance(device_extension* Vcb, void* data, ULONG length, KPROCESSOR_MODE processor_mode);\nNTSTATUS query_balance(device_extension* Vcb, void* data, ULONG length);\nNTSTATUS pause_balance(device_extension* Vcb, KPROCESSOR_MODE processor_mode);\nNTSTATUS resume_balance(device_extension* Vcb, KPROCESSOR_MODE processor_mode);\nNTSTATUS stop_balance(device_extension* Vcb, KPROCESSOR_MODE processor_mode);\nNTSTATUS look_for_balance_item(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb);\nNTSTATUS remove_device(device_extension* Vcb, void* data, ULONG length, KPROCESSOR_MODE processor_mode);\n\n_Function_class_(KSTART_ROUTINE)\nvoid __stdcall balance_thread(void* context);\n\n// in volume.c\nNTSTATUS vol_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\nNTSTATUS vol_close(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\nNTSTATUS vol_read(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\nNTSTATUS vol_write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\nNTSTATUS vol_device_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp);\nvoid add_volume_device(superblock* sb, PUNICODE_STRING devpath, uint64_t length, ULONG disk_num, ULONG part_num);\nNTSTATUS mountmgr_add_drive_letter(PDEVICE_OBJECT mountmgr, PUNICODE_STRING devpath);\n\n_Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE)\nNTSTATUS __stdcall pnp_removal(PVOID NotificationStructure, PVOID Context);\n\nvoid free_vol(volume_device_extension* vde);\n\n// in scrub.c\nNTSTATUS start_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode);\nNTSTATUS query_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode, void* data, ULONG length);\nNTSTATUS pause_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode);\nNTSTATUS resume_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode);\nNTSTATUS stop_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode);\n\n// in send.c\nNTSTATUS send_subvol(device_extension* Vcb, void* data, ULONG datalen, PFILE_OBJECT FileObject, PIRP Irp);\nNTSTATUS read_send_buffer(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, ULONG_PTR* retlen, KPROCESSOR_MODE processor_mode);\n\n// in fsrtl.c\nNTSTATUS __stdcall compat_FsRtlValidateReparsePointBuffer(IN ULONG BufferLength, IN PREPARSE_DATA_BUFFER ReparseBuffer);\n\n// in boot.c\nvoid check_system_root();\nvoid boot_add_device(DEVICE_OBJECT* pdo);\nextern BTRFS_UUID boot_uuid;\n\n// based on function in sys/sysmacros.h\n#define makedev(major, minor) (((minor) & 0xFF) | (((major) & 0xFFF) << 8) | (((uint64_t)((minor) & ~0xFF)) << 12) | (((uint64_t)((major) & ~0xFFF)) << 32))\n\n// not in mingw yet\n#ifndef _MSC_VER\ntypedef struct {\n    FSRTL_COMMON_FCB_HEADER Header;\n    PFAST_MUTEX FastMutex;\n    LIST_ENTRY FilterContexts;\n    EX_PUSH_LOCK PushLock;\n    PVOID* FileContextSupportPointer;\n    union {\n        OPLOCK Oplock;\n        PVOID ReservedForRemote;\n    };\n    PVOID ReservedContext;\n} FSRTL_ADVANCED_FCB_HEADER_NEW;\n\n#define FSRTL_FCB_HEADER_V2 2\n\n#else\n#define FSRTL_ADVANCED_FCB_HEADER_NEW FSRTL_ADVANCED_FCB_HEADER\n#endif\n\nstatic __inline POPLOCK fcb_oplock(fcb* fcb) {\n    if (fcb->Header.Version >= FSRTL_FCB_HEADER_V2)\n        return &((FSRTL_ADVANCED_FCB_HEADER_NEW*)&fcb->Header)->Oplock;\n    else\n        return &fcb->oplock;\n}\n\nstatic __inline FAST_IO_POSSIBLE fast_io_possible(fcb* fcb) {\n    if (!FsRtlOplockIsFastIoPossible(fcb_oplock(fcb)))\n        return FastIoIsNotPossible;\n\n    if (!FsRtlAreThereCurrentFileLocks(&fcb->lock) && !fcb->Vcb->readonly)\n        return FastIoIsPossible;\n\n    return FastIoIsQuestionable;\n}\n\nstatic __inline void print_open_trees(device_extension* Vcb) {\n    LIST_ENTRY* le = Vcb->trees.Flink;\n    while (le != &Vcb->trees) {\n        tree* t = CONTAINING_RECORD(le, tree, list_entry);\n        tree_data* td = CONTAINING_RECORD(t->itemlist.Flink, tree_data, list_entry);\n        ERR(\"tree %p: root %I64x, level %u, first key (%I64x,%x,%I64x)\\n\",\n                      t, t->root->id, t->header.level, td->key.obj_id, td->key.obj_type, td->key.offset);\n\n        le = le->Flink;\n    }\n}\n\nstatic __inline bool write_fcb_compressed(fcb* fcb) {\n    if (fcb->inode_item.flags & BTRFS_INODE_NODATACOW)\n        return false;\n\n    // make sure we don't accidentally write the cache inodes or pagefile compressed\n    if (fcb->subvol->id == BTRFS_ROOT_ROOT || fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE)\n        return false;\n\n    if (fcb->Vcb->options.compress_force)\n        return true;\n\n    if (fcb->inode_item.flags & BTRFS_INODE_NOCOMPRESS)\n        return false;\n\n    if (fcb->inode_item.flags & BTRFS_INODE_COMPRESS || fcb->Vcb->options.compress)\n        return true;\n\n    return false;\n}\n\n#ifdef DEBUG_FCB_REFCOUNTS\n#ifdef DEBUG_LONG_MESSAGES\n#define increase_fileref_refcount(fileref) {\\\n    LONG rc = InterlockedIncrement(&fileref->refcount);\\\n    MSG(funcname, __FILE__, __LINE__, \"fileref %p: refcount now %i\\n\", 1, fileref, rc);\\\n}\n#else\n#define increase_fileref_refcount(fileref) {\\\n    LONG rc = InterlockedIncrement(&fileref->refcount);\\\n    MSG(funcname, \"fileref %p: refcount now %i\\n\", 1, fileref, rc);\\\n}\n#endif\n#else\n#define increase_fileref_refcount(fileref) InterlockedIncrement(&fileref->refcount)\n#endif\n\n#ifdef _MSC_VER\n#define int3 __debugbreak()\n#else\n#define int3 asm(\"int3;\")\n#endif\n\n#define hex_digit(c) ((c) <= 9) ? ((c) + '0') : ((c) - 10 + 'a')\n\n// FIXME - find a way to catch unfreed trees again\n\n// from sys/stat.h\n#define __S_IFMT        0170000 /* These bits determine file type.  */\n#define __S_IFDIR       0040000 /* Directory.  */\n#define __S_IFCHR       0020000 /* Character device.  */\n#define __S_IFBLK       0060000 /* Block device.  */\n#define __S_IFREG       0100000 /* Regular file.  */\n#define __S_IFIFO       0010000 /* FIFO.  */\n#define __S_IFLNK       0120000 /* Symbolic link.  */\n#define __S_IFSOCK      0140000 /* Socket.  */\n#define __S_ISTYPE(mode, mask)  (((mode) & __S_IFMT) == (mask))\n\n#ifndef S_ISDIR\n#define S_ISDIR(mode)    __S_ISTYPE((mode), __S_IFDIR)\n#endif\n\n#ifndef S_IRUSR\n#define S_IRUSR 0000400\n#endif\n\n#ifndef S_IWUSR\n#define S_IWUSR 0000200\n#endif\n\n#ifndef S_IXUSR\n#define S_IXUSR 0000100\n#endif\n\n#ifndef S_IRGRP\n#define S_IRGRP (S_IRUSR >> 3)\n#endif\n\n#ifndef S_IWGRP\n#define S_IWGRP (S_IWUSR >> 3)\n#endif\n\n#ifndef S_IXGRP\n#define S_IXGRP (S_IXUSR >> 3)\n#endif\n\n#ifndef S_IROTH\n#define S_IROTH (S_IRGRP >> 3)\n#endif\n\n#ifndef S_IWOTH\n#define S_IWOTH (S_IWGRP >> 3)\n#endif\n\n#ifndef S_IXOTH\n#define S_IXOTH (S_IXGRP >> 3)\n#endif\n\n#ifndef S_ISUID\n#define S_ISUID 0004000\n#endif\n\n#ifndef S_ISGID\n#define S_ISGID 0002000\n#endif\n\n#ifndef S_ISVTX\n#define S_ISVTX 0001000\n#endif\n\n// based on functions in sys/sysmacros.h\n#define major(rdev) ((((rdev) >> 8) & 0xFFF) | ((uint32_t)((rdev) >> 32) & ~0xFFF))\n#define minor(rdev) (((rdev) & 0xFF) | ((uint32_t)((rdev) >> 12) & ~0xFF))\n\nstatic __inline uint64_t fcb_alloc_size(fcb* fcb) {\n    if (S_ISDIR(fcb->inode_item.st_mode))\n        return 0;\n    else if (fcb->atts & FILE_ATTRIBUTE_SPARSE_FILE)\n        return fcb->inode_item.st_blocks;\n    else\n        return sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);\n}\n\ntypedef BOOLEAN (__stdcall *tPsIsDiskCountersEnabled)();\n\ntypedef VOID (__stdcall *tPsUpdateDiskCounters)(PEPROCESS Process, ULONG64 BytesRead, ULONG64 BytesWritten,\n                                                ULONG ReadOperationCount, ULONG WriteOperationCount, ULONG FlushOperationCount);\n\ntypedef BOOLEAN (__stdcall *tCcCopyWriteEx)(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, ULONG Length, BOOLEAN Wait,\n                                            PVOID Buffer, PETHREAD IoIssuerThread);\n\ntypedef BOOLEAN (__stdcall *tCcCopyReadEx)(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, ULONG Length, BOOLEAN Wait,\n                                           PVOID Buffer, PIO_STATUS_BLOCK IoStatus, PETHREAD IoIssuerThread);\n\n#ifndef CC_ENABLE_DISK_IO_ACCOUNTING\n#define CC_ENABLE_DISK_IO_ACCOUNTING 0x00000010\n#endif\n\ntypedef VOID (__stdcall *tCcSetAdditionalCacheAttributesEx)(PFILE_OBJECT FileObject, ULONG Flags);\n\ntypedef VOID (__stdcall *tFsRtlUpdateDiskCounters)(ULONG64 BytesRead, ULONG64 BytesWritten);\n\ntypedef NTSTATUS (__stdcall *tIoUnregisterPlugPlayNotificationEx)(PVOID NotificationEntry);\n\ntypedef NTSTATUS (__stdcall *tFsRtlGetEcpListFromIrp)(PIRP Irp, PECP_LIST* EcpList);\n\ntypedef NTSTATUS (__stdcall *tFsRtlGetNextExtraCreateParameter)(PECP_LIST EcpList, PVOID CurrentEcpContext, LPGUID NextEcpType,\n                                                                PVOID* NextEcpContext, ULONG* NextEcpContextSize);\n\ntypedef NTSTATUS (__stdcall *tFsRtlValidateReparsePointBuffer)(ULONG BufferLength, PREPARSE_DATA_BUFFER ReparseBuffer);\n\ntypedef BOOLEAN (__stdcall *tFsRtlCheckLockForOplockRequest)(PFILE_LOCK FileLock, PLARGE_INTEGER AllocationSize);\n\ntypedef BOOLEAN (__stdcall *tFsRtlAreThereCurrentOrInProgressFileLocks)(PFILE_LOCK FileLock);\n\n#ifndef _MSC_VER\nPEPROCESS __stdcall PsGetThreadProcess(_In_ PETHREAD Thread); // not in mingw\n#endif\n\n// not in DDK headers - taken from winternl.h\ntypedef struct _LDR_DATA_TABLE_ENTRY {\n    PVOID Reserved1[2];\n    LIST_ENTRY InMemoryOrderLinks;\n    PVOID Reserved2[2];\n    PVOID DllBase;\n    PVOID Reserved3[2];\n    UNICODE_STRING FullDllName;\n    BYTE Reserved4[8];\n    PVOID Reserved5[3];\n    union {\n        ULONG CheckSum;\n        PVOID Reserved6;\n    };\n    ULONG TimeDateStamp;\n} LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY;\n\ntypedef struct _PEB_LDR_DATA {\n    BYTE Reserved1[8];\n    PVOID Reserved2[3];\n    LIST_ENTRY InMemoryOrderModuleList;\n} PEB_LDR_DATA,*PPEB_LDR_DATA;\n\ntypedef struct _RTL_USER_PROCESS_PARAMETERS {\n    BYTE Reserved1[16];\n    PVOID Reserved2[10];\n    UNICODE_STRING ImagePathName;\n    UNICODE_STRING CommandLine;\n} RTL_USER_PROCESS_PARAMETERS,*PRTL_USER_PROCESS_PARAMETERS;\n\ntypedef VOID (NTAPI *PPS_POST_PROCESS_INIT_ROUTINE)(VOID);\n\ntypedef struct _PEB {\n    BYTE Reserved1[2];\n    BYTE BeingDebugged;\n    BYTE Reserved2[1];\n    PVOID Reserved3[2];\n    PPEB_LDR_DATA Ldr;\n    PRTL_USER_PROCESS_PARAMETERS ProcessParameters;\n    BYTE Reserved4[104];\n    PVOID Reserved5[52];\n    PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;\n    BYTE Reserved6[128];\n    PVOID Reserved7[1];\n    ULONG SessionId;\n} PEB,*PPEB;\n\n#ifdef _MSC_VER\n__kernel_entry\nNTSTATUS NTAPI ZwQueryInformationProcess(\n    IN HANDLE ProcessHandle,\n    IN PROCESSINFOCLASS ProcessInformationClass,\n    OUT PVOID ProcessInformation,\n    IN ULONG ProcessInformationLength,\n    OUT PULONG ReturnLength OPTIONAL\n);\n#endif\n"
  },
  {
    "path": "src/btrfsioctl.h",
    "content": "// No copyright claimed in this file - do what you want with it.\n\n#pragma once\n\n#include \"btrfs.h\"\n\n#define FSCTL_BTRFS_GET_FILE_IDS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x829, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_CREATE_SUBVOL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82a, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_CREATE_SNAPSHOT CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82b, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_GET_INODE_INFO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82c, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_SET_INODE_INFO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82d, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_GET_DEVICES CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82e, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_GET_USAGE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x82f, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_START_BALANCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x830, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_QUERY_BALANCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x831, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_PAUSE_BALANCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x832, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_RESUME_BALANCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x833, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_STOP_BALANCE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x834, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_ADD_DEVICE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x835, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_REMOVE_DEVICE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x836, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define IOCTL_BTRFS_QUERY_FILESYSTEMS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x837, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_GET_UUID CTL_CODE(FILE_DEVICE_UNKNOWN, 0x838, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_START_SCRUB CTL_CODE(FILE_DEVICE_UNKNOWN, 0x839, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_QUERY_SCRUB CTL_CODE(FILE_DEVICE_UNKNOWN, 0x83a, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_PAUSE_SCRUB CTL_CODE(FILE_DEVICE_UNKNOWN, 0x83b, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_RESUME_SCRUB CTL_CODE(FILE_DEVICE_UNKNOWN, 0x83c, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_STOP_SCRUB CTL_CODE(FILE_DEVICE_UNKNOWN, 0x83d, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define IOCTL_BTRFS_PROBE_VOLUME CTL_CODE(FILE_DEVICE_UNKNOWN, 0x83e, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_RESET_STATS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x83f, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_MKNOD CTL_CODE(FILE_DEVICE_UNKNOWN, 0x840, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_RECEIVED_SUBVOL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x841, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_GET_XATTRS CTL_CODE(FILE_DEVICE_UNKNOWN, 0x842, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_SET_XATTR CTL_CODE(FILE_DEVICE_UNKNOWN, 0x843, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_RESERVE_SUBVOL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x844, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_FIND_SUBVOL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x845, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_SEND_SUBVOL CTL_CODE(FILE_DEVICE_UNKNOWN, 0x846, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_READ_SEND_BUFFER CTL_CODE(FILE_DEVICE_UNKNOWN, 0x847, METHOD_OUT_DIRECT, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_RESIZE CTL_CODE(FILE_DEVICE_UNKNOWN, 0x848, METHOD_IN_DIRECT, FILE_ANY_ACCESS)\n#define IOCTL_BTRFS_UNLOAD CTL_CODE(FILE_DEVICE_UNKNOWN, 0x849, METHOD_NEITHER, FILE_ANY_ACCESS)\n#define FSCTL_BTRFS_GET_CSUM_INFO CTL_CODE(FILE_DEVICE_UNKNOWN, 0x84a, METHOD_BUFFERED, FILE_READ_ACCESS)\n\ntypedef struct {\n    uint64_t subvol;\n    uint64_t inode;\n    BOOL top;\n} btrfs_get_file_ids;\n\ntypedef struct {\n    HANDLE subvol;\n    BOOL readonly;\n    BOOL posix;\n    uint16_t namelen;\n    WCHAR name[1];\n} btrfs_create_snapshot;\n\ntypedef struct {\n    void* POINTER_32 subvol;\n    BOOL readonly;\n    BOOL posix;\n    uint16_t namelen;\n    WCHAR name[1];\n} btrfs_create_snapshot32;\n\n#define BTRFS_COMPRESSION_ANY   0\n#define BTRFS_COMPRESSION_ZLIB  1\n#define BTRFS_COMPRESSION_LZO   2\n#define BTRFS_COMPRESSION_ZSTD  3\n\ntypedef struct {\n    uint64_t subvol;\n    uint64_t inode;\n    BOOL top;\n    uint8_t type;\n    uint32_t st_uid;\n    uint32_t st_gid;\n    uint32_t st_mode;\n    uint64_t st_rdev;\n    uint64_t flags;\n    uint32_t inline_length;\n    uint64_t disk_size_uncompressed;\n    uint64_t disk_size_zlib;\n    uint64_t disk_size_lzo;\n    uint8_t compression_type;\n    uint64_t disk_size_zstd;\n    uint64_t sparse_size;\n    uint32_t num_extents;\n} btrfs_inode_info;\n\ntypedef struct {\n    uint64_t flags;\n    BOOL flags_changed;\n    uint32_t st_uid;\n    BOOL uid_changed;\n    uint32_t st_gid;\n    BOOL gid_changed;\n    uint32_t st_mode;\n    BOOL mode_changed;\n    uint8_t compression_type;\n    BOOL compression_type_changed;\n} btrfs_set_inode_info;\n\ntypedef struct {\n    uint32_t next_entry;\n    uint64_t dev_id;\n    uint64_t size;\n    uint64_t max_size;\n    BOOL readonly;\n    BOOL missing;\n    ULONG device_number;\n    ULONG partition_number;\n    uint64_t stats[5];\n    USHORT namelen;\n    WCHAR name[1];\n} btrfs_device;\n\ntypedef struct {\n    uint64_t dev_id;\n    uint64_t alloc;\n} btrfs_usage_device;\n\ntypedef struct {\n    uint32_t next_entry;\n    uint64_t type;\n    uint64_t size;\n    uint64_t used;\n    uint64_t num_devices;\n    btrfs_usage_device devices[1];\n} btrfs_usage;\n\n#define BTRFS_BALANCE_OPTS_ENABLED      0x001\n#define BTRFS_BALANCE_OPTS_PROFILES     0x002\n#define BTRFS_BALANCE_OPTS_DEVID        0x004\n#define BTRFS_BALANCE_OPTS_DRANGE       0x008\n#define BTRFS_BALANCE_OPTS_VRANGE       0x010\n#define BTRFS_BALANCE_OPTS_LIMIT        0x020\n#define BTRFS_BALANCE_OPTS_STRIPES      0x040\n#define BTRFS_BALANCE_OPTS_USAGE        0x080\n#define BTRFS_BALANCE_OPTS_CONVERT      0x100\n#define BTRFS_BALANCE_OPTS_SOFT         0x200\n\n#define BLOCK_FLAG_SINGLE 0x1000000000000 // only used in balance\n\ntypedef struct {\n    uint64_t flags;\n    uint64_t profiles;\n    uint64_t devid;\n    uint64_t drange_start;\n    uint64_t drange_end;\n    uint64_t vrange_start;\n    uint64_t vrange_end;\n    uint64_t limit_start;\n    uint64_t limit_end;\n    uint16_t stripes_start;\n    uint16_t stripes_end;\n    uint8_t usage_start;\n    uint8_t usage_end;\n    uint64_t convert;\n} btrfs_balance_opts;\n\n#define BTRFS_BALANCE_STOPPED   0\n#define BTRFS_BALANCE_RUNNING   1\n#define BTRFS_BALANCE_PAUSED    2\n#define BTRFS_BALANCE_REMOVAL   4\n#define BTRFS_BALANCE_ERROR     8\n#define BTRFS_BALANCE_SHRINKING 16\n\ntypedef struct {\n    uint32_t status;\n    uint64_t chunks_left;\n    uint64_t total_chunks;\n    NTSTATUS error;\n    btrfs_balance_opts data_opts;\n    btrfs_balance_opts metadata_opts;\n    btrfs_balance_opts system_opts;\n} btrfs_query_balance;\n\ntypedef struct {\n    btrfs_balance_opts opts[3];\n} btrfs_start_balance;\n\ntypedef struct {\n    uint8_t uuid[16];\n    BOOL missing;\n    USHORT name_length;\n    WCHAR name[1];\n} btrfs_filesystem_device;\n\ntypedef struct {\n    uint32_t next_entry;\n    uint8_t uuid[16];\n    uint32_t num_devices;\n    btrfs_filesystem_device device;\n} btrfs_filesystem;\n\n#define BTRFS_SCRUB_STOPPED     0\n#define BTRFS_SCRUB_RUNNING     1\n#define BTRFS_SCRUB_PAUSED      2\n\ntypedef struct {\n    uint32_t next_entry;\n    uint64_t address;\n    uint64_t device;\n    BOOL recovered;\n    BOOL is_metadata;\n    BOOL parity;\n\n    union {\n        struct {\n            uint64_t subvol;\n            uint64_t offset;\n            uint16_t filename_length;\n            WCHAR filename[1];\n        } data;\n\n        struct {\n            uint64_t root;\n            uint8_t level;\n            KEY firstitem;\n        } metadata;\n    };\n} btrfs_scrub_error;\n\ntypedef struct {\n    uint32_t status;\n    LARGE_INTEGER start_time;\n    LARGE_INTEGER finish_time;\n    uint64_t chunks_left;\n    uint64_t total_chunks;\n    uint64_t data_scrubbed;\n    uint64_t duration;\n    NTSTATUS error;\n    uint32_t num_errors;\n    btrfs_scrub_error errors;\n} btrfs_query_scrub;\n\ntypedef struct {\n    uint64_t inode;\n    uint8_t type;\n    uint64_t st_rdev;\n    uint16_t namelen;\n    WCHAR name[1];\n} btrfs_mknod;\n\ntypedef struct {\n    uint64_t generation;\n    BTRFS_UUID uuid;\n} btrfs_received_subvol;\n\ntypedef struct {\n    USHORT namelen;\n    USHORT valuelen;\n    char data[1];\n} btrfs_set_xattr;\n\ntypedef struct {\n    BOOL readonly;\n    BOOL posix;\n    USHORT namelen;\n    WCHAR name[1];\n} btrfs_create_subvol;\n\ntypedef struct {\n    BTRFS_UUID uuid;\n    uint64_t ctransid;\n} btrfs_find_subvol;\n\ntypedef struct {\n    HANDLE parent;\n    ULONG num_clones;\n    HANDLE clones[1];\n} btrfs_send_subvol;\n\ntypedef struct {\n    void* POINTER_32 parent;\n    ULONG num_clones;\n    void* POINTER_32 clones[1];\n} btrfs_send_subvol32;\n\ntypedef struct {\n    uint64_t device;\n    uint64_t size;\n} btrfs_resize;\n\ntypedef struct {\n    uint8_t csum_type;\n    uint8_t csum_length;\n    uint64_t num_sectors;\n    uint8_t data[1];\n} btrfs_csum_info;\n"
  },
  {
    "path": "src/cache.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n\nCACHE_MANAGER_CALLBACKS cache_callbacks;\n\nstatic BOOLEAN __stdcall acquire_for_lazy_write(PVOID Context, BOOLEAN Wait) {\n    PFILE_OBJECT FileObject = Context;\n    fcb* fcb = FileObject->FsContext;\n\n    TRACE(\"(%p, %u)\\n\", Context, Wait);\n\n    if (!ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, Wait))\n        return false;\n\n    if (!ExAcquireResourceExclusiveLite(fcb->Header.Resource, Wait)) {\n        ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n        return false;\n    }\n\n    fcb->lazy_writer_thread = KeGetCurrentThread();\n\n    IoSetTopLevelIrp((PIRP)FSRTL_CACHE_TOP_LEVEL_IRP);\n\n    return true;\n}\n\nstatic void __stdcall release_from_lazy_write(PVOID Context) {\n    PFILE_OBJECT FileObject = Context;\n    fcb* fcb = FileObject->FsContext;\n\n    TRACE(\"(%p)\\n\", Context);\n\n    fcb->lazy_writer_thread = NULL;\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n\n    if (IoGetTopLevelIrp() == (PIRP)FSRTL_CACHE_TOP_LEVEL_IRP)\n        IoSetTopLevelIrp(NULL);\n}\n\nstatic BOOLEAN __stdcall acquire_for_read_ahead(PVOID Context, BOOLEAN Wait) {\n    PFILE_OBJECT FileObject = Context;\n    fcb* fcb = FileObject->FsContext;\n\n    TRACE(\"(%p, %u)\\n\", Context, Wait);\n\n    if (!ExAcquireResourceSharedLite(fcb->Header.Resource, Wait))\n        return false;\n\n    IoSetTopLevelIrp((PIRP)FSRTL_CACHE_TOP_LEVEL_IRP);\n\n    return true;\n}\n\nstatic void __stdcall release_from_read_ahead(PVOID Context) {\n    PFILE_OBJECT FileObject = Context;\n    fcb* fcb = FileObject->FsContext;\n\n    TRACE(\"(%p)\\n\", Context);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    if (IoGetTopLevelIrp() == (PIRP)FSRTL_CACHE_TOP_LEVEL_IRP)\n        IoSetTopLevelIrp(NULL);\n}\n\nvoid init_cache() {\n    cache_callbacks.AcquireForLazyWrite = acquire_for_lazy_write;\n    cache_callbacks.ReleaseFromLazyWrite = release_from_lazy_write;\n    cache_callbacks.AcquireForReadAhead = acquire_for_read_ahead;\n    cache_callbacks.ReleaseFromReadAhead = release_from_read_ahead;\n}\n"
  },
  {
    "path": "src/calcthread.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include \"zstd/lib/common/xxhash.h\"\n#include \"crc32c.h\"\n\nvoid calc_thread_main(device_extension* Vcb, calc_job* cj) {\n    while (true) {\n        KIRQL irql;\n        calc_job* cj2;\n        uint8_t* src;\n        void* dest;\n        bool last_one = false;\n\n        KeAcquireSpinLock(&Vcb->calcthreads.spinlock, &irql);\n\n        if (cj && cj->not_started == 0) {\n            KeReleaseSpinLock(&Vcb->calcthreads.spinlock, irql);\n            break;\n        }\n\n        if (cj)\n            cj2 = cj;\n        else {\n            if (IsListEmpty(&Vcb->calcthreads.job_list)) {\n                KeReleaseSpinLock(&Vcb->calcthreads.spinlock, irql);\n                break;\n            }\n\n            cj2 = CONTAINING_RECORD(Vcb->calcthreads.job_list.Flink, calc_job, list_entry);\n        }\n\n        src = cj2->in;\n        dest = cj2->out;\n\n        switch (cj2->type) {\n            case calc_thread_crc32c:\n            case calc_thread_xxhash:\n            case calc_thread_sha256:\n            case calc_thread_blake2:\n                cj2->in = (uint8_t*)cj2->in + Vcb->superblock.sector_size;\n                cj2->out = (uint8_t*)cj2->out + Vcb->csum_size;\n            break;\n\n            default:\n                break;\n        }\n\n        cj2->not_started--;\n\n        if (cj2->not_started == 0) {\n            RemoveEntryList(&cj2->list_entry);\n            last_one = true;\n        }\n\n        KeReleaseSpinLock(&Vcb->calcthreads.spinlock, irql);\n\n        switch (cj2->type) {\n            case calc_thread_crc32c:\n                *(uint32_t*)dest = ~calc_crc32c(0xffffffff, src, Vcb->superblock.sector_size);\n            break;\n\n            case calc_thread_xxhash:\n                *(uint64_t*)dest = XXH64(src, Vcb->superblock.sector_size, 0);\n            break;\n\n            case calc_thread_sha256:\n                calc_sha256(dest, src, Vcb->superblock.sector_size);\n            break;\n\n            case calc_thread_blake2:\n                blake2b(dest, BLAKE2_HASH_SIZE, src, Vcb->superblock.sector_size);\n            break;\n\n            case calc_thread_decomp_zlib:\n                cj2->Status = zlib_decompress(src, cj2->inlen, dest, cj2->outlen);\n\n                if (!NT_SUCCESS(cj2->Status))\n                    ERR(\"zlib_decompress returned %08lx\\n\", cj2->Status);\n            break;\n\n            case calc_thread_decomp_lzo:\n                cj2->Status = lzo_decompress(src, cj2->inlen, dest, cj2->outlen, cj2->off);\n\n                if (!NT_SUCCESS(cj2->Status))\n                    ERR(\"lzo_decompress returned %08lx\\n\", cj2->Status);\n            break;\n\n            case calc_thread_decomp_zstd:\n                cj2->Status = zstd_decompress(src, cj2->inlen, dest, cj2->outlen);\n\n                if (!NT_SUCCESS(cj2->Status))\n                    ERR(\"zstd_decompress returned %08lx\\n\", cj2->Status);\n            break;\n\n            case calc_thread_comp_zlib:\n                cj2->Status = zlib_compress(src, cj2->inlen, dest, cj2->outlen, Vcb->options.zlib_level, &cj2->space_left);\n\n                if (!NT_SUCCESS(cj2->Status))\n                    ERR(\"zlib_compress returned %08lx\\n\", cj2->Status);\n            break;\n\n            case calc_thread_comp_lzo:\n                cj2->Status = lzo_compress(src, cj2->inlen, dest, cj2->outlen, &cj2->space_left);\n\n                if (!NT_SUCCESS(cj2->Status))\n                    ERR(\"lzo_compress returned %08lx\\n\", cj2->Status);\n            break;\n\n            case calc_thread_comp_zstd:\n                cj2->Status = zstd_compress(src, cj2->inlen, dest, cj2->outlen, Vcb->options.zstd_level, &cj2->space_left);\n\n                if (!NT_SUCCESS(cj2->Status))\n                    ERR(\"zstd_compress returned %08lx\\n\", cj2->Status);\n            break;\n        }\n\n        if (InterlockedDecrement(&cj2->left) == 0)\n            KeSetEvent(&cj2->event, 0, false);\n\n        if (last_one)\n            break;\n    }\n}\n\nvoid do_calc_job(device_extension* Vcb, uint8_t* data, uint32_t sectors, void* csum) {\n    KIRQL irql;\n    calc_job cj;\n\n    cj.in = data;\n    cj.out = csum;\n    cj.left = cj.not_started = sectors;\n\n    switch (Vcb->superblock.csum_type) {\n        case CSUM_TYPE_CRC32C:\n            cj.type = calc_thread_crc32c;\n        break;\n\n        case CSUM_TYPE_XXHASH:\n            cj.type = calc_thread_xxhash;\n        break;\n\n        case CSUM_TYPE_SHA256:\n            cj.type = calc_thread_sha256;\n        break;\n\n        case CSUM_TYPE_BLAKE2:\n            cj.type = calc_thread_blake2;\n        break;\n    }\n\n    KeInitializeEvent(&cj.event, NotificationEvent, false);\n\n    KeAcquireSpinLock(&Vcb->calcthreads.spinlock, &irql);\n\n    InsertTailList(&Vcb->calcthreads.job_list, &cj.list_entry);\n\n    KeSetEvent(&Vcb->calcthreads.event, 0, false);\n    KeClearEvent(&Vcb->calcthreads.event);\n\n    KeReleaseSpinLock(&Vcb->calcthreads.spinlock, irql);\n\n    calc_thread_main(Vcb, &cj);\n\n    KeWaitForSingleObject(&cj.event, Executive, KernelMode, false, NULL);\n}\n\nNTSTATUS add_calc_job_decomp(device_extension* Vcb, uint8_t compression, void* in, unsigned int inlen,\n                             void* out, unsigned int outlen, unsigned int off, calc_job** pcj) {\n    calc_job* cj;\n    KIRQL irql;\n\n    cj = ExAllocatePoolWithTag(NonPagedPool, sizeof(calc_job), ALLOC_TAG);\n    if (!cj) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    cj->in = in;\n    cj->inlen = inlen;\n    cj->out = out;\n    cj->outlen = outlen;\n    cj->off = off;\n    cj->left = cj->not_started = 1;\n    cj->Status = STATUS_SUCCESS;\n\n    switch (compression) {\n        case BTRFS_COMPRESSION_ZLIB:\n            cj->type = calc_thread_decomp_zlib;\n        break;\n\n        case BTRFS_COMPRESSION_LZO:\n            cj->type = calc_thread_decomp_lzo;\n        break;\n\n        case BTRFS_COMPRESSION_ZSTD:\n            cj->type = calc_thread_decomp_zstd;\n        break;\n\n        default:\n            ERR(\"unexpected compression type %x\\n\", compression);\n            ExFreePool(cj);\n        return STATUS_NOT_SUPPORTED;\n    }\n\n    KeInitializeEvent(&cj->event, NotificationEvent, false);\n\n    KeAcquireSpinLock(&Vcb->calcthreads.spinlock, &irql);\n\n    InsertTailList(&Vcb->calcthreads.job_list, &cj->list_entry);\n\n    KeSetEvent(&Vcb->calcthreads.event, 0, false);\n    KeClearEvent(&Vcb->calcthreads.event);\n\n    KeReleaseSpinLock(&Vcb->calcthreads.spinlock, irql);\n\n    *pcj = cj;\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS add_calc_job_comp(device_extension* Vcb, uint8_t compression, void* in, unsigned int inlen,\n                           void* out, unsigned int outlen, calc_job** pcj) {\n    calc_job* cj;\n    KIRQL irql;\n\n    cj = ExAllocatePoolWithTag(NonPagedPool, sizeof(calc_job), ALLOC_TAG);\n    if (!cj) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    cj->in = in;\n    cj->inlen = inlen;\n    cj->out = out;\n    cj->outlen = outlen;\n    cj->left = cj->not_started = 1;\n    cj->Status = STATUS_SUCCESS;\n\n    switch (compression) {\n        case BTRFS_COMPRESSION_ZLIB:\n            cj->type = calc_thread_comp_zlib;\n        break;\n\n        case BTRFS_COMPRESSION_LZO:\n            cj->type = calc_thread_comp_lzo;\n        break;\n\n        case BTRFS_COMPRESSION_ZSTD:\n            cj->type = calc_thread_comp_zstd;\n        break;\n\n        default:\n            ERR(\"unexpected compression type %x\\n\", compression);\n            ExFreePool(cj);\n        return STATUS_NOT_SUPPORTED;\n    }\n\n    KeInitializeEvent(&cj->event, NotificationEvent, false);\n\n    KeAcquireSpinLock(&Vcb->calcthreads.spinlock, &irql);\n\n    InsertTailList(&Vcb->calcthreads.job_list, &cj->list_entry);\n\n    KeSetEvent(&Vcb->calcthreads.event, 0, false);\n    KeClearEvent(&Vcb->calcthreads.event);\n\n    KeReleaseSpinLock(&Vcb->calcthreads.spinlock, irql);\n\n    *pcj = cj;\n\n    return STATUS_SUCCESS;\n}\n\n_Function_class_(KSTART_ROUTINE)\nvoid __stdcall calc_thread(void* context) {\n    drv_calc_thread* thread = context;\n    device_extension* Vcb = thread->DeviceObject->DeviceExtension;\n\n    ObReferenceObject(thread->DeviceObject);\n\n    KeSetSystemAffinityThread((KAFFINITY)1 << thread->number);\n\n    while (true) {\n        KeWaitForSingleObject(&Vcb->calcthreads.event, Executive, KernelMode, false, NULL);\n\n        calc_thread_main(Vcb, NULL);\n\n        if (thread->quit)\n            break;\n    }\n\n    ObDereferenceObject(thread->DeviceObject);\n\n    KeSetEvent(&thread->finished, 0, false);\n\n    PsTerminateSystemThread(STATUS_SUCCESS);\n}\n"
  },
  {
    "path": "src/compress.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n * Copyright (c) Reimar Doeffinger 2006\n * Copyright (c) Markus Oberhumer 1996\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n// Portions of the LZO decompression code here were cribbed from code in\n// libavcodec, also under the LGPL. Thank you, Reimar Doeffinger.\n\n// The LZO compression code comes from v0.22 of lzo, written way back in\n// 1996, and available here:\n// https://www.ibiblio.org/pub/historic-linux/ftp-archives/sunsite.unc.edu/Sep-29-1996/libs/lzo-0.22.tar.gz\n// Modern versions of lzo are licensed under the GPL, but the very oldest\n// versions are under the LGPL and hence okay to use here.\n\n#include \"btrfs_drv.h\"\n#include \"zlib/zlib.h\"\n\n#define ZSTD_STATIC_LINKING_ONLY\n\n#include \"zstd/lib/zstd.h\"\n\n#define LZO_PAGE_SIZE 4096\n\ntypedef struct {\n    uint8_t* in;\n    uint32_t inlen;\n    uint32_t inpos;\n    uint8_t* out;\n    uint32_t outlen;\n    uint32_t outpos;\n    bool error;\n    void* wrkmem;\n} lzo_stream;\n\n#define LZO1X_MEM_COMPRESS ((uint32_t) (16384L * sizeof(uint8_t*)))\n\n#define M1_MAX_OFFSET 0x0400\n#define M2_MAX_OFFSET 0x0800\n#define M3_MAX_OFFSET 0x4000\n#define M4_MAX_OFFSET 0xbfff\n\n#define MX_MAX_OFFSET (M1_MAX_OFFSET + M2_MAX_OFFSET)\n\n#define M1_MARKER 0\n#define M2_MARKER 64\n#define M3_MARKER 32\n#define M4_MARKER 16\n\n#define _DV2(p, shift1, shift2) (((( (uint32_t)(p[2]) << shift1) ^ p[1]) << shift2) ^ p[0])\n#define DVAL_NEXT(dv, p) dv ^= p[-1]; dv = (((dv) >> 5) ^ ((uint32_t)(p[2]) << (2*5)))\n#define _DV(p, shift) _DV2(p, shift, shift)\n#define DVAL_FIRST(dv, p) dv = _DV((p), 5)\n#define _DINDEX(dv, p) ((40799u * (dv)) >> 5)\n#define DINDEX(dv, p) (((_DINDEX(dv, p)) & 0x3fff) << 0)\n#define UPDATE_D(dict, cycle, dv, p) dict[DINDEX(dv, p)] = (p)\n#define UPDATE_I(dict, cycle, index, p) dict[index] = (p)\n\n#define LZO_CHECK_MPOS_NON_DET(m_pos, m_off, in, ip, max_offset) \\\n    ((void*) m_pos < (void*) in || \\\n    (m_off = (uint8_t*) ip - (uint8_t*) m_pos) <= 0 || \\\n    m_off > max_offset)\n\n#define LZO_BYTE(x) ((unsigned char) (x))\n\n#define ZSTD_ALLOC_TAG 0x6474737a // \"zstd\"\n\n// needs to be the same as Linux (fs/btrfs/zstd.c)\n#define ZSTD_BTRFS_MAX_WINDOWLOG 17\n\nstatic void* zstd_malloc(void* opaque, size_t size);\nstatic void zstd_free(void* opaque, void* address);\n\nstatic const ZSTD_customMem zstd_mem = { .customAlloc = zstd_malloc, .customFree = zstd_free, .opaque = NULL };\n\nstatic uint8_t lzo_nextbyte(lzo_stream* stream) {\n    uint8_t c;\n\n    if (stream->inpos >= stream->inlen) {\n        stream->error = true;\n        return 0;\n    }\n\n    c = stream->in[stream->inpos];\n    stream->inpos++;\n\n    return c;\n}\n\nstatic int lzo_len(lzo_stream* stream, int byte, int mask) {\n    int len = byte & mask;\n\n    if (len == 0) {\n        while (!(byte = lzo_nextbyte(stream))) {\n            if (stream->error) return 0;\n\n            len += 255;\n        }\n\n        len += mask + byte;\n    }\n\n    return len;\n}\n\nstatic void lzo_copy(lzo_stream* stream, int len) {\n    if (stream->inpos + len > stream->inlen) {\n        stream->error = true;\n        return;\n    }\n\n    if (stream->outpos + len > stream->outlen) {\n        stream->error = true;\n        return;\n    }\n\n    do {\n        stream->out[stream->outpos] = stream->in[stream->inpos];\n        stream->inpos++;\n        stream->outpos++;\n        len--;\n    } while (len > 0);\n}\n\nstatic void lzo_copyback(lzo_stream* stream, uint32_t back, int len) {\n    if (stream->outpos < back) {\n        stream->error = true;\n        return;\n    }\n\n    if (stream->outpos + len > stream->outlen) {\n        stream->error = true;\n        return;\n    }\n\n    do {\n        stream->out[stream->outpos] = stream->out[stream->outpos - back];\n        stream->outpos++;\n        len--;\n    } while (len > 0);\n}\n\nstatic NTSTATUS do_lzo_decompress(lzo_stream* stream) {\n    uint8_t byte;\n    uint32_t len, back;\n    bool backcopy = false;\n\n    stream->error = false;\n\n    byte = lzo_nextbyte(stream);\n    if (stream->error) return STATUS_INTERNAL_ERROR;\n\n    if (byte > 17) {\n        lzo_copy(stream, min((uint8_t)(byte - 17), (uint32_t)(stream->outlen - stream->outpos)));\n        if (stream->error) return STATUS_INTERNAL_ERROR;\n\n        if (stream->outlen == stream->outpos)\n            return STATUS_SUCCESS;\n\n        byte = lzo_nextbyte(stream);\n        if (stream->error) return STATUS_INTERNAL_ERROR;\n\n        if (byte < 16) return STATUS_INTERNAL_ERROR;\n    }\n\n    while (1) {\n        if (byte >> 4) {\n            backcopy = true;\n            if (byte >> 6) {\n                len = (byte >> 5) - 1;\n                back = (lzo_nextbyte(stream) << 3) + ((byte >> 2) & 7) + 1;\n                if (stream->error) return STATUS_INTERNAL_ERROR;\n            } else if (byte >> 5) {\n                len = lzo_len(stream, byte, 31);\n                if (stream->error) return STATUS_INTERNAL_ERROR;\n\n                byte = lzo_nextbyte(stream);\n                if (stream->error) return STATUS_INTERNAL_ERROR;\n\n                back = (lzo_nextbyte(stream) << 6) + (byte >> 2) + 1;\n                if (stream->error) return STATUS_INTERNAL_ERROR;\n            } else {\n                len = lzo_len(stream, byte, 7);\n                if (stream->error) return STATUS_INTERNAL_ERROR;\n\n                back = (1 << 14) + ((byte & 8) << 11);\n\n                byte = lzo_nextbyte(stream);\n                if (stream->error) return STATUS_INTERNAL_ERROR;\n\n                back += (lzo_nextbyte(stream) << 6) + (byte >> 2);\n                if (stream->error) return STATUS_INTERNAL_ERROR;\n\n                if (back == (1 << 14)) {\n                    if (len != 1)\n                        return STATUS_INTERNAL_ERROR;\n                    break;\n                }\n            }\n        } else if (backcopy) {\n            len = 0;\n            back = (lzo_nextbyte(stream) << 2) + (byte >> 2) + 1;\n            if (stream->error) return STATUS_INTERNAL_ERROR;\n        } else {\n            len = lzo_len(stream, byte, 15);\n            if (stream->error) return STATUS_INTERNAL_ERROR;\n\n            lzo_copy(stream, min(len + 3, stream->outlen - stream->outpos));\n            if (stream->error) return STATUS_INTERNAL_ERROR;\n\n            if (stream->outlen == stream->outpos)\n                return STATUS_SUCCESS;\n\n            byte = lzo_nextbyte(stream);\n            if (stream->error) return STATUS_INTERNAL_ERROR;\n\n            if (byte >> 4)\n                continue;\n\n            len = 1;\n            back = (1 << 11) + (lzo_nextbyte(stream) << 2) + (byte >> 2) + 1;\n            if (stream->error) return STATUS_INTERNAL_ERROR;\n\n            break;\n        }\n\n        lzo_copyback(stream, back, min(len + 2, stream->outlen - stream->outpos));\n        if (stream->error) return STATUS_INTERNAL_ERROR;\n\n        if (stream->outlen == stream->outpos)\n            return STATUS_SUCCESS;\n\n        len = byte & 3;\n\n        if (len) {\n            lzo_copy(stream, min(len, stream->outlen - stream->outpos));\n            if (stream->error) return STATUS_INTERNAL_ERROR;\n\n            if (stream->outlen == stream->outpos)\n                return STATUS_SUCCESS;\n        } else\n            backcopy = !backcopy;\n\n        byte = lzo_nextbyte(stream);\n        if (stream->error) return STATUS_INTERNAL_ERROR;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS lzo_decompress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, uint32_t inpageoff) {\n    NTSTATUS Status;\n    uint32_t partlen, inoff, outoff;\n    lzo_stream stream;\n\n    inoff = 0;\n    outoff = 0;\n\n    do {\n        partlen = *(uint32_t*)&inbuf[inoff];\n\n        if (partlen + inoff > inlen) {\n            ERR(\"overflow: %x + %x > %x\\n\", partlen, inoff, inlen);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        inoff += sizeof(uint32_t);\n\n        stream.in = &inbuf[inoff];\n        stream.inlen = partlen;\n        stream.inpos = 0;\n        stream.out = &outbuf[outoff];\n        stream.outlen = min(outlen, LZO_PAGE_SIZE);\n        stream.outpos = 0;\n\n        Status = do_lzo_decompress(&stream);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"do_lzo_decompress returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (stream.outpos < stream.outlen)\n            RtlZeroMemory(&stream.out[stream.outpos], stream.outlen - stream.outpos);\n\n        inoff += partlen;\n        outoff += stream.outlen;\n\n        if (LZO_PAGE_SIZE - ((inpageoff + inoff) % LZO_PAGE_SIZE) < sizeof(uint32_t))\n            inoff = ((((inpageoff + inoff) / LZO_PAGE_SIZE) + 1) * LZO_PAGE_SIZE) - inpageoff;\n\n        outlen -= stream.outlen;\n    } while (inoff < inlen && outlen > 0);\n\n    return STATUS_SUCCESS;\n}\n\nstatic void* zlib_alloc(void* opaque, unsigned int items, unsigned int size) {\n    UNUSED(opaque);\n\n    return ExAllocatePoolWithTag(PagedPool, items * size, ALLOC_TAG_ZLIB);\n}\n\nstatic void zlib_free(void* opaque, void* ptr) {\n    UNUSED(opaque);\n\n    ExFreePool(ptr);\n}\n\nNTSTATUS zlib_compress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, unsigned int level, unsigned int* space_left) {\n    z_stream c_stream;\n    int ret;\n\n    c_stream.zalloc = zlib_alloc;\n    c_stream.zfree = zlib_free;\n    c_stream.opaque = (voidpf)0;\n\n    ret = deflateInit(&c_stream, level);\n\n    if (ret != Z_OK) {\n        ERR(\"deflateInit returned %i\\n\", ret);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    c_stream.next_in = inbuf;\n    c_stream.avail_in = inlen;\n\n    c_stream.next_out = outbuf;\n    c_stream.avail_out = outlen;\n\n    do {\n        ret = deflate(&c_stream, Z_FINISH);\n\n        if (ret != Z_OK && ret != Z_STREAM_END) {\n            ERR(\"deflate returned %i\\n\", ret);\n            deflateEnd(&c_stream);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (c_stream.avail_in == 0 || c_stream.avail_out == 0)\n            break;\n    } while (ret != Z_STREAM_END);\n\n    deflateEnd(&c_stream);\n\n    *space_left = c_stream.avail_in > 0 ? 0 : c_stream.avail_out;\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS zlib_decompress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen) {\n    z_stream c_stream;\n    int ret;\n\n    c_stream.zalloc = zlib_alloc;\n    c_stream.zfree = zlib_free;\n    c_stream.opaque = (voidpf)0;\n\n    ret = inflateInit(&c_stream);\n\n    if (ret != Z_OK) {\n        ERR(\"inflateInit returned %i\\n\", ret);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    c_stream.next_in = inbuf;\n    c_stream.avail_in = inlen;\n\n    c_stream.next_out = outbuf;\n    c_stream.avail_out = outlen;\n\n    do {\n        ret = inflate(&c_stream, Z_NO_FLUSH);\n\n        if (ret != Z_OK && ret != Z_STREAM_END) {\n            ERR(\"inflate returned %i\\n\", ret);\n            inflateEnd(&c_stream);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (c_stream.avail_out == 0)\n            break;\n    } while (ret != Z_STREAM_END);\n\n    ret = inflateEnd(&c_stream);\n\n    if (ret != Z_OK) {\n        ERR(\"inflateEnd returned %i\\n\", ret);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    // FIXME - if we're short, should we zero the end of outbuf so we don't leak information into userspace?\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS lzo_do_compress(const uint8_t* in, uint32_t in_len, uint8_t* out, uint32_t* out_len, void* wrkmem) {\n    const uint8_t* ip;\n    uint32_t dv;\n    uint8_t* op;\n    const uint8_t* in_end = in + in_len;\n    const uint8_t* ip_end = in + in_len - 9 - 4;\n    const uint8_t* ii;\n    const uint8_t** dict = (const uint8_t**)wrkmem;\n\n    op = out;\n    ip = in;\n    ii = ip;\n\n    DVAL_FIRST(dv, ip); UPDATE_D(dict, cycle, dv, ip); ip++;\n    DVAL_NEXT(dv, ip);  UPDATE_D(dict, cycle, dv, ip); ip++;\n    DVAL_NEXT(dv, ip);  UPDATE_D(dict, cycle, dv, ip); ip++;\n    DVAL_NEXT(dv, ip);  UPDATE_D(dict, cycle, dv, ip); ip++;\n\n    while (1) {\n        const uint8_t* m_pos;\n        uint32_t m_len;\n        ptrdiff_t m_off;\n        uint32_t lit, dindex;\n\n        dindex = DINDEX(dv, ip);\n        m_pos = dict[dindex];\n        UPDATE_I(dict, cycle, dindex, ip);\n\n        if (!LZO_CHECK_MPOS_NON_DET(m_pos, m_off, in, ip, M4_MAX_OFFSET) && m_pos[0] == ip[0] && m_pos[1] == ip[1] && m_pos[2] == ip[2]) {\n            lit = (uint32_t)(ip - ii);\n            m_pos += 3;\n            if (m_off <= M2_MAX_OFFSET)\n                goto match;\n\n            if (lit == 3) { /* better compression, but slower */\n                if (op - 2 <= out)\n                    return STATUS_INTERNAL_ERROR;\n\n                op[-2] |= LZO_BYTE(3);\n                *op++ = *ii++; *op++ = *ii++; *op++ = *ii++;\n                goto code_match;\n            }\n\n            if (*m_pos == ip[3])\n                goto match;\n        }\n\n        /* a literal */\n        ++ip;\n        if (ip >= ip_end)\n            break;\n        DVAL_NEXT(dv, ip);\n        continue;\n\n        /* a match */\nmatch:\n        /* store current literal run */\n        if (lit > 0) {\n            uint32_t t = lit;\n\n            if (t <= 3) {\n                if (op - 2 <= out)\n                    return STATUS_INTERNAL_ERROR;\n\n                op[-2] |= LZO_BYTE(t);\n            } else if (t <= 18)\n                *op++ = LZO_BYTE(t - 3);\n            else {\n                uint32_t tt = t - 18;\n\n                *op++ = 0;\n                while (tt > 255) {\n                    tt -= 255;\n                    *op++ = 0;\n                }\n\n                if (tt <= 0)\n                    return STATUS_INTERNAL_ERROR;\n\n                *op++ = LZO_BYTE(tt);\n            }\n\n            do {\n                *op++ = *ii++;\n            } while (--t > 0);\n        }\n\n\n        /* code the match */\ncode_match:\n        if (ii != ip)\n            return STATUS_INTERNAL_ERROR;\n\n        ip += 3;\n        if (*m_pos++ != *ip++ || *m_pos++ != *ip++ || *m_pos++ != *ip++ ||\n            *m_pos++ != *ip++ || *m_pos++ != *ip++ || *m_pos++ != *ip++) {\n            --ip;\n            m_len = (uint32_t)(ip - ii);\n\n            if (m_len < 3 || m_len > 8)\n                return STATUS_INTERNAL_ERROR;\n\n            if (m_off <= M2_MAX_OFFSET) {\n                m_off -= 1;\n                *op++ = LZO_BYTE(((m_len - 1) << 5) | ((m_off & 7) << 2));\n                *op++ = LZO_BYTE(m_off >> 3);\n            } else if (m_off <= M3_MAX_OFFSET) {\n                m_off -= 1;\n                *op++ = LZO_BYTE(M3_MARKER | (m_len - 2));\n                goto m3_m4_offset;\n            } else {\n                m_off -= 0x4000;\n\n                if (m_off <= 0 || m_off > 0x7fff)\n                    return STATUS_INTERNAL_ERROR;\n\n                *op++ = LZO_BYTE(M4_MARKER | ((m_off & 0x4000) >> 11) | (m_len - 2));\n                goto m3_m4_offset;\n            }\n        } else {\n            const uint8_t* end;\n            end = in_end;\n            while (ip < end && *m_pos == *ip)\n                m_pos++, ip++;\n            m_len = (uint32_t)(ip - ii);\n\n            if (m_len < 3)\n                return STATUS_INTERNAL_ERROR;\n\n            if (m_off <= M3_MAX_OFFSET) {\n                m_off -= 1;\n                if (m_len <= 33)\n                    *op++ = LZO_BYTE(M3_MARKER | (m_len - 2));\n                else {\n                    m_len -= 33;\n                    *op++ = M3_MARKER | 0;\n                    goto m3_m4_len;\n                }\n            } else {\n                m_off -= 0x4000;\n\n                if (m_off <= 0 || m_off > 0x7fff)\n                    return STATUS_INTERNAL_ERROR;\n\n                if (m_len <= 9)\n                    *op++ = LZO_BYTE(M4_MARKER | ((m_off & 0x4000) >> 11) | (m_len - 2));\n                else {\n                    m_len -= 9;\n                    *op++ = LZO_BYTE(M4_MARKER | ((m_off & 0x4000) >> 11));\nm3_m4_len:\n                    while (m_len > 255) {\n                        m_len -= 255;\n                        *op++ = 0;\n                    }\n\n                    if (m_len <= 0)\n                        return STATUS_INTERNAL_ERROR;\n\n                    *op++ = LZO_BYTE(m_len);\n                }\n            }\n\nm3_m4_offset:\n            *op++ = LZO_BYTE((m_off & 63) << 2);\n            *op++ = LZO_BYTE(m_off >> 6);\n        }\n\n        ii = ip;\n        if (ip >= ip_end)\n            break;\n        DVAL_FIRST(dv, ip);\n    }\n\n    /* store final literal run */\n    if (in_end - ii > 0) {\n        uint32_t t = (uint32_t)(in_end - ii);\n\n        if (op == out && t <= 238)\n            *op++ = LZO_BYTE(17 + t);\n        else if (t <= 3)\n            op[-2] |= LZO_BYTE(t);\n        else if (t <= 18)\n            *op++ = LZO_BYTE(t - 3);\n        else {\n            uint32_t tt = t - 18;\n\n            *op++ = 0;\n            while (tt > 255) {\n                tt -= 255;\n                *op++ = 0;\n            }\n\n            if (tt <= 0)\n                return STATUS_INTERNAL_ERROR;\n\n            *op++ = LZO_BYTE(tt);\n        }\n\n        do {\n            *op++ = *ii++;\n        } while (--t > 0);\n    }\n\n    *out_len = (uint32_t)(op - out);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS lzo1x_1_compress(lzo_stream* stream) {\n    uint8_t *op = stream->out;\n    NTSTATUS Status = STATUS_SUCCESS;\n\n    if (stream->inlen <= 0)\n        stream->outlen = 0;\n    else if (stream->inlen <= 9 + 4) {\n        *op++ = LZO_BYTE(17 + stream->inlen);\n\n        stream->inpos = 0;\n        do {\n            *op++ = stream->in[stream->inpos];\n            stream->inpos++;\n        } while (stream->inlen < stream->inpos);\n        stream->outlen = (uint32_t)(op - stream->out);\n    } else\n        Status = lzo_do_compress(stream->in, stream->inlen, stream->out, &stream->outlen, stream->wrkmem);\n\n    if (Status == STATUS_SUCCESS) {\n        op = stream->out + stream->outlen;\n        *op++ = M4_MARKER | 1;\n        *op++ = 0;\n        *op++ = 0;\n        stream->outlen += 3;\n    }\n\n    return Status;\n}\n\nstatic __inline uint32_t lzo_max_outlen(uint32_t inlen) {\n    return inlen + (inlen / 16) + 64 + 3; // formula comes from LZO.FAQ\n}\n\nstatic void* zstd_malloc(void* opaque, size_t size) {\n    UNUSED(opaque);\n\n    return ExAllocatePoolWithTag(PagedPool, size, ZSTD_ALLOC_TAG);\n}\n\nstatic void zstd_free(void* opaque, void* address) {\n    UNUSED(opaque);\n\n    ExFreePool(address);\n}\n\nNTSTATUS zstd_decompress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen) {\n    NTSTATUS Status;\n    ZSTD_DStream* stream;\n    size_t init_res, read;\n    ZSTD_inBuffer input;\n    ZSTD_outBuffer output;\n\n    stream = ZSTD_createDStream_advanced(zstd_mem);\n\n    if (!stream) {\n        ERR(\"ZSTD_createDStream failed.\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    init_res = ZSTD_initDStream(stream);\n\n    if (ZSTD_isError(init_res)) {\n        ERR(\"ZSTD_initDStream failed: %s\\n\", ZSTD_getErrorName(init_res));\n        Status = STATUS_INTERNAL_ERROR;\n        goto end;\n    }\n\n    input.src = inbuf;\n    input.size = inlen;\n    input.pos = 0;\n\n    output.dst = outbuf;\n    output.size = outlen;\n    output.pos = 0;\n\n    do {\n        read = ZSTD_decompressStream(stream, &output, &input);\n\n        if (ZSTD_isError(read)) {\n            ERR(\"ZSTD_decompressStream failed: %s\\n\", ZSTD_getErrorName(read));\n            Status = STATUS_INTERNAL_ERROR;\n            goto end;\n        }\n\n        if (output.pos == output.size)\n            break;\n    } while (read != 0);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ZSTD_freeDStream(stream);\n\n    return Status;\n}\n\nNTSTATUS lzo_compress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, unsigned int* space_left) {\n    NTSTATUS Status;\n    unsigned int num_pages;\n    unsigned int comp_data_len;\n    uint8_t* comp_data;\n    lzo_stream stream;\n    uint32_t* out_size;\n\n    num_pages = (unsigned int)sector_align(inlen, LZO_PAGE_SIZE) / LZO_PAGE_SIZE;\n\n    // Four-byte overall header\n    // Another four-byte header page\n    // Each page has a maximum size of lzo_max_outlen(LZO_PAGE_SIZE)\n    // Plus another four bytes for possible padding\n    comp_data_len = sizeof(uint32_t) + ((lzo_max_outlen(LZO_PAGE_SIZE) + (2 * sizeof(uint32_t))) * num_pages);\n\n    // FIXME - can we write this so comp_data isn't necessary?\n\n    comp_data = ExAllocatePoolWithTag(PagedPool, comp_data_len, ALLOC_TAG);\n    if (!comp_data) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    stream.wrkmem = ExAllocatePoolWithTag(PagedPool, LZO1X_MEM_COMPRESS, ALLOC_TAG);\n    if (!stream.wrkmem) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(comp_data);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    out_size = (uint32_t*)comp_data;\n    *out_size = sizeof(uint32_t);\n\n    stream.in = inbuf;\n    stream.out = comp_data + (2 * sizeof(uint32_t));\n\n    for (unsigned int i = 0; i < num_pages; i++) {\n        uint32_t* pagelen = (uint32_t*)(stream.out - sizeof(uint32_t));\n\n        stream.inlen = (uint32_t)min(LZO_PAGE_SIZE, outlen - (i * LZO_PAGE_SIZE));\n\n        Status = lzo1x_1_compress(&stream);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"lzo1x_1_compress returned %08lx\\n\", Status);\n            ExFreePool(comp_data);\n            return Status;\n        }\n\n        *pagelen = stream.outlen;\n        *out_size += stream.outlen + sizeof(uint32_t);\n\n        stream.in += LZO_PAGE_SIZE;\n        stream.out += stream.outlen + sizeof(uint32_t);\n\n        // new page needs to start at a 32-bit boundary\n        if (LZO_PAGE_SIZE - (*out_size % LZO_PAGE_SIZE) < sizeof(uint32_t)) {\n            RtlZeroMemory(stream.out, LZO_PAGE_SIZE - (*out_size % LZO_PAGE_SIZE));\n            stream.out += LZO_PAGE_SIZE - (*out_size % LZO_PAGE_SIZE);\n            *out_size += LZO_PAGE_SIZE - (*out_size % LZO_PAGE_SIZE);\n        }\n    }\n\n    ExFreePool(stream.wrkmem);\n\n    if (*out_size >= outlen)\n        *space_left = 0;\n    else {\n        *space_left = outlen - *out_size;\n\n        RtlCopyMemory(outbuf, comp_data, *out_size);\n    }\n\n    ExFreePool(comp_data);\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS zstd_compress(uint8_t* inbuf, uint32_t inlen, uint8_t* outbuf, uint32_t outlen, uint32_t level, unsigned int* space_left) {\n    ZSTD_CStream* stream;\n    size_t init_res, written;\n    ZSTD_inBuffer input;\n    ZSTD_outBuffer output;\n    ZSTD_parameters params;\n\n    stream = ZSTD_createCStream_advanced(zstd_mem);\n\n    if (!stream) {\n        ERR(\"ZSTD_createCStream failed.\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    params = ZSTD_getParams(level, inlen, 0);\n\n    if (params.cParams.windowLog > ZSTD_BTRFS_MAX_WINDOWLOG)\n        params.cParams.windowLog = ZSTD_BTRFS_MAX_WINDOWLOG;\n\n    init_res = ZSTD_initCStream_advanced(stream, NULL, 0, params, inlen);\n\n    if (ZSTD_isError(init_res)) {\n        ERR(\"ZSTD_initCStream_advanced failed: %s\\n\", ZSTD_getErrorName(init_res));\n        ZSTD_freeCStream(stream);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    input.src = inbuf;\n    input.size = inlen;\n    input.pos = 0;\n\n    output.dst = outbuf;\n    output.size = outlen;\n    output.pos = 0;\n\n    while (input.pos < input.size && output.pos < output.size) {\n        written = ZSTD_compressStream(stream, &output, &input);\n\n        if (ZSTD_isError(written)) {\n            ERR(\"ZSTD_compressStream failed: %s\\n\", ZSTD_getErrorName(written));\n            ZSTD_freeCStream(stream);\n            return STATUS_INTERNAL_ERROR;\n        }\n    }\n\n    written = ZSTD_endStream(stream, &output);\n    if (ZSTD_isError(written)) {\n        ERR(\"ZSTD_endStream failed: %s\\n\", ZSTD_getErrorName(written));\n        ZSTD_freeCStream(stream);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    ZSTD_freeCStream(stream);\n\n    if (input.pos < input.size) // output would be larger than input\n        *space_left = 0;\n    else\n        *space_left = output.size - output.pos;\n\n    return STATUS_SUCCESS;\n}\n\ntypedef struct {\n    uint8_t buf[COMPRESSED_EXTENT_SIZE];\n    uint8_t compression_type;\n    unsigned int inlen;\n    unsigned int outlen;\n    calc_job* cj;\n} comp_part;\n\nNTSTATUS write_compressed(fcb* fcb, uint64_t start_data, uint64_t end_data, void* data, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    uint64_t i;\n    unsigned int num_parts = (unsigned int)sector_align(end_data - start_data, COMPRESSED_EXTENT_SIZE) / COMPRESSED_EXTENT_SIZE;\n    uint8_t type;\n    comp_part* parts;\n    unsigned int buflen = 0;\n    uint8_t* buf;\n    chunk* c = NULL;\n    LIST_ENTRY* le;\n    uint64_t address, extaddr;\n    void* csum = NULL;\n\n    if (fcb->Vcb->options.compress_type != 0 && fcb->prop_compression == PropCompression_None)\n        type = fcb->Vcb->options.compress_type;\n    else {\n        if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_COMPRESS_ZSTD) && fcb->prop_compression == PropCompression_ZSTD)\n            type = BTRFS_COMPRESSION_ZSTD;\n        else if (fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_COMPRESS_ZSTD && fcb->prop_compression != PropCompression_Zlib && fcb->prop_compression != PropCompression_LZO)\n            type = BTRFS_COMPRESSION_ZSTD;\n        else if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_COMPRESS_LZO) && fcb->prop_compression == PropCompression_LZO)\n            type = BTRFS_COMPRESSION_LZO;\n        else if (fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_COMPRESS_LZO && fcb->prop_compression != PropCompression_Zlib)\n            type = BTRFS_COMPRESSION_LZO;\n        else\n            type = BTRFS_COMPRESSION_ZLIB;\n    }\n\n    Status = excise_extents(fcb->Vcb, fcb, start_data, end_data, Irp, rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"excise_extents returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    parts = ExAllocatePoolWithTag(PagedPool, sizeof(comp_part) * num_parts, ALLOC_TAG);\n    if (!parts) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    for (i = 0; i < num_parts; i++) {\n        if (i == num_parts - 1)\n            parts[i].inlen = ((unsigned int)(end_data - start_data) - ((num_parts - 1) * COMPRESSED_EXTENT_SIZE));\n        else\n            parts[i].inlen = COMPRESSED_EXTENT_SIZE;\n\n        Status = add_calc_job_comp(fcb->Vcb, type, (uint8_t*)data + (i * COMPRESSED_EXTENT_SIZE), parts[i].inlen,\n                                   parts[i].buf, parts[i].inlen, &parts[i].cj);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_calc_job_comp returned %08lx\\n\", Status);\n\n            for (unsigned int j = 0; j < i; j++) {\n                KeWaitForSingleObject(&parts[j].cj->event, Executive, KernelMode, false, NULL);\n                ExFreePool(parts[j].cj);\n            }\n\n            ExFreePool(parts);\n            return Status;\n        }\n    }\n\n    Status = STATUS_SUCCESS;\n\n    for (int i = num_parts - 1; i >= 0; i--) {\n        calc_thread_main(fcb->Vcb, parts[i].cj);\n\n        KeWaitForSingleObject(&parts[i].cj->event, Executive, KernelMode, false, NULL);\n\n        if (!NT_SUCCESS(parts[i].cj->Status))\n            Status = parts[i].cj->Status;\n    }\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"calc job returned %08lx\\n\", Status);\n\n        for (unsigned int i = 0; i < num_parts; i++) {\n            ExFreePool(parts[i].cj);\n        }\n\n        ExFreePool(parts);\n        return Status;\n    }\n\n    for (unsigned int i = 0; i < num_parts; i++) {\n        if (parts[i].cj->space_left >= fcb->Vcb->superblock.sector_size) {\n            parts[i].compression_type = type;\n            parts[i].outlen = parts[i].inlen - parts[i].cj->space_left;\n\n            if (type == BTRFS_COMPRESSION_LZO)\n                fcb->Vcb->superblock.incompat_flags |= BTRFS_INCOMPAT_FLAGS_COMPRESS_LZO;\n            else if (type == BTRFS_COMPRESSION_ZSTD)\n                fcb->Vcb->superblock.incompat_flags |= BTRFS_INCOMPAT_FLAGS_COMPRESS_ZSTD;\n\n            if ((parts[i].outlen & (fcb->Vcb->superblock.sector_size - 1)) != 0) {\n                unsigned int newlen = (unsigned int)sector_align(parts[i].outlen, fcb->Vcb->superblock.sector_size);\n\n                RtlZeroMemory(parts[i].buf + parts[i].outlen, newlen - parts[i].outlen);\n\n                parts[i].outlen = newlen;\n            }\n        } else {\n            parts[i].compression_type = BTRFS_COMPRESSION_NONE;\n            parts[i].outlen = (unsigned int)sector_align(parts[i].inlen, fcb->Vcb->superblock.sector_size);\n        }\n\n        buflen += parts[i].outlen;\n        ExFreePool(parts[i].cj);\n    }\n\n    // check if first 128 KB of file is incompressible\n\n    if (start_data == 0 && parts[0].compression_type == BTRFS_COMPRESSION_NONE && !fcb->Vcb->options.compress_force) {\n        TRACE(\"adding nocompress flag to subvol %I64x, inode %I64x\\n\", fcb->subvol->id, fcb->inode);\n\n        fcb->inode_item.flags |= BTRFS_INODE_NOCOMPRESS;\n        fcb->inode_item_changed = true;\n        mark_fcb_dirty(fcb);\n    }\n\n    // join together into continuous buffer\n\n    buf = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG);\n    if (!buf) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(parts);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    {\n        uint8_t* buf2 = buf;\n\n        for (i = 0; i < num_parts; i++) {\n            if (parts[i].compression_type == BTRFS_COMPRESSION_NONE)\n                RtlCopyMemory(buf2, (uint8_t*)data + (i * COMPRESSED_EXTENT_SIZE), parts[i].outlen);\n            else\n                RtlCopyMemory(buf2, parts[i].buf, parts[i].outlen);\n\n            buf2 += parts[i].outlen;\n        }\n    }\n\n    // find an address\n\n    ExAcquireResourceSharedLite(&fcb->Vcb->chunk_lock, true);\n\n    le = fcb->Vcb->chunks.Flink;\n    while (le != &fcb->Vcb->chunks) {\n        chunk* c2 = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (!c2->readonly && !c2->reloc) {\n            acquire_chunk_lock(c2, fcb->Vcb);\n\n            if (c2->chunk_item->type == fcb->Vcb->data_flags && (c2->chunk_item->size - c2->used) >= buflen) {\n                if (find_data_address_in_chunk(fcb->Vcb, c2, buflen, &address)) {\n                    c = c2;\n                    c->used += buflen;\n                    space_list_subtract(c, address, buflen, rollback);\n                    release_chunk_lock(c2, fcb->Vcb);\n                    break;\n                }\n            }\n\n            release_chunk_lock(c2, fcb->Vcb);\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&fcb->Vcb->chunk_lock);\n\n    if (!c) {\n        chunk* c2;\n\n        ExAcquireResourceExclusiveLite(&fcb->Vcb->chunk_lock, true);\n\n        Status = alloc_chunk(fcb->Vcb, fcb->Vcb->data_flags, &c2, false);\n\n        ExReleaseResourceLite(&fcb->Vcb->chunk_lock);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"alloc_chunk returned %08lx\\n\", Status);\n            ExFreePool(buf);\n            ExFreePool(parts);\n            return Status;\n        }\n\n        acquire_chunk_lock(c2, fcb->Vcb);\n\n        if (find_data_address_in_chunk(fcb->Vcb, c2, buflen, &address)) {\n            c = c2;\n            c->used += buflen;\n            space_list_subtract(c, address, buflen, rollback);\n        }\n\n        release_chunk_lock(c2, fcb->Vcb);\n    }\n\n    if (!c) {\n        WARN(\"couldn't find any data chunks with %x bytes free\\n\", buflen);\n        ExFreePool(buf);\n        ExFreePool(parts);\n        return STATUS_DISK_FULL;\n    }\n\n    // write to disk\n\n    TRACE(\"writing %x bytes to %I64x\\n\", buflen, address);\n\n    Status = write_data_complete(fcb->Vcb, address, buf, buflen, Irp, NULL, false, 0,\n                                 fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? HighPagePriority : NormalPagePriority);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"write_data_complete returned %08lx\\n\", Status);\n        ExFreePool(buf);\n        ExFreePool(parts);\n        return Status;\n    }\n\n    // FIXME - do rest of the function while we're waiting for I/O to finish?\n\n    // calculate csums if necessary\n\n    if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) {\n        unsigned int sl = buflen >> fcb->Vcb->sector_shift;\n\n        csum = ExAllocatePoolWithTag(PagedPool, sl * fcb->Vcb->csum_size, ALLOC_TAG);\n        if (!csum) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(buf);\n            ExFreePool(parts);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        do_calc_job(fcb->Vcb, buf, sl, csum);\n    }\n\n    ExFreePool(buf);\n\n    // add extents to fcb\n\n    extaddr = address;\n\n    for (i = 0; i < num_parts; i++) {\n        EXTENT_DATA* ed;\n        EXTENT_DATA2* ed2;\n        void* csum2;\n\n        ed = ExAllocatePoolWithTag(PagedPool, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2), ALLOC_TAG);\n        if (!ed) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(parts);\n\n            if (csum)\n                ExFreePool(csum);\n\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        ed->generation = fcb->Vcb->superblock.generation;\n        ed->decoded_size = parts[i].inlen;\n        ed->compression = parts[i].compression_type;\n        ed->encryption = BTRFS_ENCRYPTION_NONE;\n        ed->encoding = BTRFS_ENCODING_NONE;\n        ed->type = EXTENT_TYPE_REGULAR;\n\n        ed2 = (EXTENT_DATA2*)ed->data;\n        ed2->address = extaddr;\n        ed2->size = parts[i].outlen;\n        ed2->offset = 0;\n        ed2->num_bytes = parts[i].inlen;\n\n        if (csum) {\n            csum2 = ExAllocatePoolWithTag(PagedPool, (parts[i].outlen * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift, ALLOC_TAG);\n            if (!csum2) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(ed);\n                ExFreePool(parts);\n                ExFreePool(csum);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(csum2, (uint8_t*)csum + (((extaddr - address) * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift),\n                          (parts[i].outlen * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift);\n        } else\n            csum2 = NULL;\n\n        Status = add_extent_to_fcb(fcb, start_data + (i * COMPRESSED_EXTENT_SIZE), ed, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2),\n                                   true, csum2, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_extent_to_fcb returned %08lx\\n\", Status);\n            ExFreePool(ed);\n            ExFreePool(parts);\n\n            if (csum)\n                ExFreePool(csum);\n\n            return Status;\n        }\n\n        ExFreePool(ed);\n\n        fcb->inode_item.st_blocks += parts[i].inlen;\n\n        extaddr += parts[i].outlen;\n    }\n\n    if (csum)\n        ExFreePool(csum);\n\n    // update extent refcounts\n\n    ExAcquireResourceExclusiveLite(&c->changed_extents_lock, true);\n\n    extaddr = address;\n\n    for (i = 0; i < num_parts; i++) {\n        add_changed_extent_ref(c, extaddr, parts[i].outlen, fcb->subvol->id, fcb->inode,\n                               start_data + (i * COMPRESSED_EXTENT_SIZE), 1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM);\n\n        extaddr += parts[i].outlen;\n    }\n\n    ExReleaseResourceLite(&c->changed_extents_lock);\n\n    fcb->extents_changed = true;\n    fcb->inode_item_changed = true;\n    mark_fcb_dirty(fcb);\n\n    ExFreePool(parts);\n\n    return STATUS_SUCCESS;\n}\n"
  },
  {
    "path": "src/crc32c-aarch64.asm",
    "content": "    AREA .text, CODE, READONLY\n\n    GLOBAL calc_crc32c_hw\n\n; uint32_t calc_crc32c_hw(uint32_t seed, const uint8_t* buf, uint32_t len);\ncalc_crc32c_hw\n    ; w0 = crc / seed\n    ; x1 = buf\n    ; w2 = len\n    ; x3 = scratch\n\n    cmp w2, #8\n    b.lt crchw_stragglers\n\n    ldr x3, [x1]\n    crc32cx w0, w0, x3\n\n    add x1, x1, #8\n    sub w2, w2, #8\n    b calc_crc32c_hw\n\ncrchw_stragglers\n    cmp w2, #4\n    b.lt crchw_stragglers2\n\n    ldr w3, [x1]\n    crc32cw w0, w0, w3\n\n    add x1, x1, #4\n    sub w2, w2, #4\n\ncrchw_stragglers2\n    cmp w2, #2\n    b.lt crchw_stragglers3\n\n    ldrh w3, [x1]\n    crc32ch w0, w0, w3\n\n    add x1, x1, #2\n    sub w2, w2, #2\n\ncrchw_stragglers3\n    cmp w2, #0\n    b.eq crchw_end\n\n    ldrb w3, [x1]\n    crc32cb w0, w0, w3\n\ncrchw_end\n    ret\n\n    END\n"
  },
  {
    "path": "src/crc32c-gas.S",
    "content": "/* Copyright (c) Mark Harmstone 2020\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n.intel_syntax noprefix\n\n#ifdef __x86_64__\n\n.extern crctable\n.global calc_crc32c_sw\n\n/* uint32_t __stdcall calc_crc32c_sw(uint32_t seed, uint8_t* msg, uint32_t msglen); */\n\ncalc_crc32c_sw:\n\n/* rax = crc / seed\n * rdx = buf\n * r8 = len\n * rcx = tmp\n * r10 = tmp2 */\n\nmov rax, rcx\n\ncrcloop:\ntest r8, r8\njz crcend\n\nmov rcx, rax\nshr rcx, 8\nmov r10b, byte ptr [rdx]\nxor al, r10b\nand rax, 255\nshl rax, 2\nmovabs r10, offset crctable\nmov eax, dword ptr [r10 + rax]\nxor rax, rcx\n\ninc rdx\ndec r8\n\njmp crcloop\n\ncrcend:\nret\n\n/****************************************************/\n\n/* uint32_t __stdcall calc_crc32c_hw(uint32_t seed, uint8_t* msg, uint32_t msglen); */\n\n.global calc_crc32c_hw\n\ncalc_crc32c_hw:\n\n/* rax = crc / seed\n * rdx = buf\n * r8 = len */\n\nmov rax, rcx\n\ncrchw_loop:\ncmp r8, 8\njl crchw_stragglers\n\ncrc32 rax, qword ptr [rdx]\n\nadd rdx, 8\nsub r8, 8\njmp crchw_loop\n\ncrchw_stragglers:\ncmp r8, 4\njl crchw_stragglers2\n\ncrc32 eax, dword ptr [rdx]\n\nadd rdx, 4\nsub r8, 4\n\ncrchw_stragglers2:\ncmp r8, 2\njl crchw_stragglers3\n\ncrc32 eax, word ptr [rdx]\n\nadd rdx, 2\nsub r8, 2\n\ncrchw_stragglers3:\ntest r8, r8\njz crchw_end\n\ncrc32 eax, byte ptr [rdx]\ninc rdx\ndec r8\njmp crchw_stragglers3\n\ncrchw_end:\nret\n\n#elif defined(_X86_)\n\n.extern _crctable\n.global _calc_crc32c_sw@12\n.global _calc_crc32c_hw@12\n\n/* uint32_t __stdcall calc_crc32c_sw(uint32_t seed, uint8_t* msg, uint32_t msglen); */\n\n_calc_crc32c_sw@12:\n\npush ebp\nmov ebp, esp\n\npush esi\npush ebx\n\nmov eax, [ebp+8]\nmov edx, [ebp+12]\nmov ebx, [ebp+16]\n\n/* eax = crc / seed\n * ebx = len\n * esi = tmp\n * edx = buf\n * ecx = tmp2 */\n\ncrcloop:\ntest ebx, ebx\njz crcend\n\nmov esi, eax\nshr esi, 8\nmov cl, byte ptr [edx]\nxor al, cl\nand eax, 255\nshl eax, 2\nmov eax, [_crctable + eax]\nxor eax, esi\n\ninc edx\ndec ebx\n\njmp crcloop\n\ncrcend:\npop ebx\npop esi\n\npop ebp\n\nret 12\n\n/****************************************************/\n\n/* uint32_t __stdcall calc_crc32c_hw(uint32_t seed, uint8_t* msg, uint32_t msglen); */\n\n_calc_crc32c_hw@12:\n\npush ebp\nmov ebp, esp\n\nmov eax, [ebp+8]\nmov edx, [ebp+12]\nmov ecx, [ebp+16]\n\n/* eax = crc / seed\n * ecx = len\n * edx = buf */\n\ncrchw_loop:\ncmp ecx, 4\njl crchw_stragglers\n\ncrc32 eax, dword ptr [edx]\n\nadd edx, 4\nsub ecx, 4\njmp crchw_loop\n\ncrchw_stragglers:\ncmp ecx, 2\njl crchw_stragglers2\n\ncrc32 eax, word ptr [edx]\n\nadd edx, 2\nsub ecx, 2\n\ncrchw_stragglers2:\ntest ecx, ecx\njz crchw_end\n\ncrc32 eax, byte ptr [edx]\ninc edx\ndec ecx\njmp crchw_stragglers2\n\ncrchw_end:\npop ebp\n\nret 12\n\n#endif\n"
  },
  {
    "path": "src/crc32c-masm.asm",
    "content": "; Copyright (c) Mark Harmstone 2020\n;\n; This file is part of WinBtrfs.\n;\n; WinBtrfs is free software: you can redistribute it and/or modify\n; it under the terms of the GNU Lesser General Public Licence as published by\n; the Free Software Foundation, either version 3 of the Licence, or\n; (at your option) any later version.\n;\n; WinBtrfs is distributed in the hope that it will be useful,\n; but WITHOUT ANY WARRANTY; without even the implied warranty of\n; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n; GNU Lesser General Public Licence for more details.\n;\n; You should have received a copy of the GNU Lesser General Public Licence\n; along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>.\n\nIFDEF RAX\nELSE\n.686P\nENDIF\n\n_TEXT  SEGMENT\n\nIFDEF RAX\n\nEXTERN crctable:qword\n\nPUBLIC calc_crc32c_sw\n\n; uint32_t __stdcall calc_crc32c_sw(uint32_t seed, uint8_t* msg, uint32_t msglen);\n\ncalc_crc32c_sw:\n\n; rax = crc / seed\n; rdx = buf\n; r8 = len\n; rcx = tmp\n; r10 = tmp2\n\nmov rax, rcx\n\ncrcloop:\ntest r8, r8\njz crcend\n\nmov rcx, rax\nshr rcx, 8\nmov r10b, byte ptr [rdx]\nxor al, r10b\nand rax, 255\nshl rax, 2\nmov r10, offset crctable\nmov eax, dword ptr [r10 + rax]\nxor rax, rcx\n\ninc rdx\ndec r8\n\njmp crcloop\n\ncrcend:\nret\n\n; ****************************************************\n\n; uint32_t __stdcall calc_crc32c_hw(uint32_t seed, uint8_t* msg, uint32_t msglen);\n\nPUBLIC calc_crc32c_hw\n\ncalc_crc32c_hw:\n\n; rax = crc / seed\n; rdx = buf\n; r8 = len\n\nmov rax, rcx\n\ncrchw_loop:\ncmp r8, 8\njl crchw_stragglers\n\ncrc32 rax, qword ptr [rdx]\n\nadd rdx, 8\nsub r8, 8\njmp crchw_loop\n\ncrchw_stragglers:\ncmp r8, 4\njl crchw_stragglers2\n\ncrc32 eax, dword ptr [rdx]\n\nadd rdx, 4\nsub r8, 4\n\ncrchw_stragglers2:\ncmp r8, 2\njl crchw_stragglers3\n\ncrc32 eax, word ptr [rdx]\n\nadd rdx, 2\nsub r8, 2\n\ncrchw_stragglers3:\ntest r8, r8\njz crchw_end\n\ncrc32 eax, byte ptr [rdx]\ninc rdx\ndec r8\njmp crchw_stragglers3\n\ncrchw_end:\nret\n\nELSE\n\nEXTERN crctable:ABS\n\n; uint32_t __stdcall calc_crc32c_sw(uint32_t seed, uint8_t* msg, uint32_t msglen);\n\nPUBLIC calc_crc32c_sw@12\n\ncalc_crc32c_sw@12:\n\npush ebp\nmov ebp, esp\n\npush esi\npush ebx\n\nmov eax, [ebp+8]\nmov edx, [ebp+12]\nmov ebx, [ebp+16]\n\n; eax = crc / seed\n; ebx = len\n; esi = tmp\n; edx = buf\n; ecx = tmp2\n\ncrcloop:\ntest ebx, ebx\njz crcend\n\nmov esi, eax\nshr esi, 8\nmov cl, byte ptr [edx]\nxor al, cl\nand eax, 255\nshl eax, 2\nmov eax, [crctable + eax]\nxor eax, esi\n\ninc edx\ndec ebx\n\njmp crcloop\n\ncrcend:\npop ebx\npop esi\n\npop ebp\n\nret 12\n\n; ****************************************************\n\n; uint32_t __stdcall calc_crc32c_hw(uint32_t seed, uint8_t* msg, uint32_t msglen);\n\nPUBLIC calc_crc32c_hw@12\n\ncalc_crc32c_hw@12:\n\npush ebp\nmov ebp, esp\n\nmov eax, [ebp+8]\nmov edx, [ebp+12]\nmov ecx, [ebp+16]\n\n; eax = crc / seed\n; ecx = len\n; edx = buf\n\ncrchw_loop:\ncmp ecx, 4\njl crchw_stragglers\n\ncrc32 eax, dword ptr [edx]\n\nadd edx, 4\nsub ecx, 4\njmp crchw_loop\n\ncrchw_stragglers:\ncmp ecx, 2\njl crchw_stragglers2\n\ncrc32 eax, word ptr [edx]\n\nadd edx, 2\nsub ecx, 2\n\ncrchw_stragglers2:\ntest ecx, ecx\njz crchw_end\n\ncrc32 eax, byte ptr [edx]\ninc edx\ndec ecx\njmp crchw_stragglers2\n\ncrchw_end:\npop ebp\n\nret 12\n\nENDIF\n\n_TEXT  ENDS\n\nend\n"
  },
  {
    "path": "src/crc32c.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"crc32c.h\"\n#include <stdint.h>\n#include <stdbool.h>\n#include <sal.h>\n\ncrc_func calc_crc32c = calc_crc32c_sw;\n\nconst uint32_t crctable[] = {\n    0x00000000, 0xf26b8303, 0xe13b70f7, 0x1350f3f4, 0xc79a971f, 0x35f1141c, 0x26a1e7e8, 0xd4ca64eb,\n    0x8ad958cf, 0x78b2dbcc, 0x6be22838, 0x9989ab3b, 0x4d43cfd0, 0xbf284cd3, 0xac78bf27, 0x5e133c24,\n    0x105ec76f, 0xe235446c, 0xf165b798, 0x030e349b, 0xd7c45070, 0x25afd373, 0x36ff2087, 0xc494a384,\n    0x9a879fa0, 0x68ec1ca3, 0x7bbcef57, 0x89d76c54, 0x5d1d08bf, 0xaf768bbc, 0xbc267848, 0x4e4dfb4b,\n    0x20bd8ede, 0xd2d60ddd, 0xc186fe29, 0x33ed7d2a, 0xe72719c1, 0x154c9ac2, 0x061c6936, 0xf477ea35,\n    0xaa64d611, 0x580f5512, 0x4b5fa6e6, 0xb93425e5, 0x6dfe410e, 0x9f95c20d, 0x8cc531f9, 0x7eaeb2fa,\n    0x30e349b1, 0xc288cab2, 0xd1d83946, 0x23b3ba45, 0xf779deae, 0x05125dad, 0x1642ae59, 0xe4292d5a,\n    0xba3a117e, 0x4851927d, 0x5b016189, 0xa96ae28a, 0x7da08661, 0x8fcb0562, 0x9c9bf696, 0x6ef07595,\n    0x417b1dbc, 0xb3109ebf, 0xa0406d4b, 0x522bee48, 0x86e18aa3, 0x748a09a0, 0x67dafa54, 0x95b17957,\n    0xcba24573, 0x39c9c670, 0x2a993584, 0xd8f2b687, 0x0c38d26c, 0xfe53516f, 0xed03a29b, 0x1f682198,\n    0x5125dad3, 0xa34e59d0, 0xb01eaa24, 0x42752927, 0x96bf4dcc, 0x64d4cecf, 0x77843d3b, 0x85efbe38,\n    0xdbfc821c, 0x2997011f, 0x3ac7f2eb, 0xc8ac71e8, 0x1c661503, 0xee0d9600, 0xfd5d65f4, 0x0f36e6f7,\n    0x61c69362, 0x93ad1061, 0x80fde395, 0x72966096, 0xa65c047d, 0x5437877e, 0x4767748a, 0xb50cf789,\n    0xeb1fcbad, 0x197448ae, 0x0a24bb5a, 0xf84f3859, 0x2c855cb2, 0xdeeedfb1, 0xcdbe2c45, 0x3fd5af46,\n    0x7198540d, 0x83f3d70e, 0x90a324fa, 0x62c8a7f9, 0xb602c312, 0x44694011, 0x5739b3e5, 0xa55230e6,\n    0xfb410cc2, 0x092a8fc1, 0x1a7a7c35, 0xe811ff36, 0x3cdb9bdd, 0xceb018de, 0xdde0eb2a, 0x2f8b6829,\n    0x82f63b78, 0x709db87b, 0x63cd4b8f, 0x91a6c88c, 0x456cac67, 0xb7072f64, 0xa457dc90, 0x563c5f93,\n    0x082f63b7, 0xfa44e0b4, 0xe9141340, 0x1b7f9043, 0xcfb5f4a8, 0x3dde77ab, 0x2e8e845f, 0xdce5075c,\n    0x92a8fc17, 0x60c37f14, 0x73938ce0, 0x81f80fe3, 0x55326b08, 0xa759e80b, 0xb4091bff, 0x466298fc,\n    0x1871a4d8, 0xea1a27db, 0xf94ad42f, 0x0b21572c, 0xdfeb33c7, 0x2d80b0c4, 0x3ed04330, 0xccbbc033,\n    0xa24bb5a6, 0x502036a5, 0x4370c551, 0xb11b4652, 0x65d122b9, 0x97baa1ba, 0x84ea524e, 0x7681d14d,\n    0x2892ed69, 0xdaf96e6a, 0xc9a99d9e, 0x3bc21e9d, 0xef087a76, 0x1d63f975, 0x0e330a81, 0xfc588982,\n    0xb21572c9, 0x407ef1ca, 0x532e023e, 0xa145813d, 0x758fe5d6, 0x87e466d5, 0x94b49521, 0x66df1622,\n    0x38cc2a06, 0xcaa7a905, 0xd9f75af1, 0x2b9cd9f2, 0xff56bd19, 0x0d3d3e1a, 0x1e6dcdee, 0xec064eed,\n    0xc38d26c4, 0x31e6a5c7, 0x22b65633, 0xd0ddd530, 0x0417b1db, 0xf67c32d8, 0xe52cc12c, 0x1747422f,\n    0x49547e0b, 0xbb3ffd08, 0xa86f0efc, 0x5a048dff, 0x8ecee914, 0x7ca56a17, 0x6ff599e3, 0x9d9e1ae0,\n    0xd3d3e1ab, 0x21b862a8, 0x32e8915c, 0xc083125f, 0x144976b4, 0xe622f5b7, 0xf5720643, 0x07198540,\n    0x590ab964, 0xab613a67, 0xb831c993, 0x4a5a4a90, 0x9e902e7b, 0x6cfbad78, 0x7fab5e8c, 0x8dc0dd8f,\n    0xe330a81a, 0x115b2b19, 0x020bd8ed, 0xf0605bee, 0x24aa3f05, 0xd6c1bc06, 0xc5914ff2, 0x37faccf1,\n    0x69e9f0d5, 0x9b8273d6, 0x88d28022, 0x7ab90321, 0xae7367ca, 0x5c18e4c9, 0x4f48173d, 0xbd23943e,\n    0xf36e6f75, 0x0105ec76, 0x12551f82, 0xe03e9c81, 0x34f4f86a, 0xc69f7b69, 0xd5cf889d, 0x27a40b9e,\n    0x79b737ba, 0x8bdcb4b9, 0x988c474d, 0x6ae7c44e, 0xbe2da0a5, 0x4c4623a6, 0x5f16d052, 0xad7d5351,\n};\n\n// x86 and amd64 versions live in asm files\n#if !defined(_X86_) && !defined(_AMD64_)\nuint32_t __stdcall calc_crc32c_sw(_In_ uint32_t seed, _In_reads_bytes_(msglen) uint8_t* msg, _In_ uint32_t msglen) {\n    uint32_t rem = seed;\n\n    for (uint32_t i = 0; i < msglen; i++) {\n        rem = crctable[(rem ^ msg[i]) & 0xff] ^ (rem >> 8);\n    }\n\n    return rem;\n}\n#endif\n"
  },
  {
    "path": "src/crc32c.h",
    "content": "#pragma once\n\n#include <stdint.h>\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\n\n#if defined(_X86_) || defined(_AMD64_) || defined(_ARM64_)\nuint32_t __stdcall calc_crc32c_hw(uint32_t seed, uint8_t* msg, uint32_t msglen);\n#endif\n\nuint32_t __stdcall calc_crc32c_sw(uint32_t seed, uint8_t* msg, uint32_t msglen);\n\ntypedef uint32_t (__stdcall *crc_func)(uint32_t seed, uint8_t* msg, uint32_t msglen);\n\nextern crc_func calc_crc32c;\n\n#ifdef __cplusplus\n}\n#endif\n"
  },
  {
    "path": "src/create.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include <sys/stat.h>\n#include \"btrfs_drv.h\"\n#include \"crc32c.h\"\n#include <ntddstor.h>\n\nextern PDEVICE_OBJECT master_devobj;\nextern tFsRtlGetEcpListFromIrp fFsRtlGetEcpListFromIrp;\nextern tFsRtlGetNextExtraCreateParameter fFsRtlGetNextExtraCreateParameter;\nextern tFsRtlValidateReparsePointBuffer fFsRtlValidateReparsePointBuffer;\n\nstatic const WCHAR datastring[] = L\"::$DATA\";\n\nstatic const char root_dir[] = \"$Root\";\nstatic const WCHAR root_dir_utf16[] = L\"$Root\";\n\n// Windows 10\n#define ATOMIC_CREATE_ECP_IN_FLAG_REPARSE_POINT_SPECIFIED   0x0002\n#define ATOMIC_CREATE_ECP_IN_FLAG_OP_FLAGS_SPECIFIED        0x0080\n#define ATOMIC_CREATE_ECP_IN_FLAG_BEST_EFFORT               0x0100\n\n#define ATOMIC_CREATE_ECP_OUT_FLAG_REPARSE_POINT_SET        0x0002\n#define ATOMIC_CREATE_ECP_OUT_FLAG_OP_FLAGS_HONORED         0x0080\n\n#define ATOMIC_CREATE_ECP_IN_OP_FLAG_CASE_SENSITIVE_FLAGS_SPECIFIED       1\n#define ATOMIC_CREATE_ECP_OUT_OP_FLAG_CASE_SENSITIVE_FLAGS_SET            1\n\n#ifndef SL_IGNORE_READONLY_ATTRIBUTE\n#define SL_IGNORE_READONLY_ATTRIBUTE 0x40 // introduced in Windows 10, not in mingw\n#endif\n\ntypedef struct _FILE_TIMESTAMPS {\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n} FILE_TIMESTAMPS, *PFILE_TIMESTAMPS;\n\ntypedef struct _ATOMIC_CREATE_ECP_CONTEXT {\n    USHORT Size;\n    USHORT InFlags;\n    USHORT OutFlags;\n    USHORT ReparseBufferLength;\n    PREPARSE_DATA_BUFFER ReparseBuffer;\n    LONGLONG FileSize;\n    LONGLONG ValidDataLength;\n    PFILE_TIMESTAMPS FileTimestamps;\n    ULONG FileAttributes;\n    ULONG UsnSourceInfo;\n    USN Usn;\n    ULONG SuppressFileAttributeInheritanceMask;\n    ULONG InOpFlags;\n    ULONG OutOpFlags;\n    ULONG InGenFlags;\n    ULONG OutGenFlags;\n    ULONG CaseSensitiveFlagsMask;\n    ULONG InCaseSensitiveFlags;\n    ULONG OutCaseSensitiveFlags;\n} ATOMIC_CREATE_ECP_CONTEXT, *PATOMIC_CREATE_ECP_CONTEXT;\n\nstatic const GUID GUID_ECP_ATOMIC_CREATE = { 0x4720bd83, 0x52ac, 0x4104, { 0xa1, 0x30, 0xd1, 0xec, 0x6a, 0x8c, 0xc8, 0xe5 } };\nstatic const GUID GUID_ECP_QUERY_ON_CREATE = { 0x1aca62e9, 0xabb4, 0x4ff2, { 0xbb, 0x5c, 0x1c, 0x79, 0x02, 0x5e, 0x41, 0x7f } };\nstatic const GUID GUID_ECP_CREATE_REDIRECTION = { 0x188d6bd6, 0xa126, 0x4fa8, { 0xbd, 0xf2, 0x1c, 0xcd, 0xf8, 0x96, 0xf3, 0xe0 } };\n\ntypedef struct {\n    device_extension* Vcb;\n    ACCESS_MASK granted_access;\n    file_ref* fileref;\n    NTSTATUS Status;\n    KEVENT event;\n} oplock_context;\n\nfcb* create_fcb(device_extension* Vcb, POOL_TYPE pool_type) {\n    fcb* fcb;\n\n    if (pool_type == NonPagedPool) {\n        fcb = ExAllocatePoolWithTag(pool_type, sizeof(struct _fcb), ALLOC_TAG);\n        if (!fcb) {\n            ERR(\"out of memory\\n\");\n            return NULL;\n        }\n    } else {\n        fcb = ExAllocateFromPagedLookasideList(&Vcb->fcb_lookaside);\n        if (!fcb) {\n            ERR(\"out of memory\\n\");\n            return NULL;\n        }\n    }\n\n#ifdef DEBUG_FCB_REFCOUNTS\n    WARN(\"allocating fcb %p\\n\", fcb);\n#endif\n    RtlZeroMemory(fcb, sizeof(struct _fcb));\n    fcb->pool_type = pool_type;\n\n    fcb->Header.NodeTypeCode = BTRFS_NODE_TYPE_FCB;\n    fcb->Header.NodeByteSize = sizeof(struct _fcb);\n\n    fcb->nonpaged = ExAllocateFromNPagedLookasideList(&Vcb->fcb_np_lookaside);\n    if (!fcb->nonpaged) {\n        ERR(\"out of memory\\n\");\n\n        if (pool_type == NonPagedPool)\n            ExFreePool(fcb);\n        else\n            ExFreeToPagedLookasideList(&Vcb->fcb_lookaside, fcb);\n\n        return NULL;\n    }\n    RtlZeroMemory(fcb->nonpaged, sizeof(struct _fcb_nonpaged));\n\n    ExInitializeResourceLite(&fcb->nonpaged->paging_resource);\n    fcb->Header.PagingIoResource = &fcb->nonpaged->paging_resource;\n\n    ExInitializeFastMutex(&fcb->nonpaged->HeaderMutex);\n    FsRtlSetupAdvancedHeader(&fcb->Header, &fcb->nonpaged->HeaderMutex);\n\n    fcb->refcount = 1;\n#ifdef DEBUG_FCB_REFCOUNTS\n    WARN(\"fcb %p: refcount now %i\\n\", fcb, fcb->refcount);\n#endif\n\n    ExInitializeResourceLite(&fcb->nonpaged->resource);\n    fcb->Header.Resource = &fcb->nonpaged->resource;\n\n    ExInitializeResourceLite(&fcb->nonpaged->dir_children_lock);\n\n    FsRtlInitializeFileLock(&fcb->lock, NULL, NULL);\n    FsRtlInitializeOplock(fcb_oplock(fcb));\n\n    InitializeListHead(&fcb->extents);\n    InitializeListHead(&fcb->hardlinks);\n    InitializeListHead(&fcb->xattrs);\n\n    InitializeListHead(&fcb->dir_children_index);\n    InitializeListHead(&fcb->dir_children_hash);\n    InitializeListHead(&fcb->dir_children_hash_uc);\n\n    return fcb;\n}\n\nfile_ref* create_fileref(device_extension* Vcb) {\n    file_ref* fr;\n\n    fr = ExAllocateFromPagedLookasideList(&Vcb->fileref_lookaside);\n    if (!fr) {\n        ERR(\"out of memory\\n\");\n        return NULL;\n    }\n\n    RtlZeroMemory(fr, sizeof(file_ref));\n\n    fr->refcount = 1;\n\n#ifdef DEBUG_FCB_REFCOUNTS\n    WARN(\"fileref %p: refcount now 1\\n\", fr);\n#endif\n\n    InitializeListHead(&fr->children);\n\n    return fr;\n}\n\nNTSTATUS find_file_in_dir(PUNICODE_STRING filename, fcb* fcb, root** subvol, uint64_t* inode, dir_child** pdc, bool case_sensitive) {\n    NTSTATUS Status;\n    UNICODE_STRING fnus;\n    uint32_t hash;\n    LIST_ENTRY* le;\n    uint8_t c;\n    bool locked = false;\n\n    if (!case_sensitive) {\n        Status = RtlUpcaseUnicodeString(&fnus, filename, true);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n            return Status;\n        }\n    } else\n        fnus = *filename;\n\n    Status = check_file_name_valid(filename, false, false);\n    if (!NT_SUCCESS(Status))\n        return Status;\n\n    hash = calc_crc32c(0xffffffff, (uint8_t*)fnus.Buffer, fnus.Length);\n\n    c = hash >> 24;\n\n    if (!ExIsResourceAcquiredSharedLite(&fcb->nonpaged->dir_children_lock)) {\n        ExAcquireResourceSharedLite(&fcb->nonpaged->dir_children_lock, true);\n        locked = true;\n    }\n\n    if (case_sensitive) {\n        if (!fcb->hash_ptrs[c]) {\n            Status = STATUS_OBJECT_NAME_NOT_FOUND;\n            goto end;\n        }\n\n        le = fcb->hash_ptrs[c];\n        while (le != &fcb->dir_children_hash) {\n            dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_hash);\n\n            if (dc->hash == hash) {\n                if (dc->name.Length == fnus.Length && RtlCompareMemory(dc->name.Buffer, fnus.Buffer, fnus.Length) == fnus.Length) {\n                    if (dc->key.obj_type == TYPE_ROOT_ITEM) {\n                        LIST_ENTRY* le2;\n\n                        *subvol = NULL;\n\n                        le2 = fcb->Vcb->roots.Flink;\n                        while (le2 != &fcb->Vcb->roots) {\n                            root* r2 = CONTAINING_RECORD(le2, root, list_entry);\n\n                            if (r2->id == dc->key.obj_id) {\n                                *subvol = r2;\n                                break;\n                            }\n\n                            le2 = le2->Flink;\n                        }\n\n                        *inode = SUBVOL_ROOT_INODE;\n                    } else {\n                        *subvol = fcb->subvol;\n                        *inode = dc->key.obj_id;\n                    }\n\n                    *pdc = dc;\n\n                    Status = STATUS_SUCCESS;\n                    goto end;\n                }\n            } else if (dc->hash > hash) {\n                Status = STATUS_OBJECT_NAME_NOT_FOUND;\n                goto end;\n            }\n\n            le = le->Flink;\n        }\n    } else {\n        if (!fcb->hash_ptrs_uc[c]) {\n            Status = STATUS_OBJECT_NAME_NOT_FOUND;\n            goto end;\n        }\n\n        le = fcb->hash_ptrs_uc[c];\n        while (le != &fcb->dir_children_hash_uc) {\n            dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_hash_uc);\n\n            if (dc->hash_uc == hash) {\n                if (dc->name_uc.Length == fnus.Length && RtlCompareMemory(dc->name_uc.Buffer, fnus.Buffer, fnus.Length) == fnus.Length) {\n                    if (dc->key.obj_type == TYPE_ROOT_ITEM) {\n                        LIST_ENTRY* le2;\n\n                        *subvol = NULL;\n\n                        le2 = fcb->Vcb->roots.Flink;\n                        while (le2 != &fcb->Vcb->roots) {\n                            root* r2 = CONTAINING_RECORD(le2, root, list_entry);\n\n                            if (r2->id == dc->key.obj_id) {\n                                *subvol = r2;\n                                break;\n                            }\n\n                            le2 = le2->Flink;\n                        }\n\n                        *inode = SUBVOL_ROOT_INODE;\n                    } else {\n                        *subvol = fcb->subvol;\n                        *inode = dc->key.obj_id;\n                    }\n\n                    *pdc = dc;\n\n                    Status = STATUS_SUCCESS;\n                    goto end;\n                }\n            } else if (dc->hash_uc > hash) {\n                Status = STATUS_OBJECT_NAME_NOT_FOUND;\n                goto end;\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    Status = STATUS_OBJECT_NAME_NOT_FOUND;\n\nend:\n    if (locked)\n        ExReleaseResourceLite(&fcb->nonpaged->dir_children_lock);\n\n    if (!case_sensitive)\n        ExFreePool(fnus.Buffer);\n\n    return Status;\n}\n\nstatic NTSTATUS split_path(device_extension* Vcb, PUNICODE_STRING path, LIST_ENTRY* parts, bool* stream) {\n    ULONG len, i;\n    bool has_stream;\n    WCHAR* buf;\n    name_bit* nb;\n    NTSTATUS Status;\n\n    len = path->Length / sizeof(WCHAR);\n    if (len > 0 && (path->Buffer[len - 1] == '/' || path->Buffer[len - 1] == '\\\\'))\n        len--;\n\n    if (len == 0 || (path->Buffer[len - 1] == '/' || path->Buffer[len - 1] == '\\\\')) {\n        WARN(\"zero-length filename part\\n\");\n        return STATUS_OBJECT_NAME_INVALID;\n    }\n\n    has_stream = false;\n    for (i = 0; i < len; i++) {\n        if (path->Buffer[i] == '/' || path->Buffer[i] == '\\\\') {\n            has_stream = false;\n        } else if (path->Buffer[i] == ':') {\n            has_stream = true;\n        }\n    }\n\n    buf = path->Buffer;\n\n    for (i = 0; i < len; i++) {\n        if (path->Buffer[i] == '/' || path->Buffer[i] == '\\\\') {\n            if (buf[0] == '/' || buf[0] == '\\\\') {\n                WARN(\"zero-length filename part\\n\");\n                Status = STATUS_OBJECT_NAME_INVALID;\n                goto cleanup;\n            }\n\n            nb = ExAllocateFromPagedLookasideList(&Vcb->name_bit_lookaside);\n            if (!nb) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto cleanup;\n            }\n\n            nb->us.Buffer = buf;\n            nb->us.Length = nb->us.MaximumLength = (USHORT)(&path->Buffer[i] - buf) * sizeof(WCHAR);\n            InsertTailList(parts, &nb->list_entry);\n\n            buf = &path->Buffer[i+1];\n        }\n    }\n\n    nb = ExAllocateFromPagedLookasideList(&Vcb->name_bit_lookaside);\n    if (!nb) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto cleanup;\n    }\n\n    nb->us.Buffer = buf;\n    nb->us.Length = nb->us.MaximumLength = (USHORT)(&path->Buffer[i] - buf) * sizeof(WCHAR);\n    InsertTailList(parts, &nb->list_entry);\n\n    if (has_stream) {\n        static const WCHAR datasuf[] = {':','$','D','A','T','A',0};\n        UNICODE_STRING dsus;\n\n        dsus.Buffer = (WCHAR*)datasuf;\n        dsus.Length = dsus.MaximumLength = sizeof(datasuf) - sizeof(WCHAR);\n\n        for (i = 0; i < nb->us.Length / sizeof(WCHAR); i++) {\n            if (nb->us.Buffer[i] == ':') {\n                name_bit* nb2;\n\n                if (i + 1 == nb->us.Length / sizeof(WCHAR)) {\n                    WARN(\"zero-length stream name\\n\");\n                    Status = STATUS_OBJECT_NAME_INVALID;\n                    goto cleanup;\n                }\n\n                nb2 = ExAllocateFromPagedLookasideList(&Vcb->name_bit_lookaside);\n                if (!nb2) {\n                    ERR(\"out of memory\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto cleanup;\n                }\n\n                nb2->us.Buffer = &nb->us.Buffer[i+1];\n                nb2->us.Length = nb2->us.MaximumLength = (uint16_t)(nb->us.Length - (i * sizeof(WCHAR)) - sizeof(WCHAR));\n                InsertTailList(parts, &nb2->list_entry);\n\n                nb->us.Length = (uint16_t)i * sizeof(WCHAR);\n                nb->us.MaximumLength = nb->us.Length;\n\n                nb = nb2;\n\n                break;\n            }\n        }\n\n        // FIXME - should comparison be case-insensitive?\n        // remove :$DATA suffix\n        if (nb->us.Length >= dsus.Length && RtlCompareMemory(&nb->us.Buffer[(nb->us.Length - dsus.Length)/sizeof(WCHAR)], dsus.Buffer, dsus.Length) == dsus.Length)\n            nb->us.Length -= dsus.Length;\n\n        if (nb->us.Length == 0) {\n            RemoveTailList(parts);\n            ExFreeToPagedLookasideList(&Vcb->name_bit_lookaside, nb);\n\n            has_stream = false;\n        }\n    }\n\n    // if path is just stream name, remove first empty item\n    if (has_stream && path->Length >= sizeof(WCHAR) && path->Buffer[0] == ':') {\n        name_bit *nb1 = CONTAINING_RECORD(RemoveHeadList(parts), name_bit, list_entry);\n\n        ExFreeToPagedLookasideList(&Vcb->name_bit_lookaside, nb1);\n    }\n\n    *stream = has_stream;\n\n    return STATUS_SUCCESS;\n\ncleanup:\n    while (!IsListEmpty(parts)) {\n        nb = CONTAINING_RECORD(RemoveHeadList(parts), name_bit, list_entry);\n\n        ExFreeToPagedLookasideList(&Vcb->name_bit_lookaside, nb);\n    }\n\n    return Status;\n}\n\nNTSTATUS load_csum(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, void* csum, uint64_t start, uint64_t length, PIRP Irp) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp, next_tp;\n    uint64_t i, j;\n    bool b;\n    void* ptr = csum;\n\n    searchkey.obj_id = EXTENT_CSUM_ID;\n    searchkey.obj_type = TYPE_EXTENT_CSUM;\n    searchkey.offset = start;\n\n    Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    i = 0;\n    do {\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n            ULONG readlen;\n\n            if (start < tp.item->key.offset)\n                j = 0;\n            else\n                j = ((start - tp.item->key.offset) >> Vcb->sector_shift) + i;\n\n            if (j * Vcb->csum_size > tp.item->size || tp.item->key.offset > start + (i << Vcb->sector_shift)) {\n                ERR(\"checksum not found for %I64x\\n\", start + (i << Vcb->sector_shift));\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            readlen = (ULONG)min((tp.item->size / Vcb->csum_size) - j, length - i);\n            RtlCopyMemory(ptr, tp.item->data + (j * Vcb->csum_size), readlen * Vcb->csum_size);\n\n            ptr = (uint8_t*)ptr + (readlen * Vcb->csum_size);\n            i += readlen;\n\n            if (i == length)\n                break;\n        }\n\n        b = find_next_item(Vcb, &tp, &next_tp, false, Irp);\n\n        if (b)\n            tp = next_tp;\n    } while (b);\n\n    if (i < length) {\n        ERR(\"could not read checksums: offset %I64x, length %I64x sectors\\n\", start, length);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS load_dir_children(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, fcb* fcb, bool ignore_size, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp, next_tp;\n    NTSTATUS Status;\n    ULONG num_children = 0;\n    uint64_t max_index = 2;\n\n    fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);\n    if (!fcb->hash_ptrs) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256);\n\n    fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);\n    if (!fcb->hash_ptrs_uc) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256);\n\n    if (!ignore_size && fcb->inode_item.st_size == 0)\n        return STATUS_SUCCESS;\n\n    searchkey.obj_id = fcb->inode;\n    searchkey.obj_type = TYPE_DIR_INDEX;\n    searchkey.offset = 2;\n\n    Status = find_item(Vcb, fcb->subvol, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (keycmp(tp.item->key, searchkey) == -1) {\n        if (find_next_item(Vcb, &tp, &next_tp, false, Irp)) {\n            tp = next_tp;\n            TRACE(\"moving on to %I64x,%x,%I64x\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n        }\n    }\n\n    while (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n        DIR_ITEM* di = (DIR_ITEM*)tp.item->data;\n        dir_child* dc;\n        ULONG utf16len;\n\n        if (tp.item->size < sizeof(DIR_ITEM)) {\n            WARN(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(DIR_ITEM));\n            goto cont;\n        }\n\n        if (di->n == 0) {\n            WARN(\"(%I64x,%x,%I64x): DIR_ITEM name length is zero\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n            goto cont;\n        }\n\n        Status = utf8_to_utf16(NULL, 0, &utf16len, di->name, di->n);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"utf8_to_utf16 1 returned %08lx\\n\", Status);\n            goto cont;\n        }\n\n        dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG);\n        if (!dc) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        dc->key = di->key;\n        dc->index = tp.item->key.offset;\n        dc->type = di->type;\n        dc->fileref = NULL;\n        dc->root_dir = false;\n\n        max_index = dc->index;\n\n        dc->utf8.MaximumLength = dc->utf8.Length = di->n;\n        dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, di->n, ALLOC_TAG);\n        if (!dc->utf8.Buffer) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(dc);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(dc->utf8.Buffer, di->name, di->n);\n\n        dc->name.MaximumLength = dc->name.Length = (uint16_t)utf16len;\n        dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, dc->name.MaximumLength, ALLOC_TAG);\n        if (!dc->name.Buffer) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(dc->utf8.Buffer);\n            ExFreePool(dc);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        Status = utf8_to_utf16(dc->name.Buffer, utf16len, &utf16len, di->name, di->n);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"utf8_to_utf16 2 returned %08lx\\n\", Status);\n            ExFreePool(dc->utf8.Buffer);\n            ExFreePool(dc->name.Buffer);\n            ExFreePool(dc);\n            goto cont;\n        }\n\n        Status = RtlUpcaseUnicodeString(&dc->name_uc, &dc->name, true);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n            ExFreePool(dc->utf8.Buffer);\n            ExFreePool(dc->name.Buffer);\n            ExFreePool(dc);\n            goto cont;\n        }\n\n        dc->hash = calc_crc32c(0xffffffff, (uint8_t*)dc->name.Buffer, dc->name.Length);\n        dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)dc->name_uc.Buffer, dc->name_uc.Length);\n\n        InsertTailList(&fcb->dir_children_index, &dc->list_entry_index);\n\n        insert_dir_child_into_hash_lists(fcb, dc);\n\n        num_children++;\n\ncont:\n        if (find_next_item(Vcb, &tp, &next_tp, false, Irp))\n            tp = next_tp;\n        else\n            break;\n    }\n\n    if (!Vcb->options.no_root_dir && fcb->inode == SUBVOL_ROOT_INODE) {\n        root* top_subvol;\n\n        if (Vcb->root_fileref && Vcb->root_fileref->fcb)\n            top_subvol = Vcb->root_fileref->fcb->subvol;\n        else\n            top_subvol = find_default_subvol(Vcb, NULL);\n\n        if (fcb->subvol == top_subvol && top_subvol->id != BTRFS_ROOT_FSTREE) {\n            dir_child* dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG);\n            if (!dc) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            dc->key.obj_id = BTRFS_ROOT_FSTREE;\n            dc->key.obj_type = TYPE_ROOT_ITEM;\n            dc->key.offset = 0;\n            dc->index = max_index + 1;\n            dc->type = BTRFS_TYPE_DIRECTORY;\n            dc->fileref = NULL;\n            dc->root_dir = true;\n\n            dc->utf8.MaximumLength = dc->utf8.Length = sizeof(root_dir) - sizeof(char);\n            dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, sizeof(root_dir) - sizeof(char), ALLOC_TAG);\n            if (!dc->utf8.Buffer) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(dc);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(dc->utf8.Buffer, root_dir, sizeof(root_dir) - sizeof(char));\n\n            dc->name.MaximumLength = dc->name.Length = sizeof(root_dir_utf16) - sizeof(WCHAR);\n            dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, sizeof(root_dir_utf16) - sizeof(WCHAR), ALLOC_TAG);\n            if (!dc->name.Buffer) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(dc->utf8.Buffer);\n                ExFreePool(dc);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(dc->name.Buffer, root_dir_utf16, sizeof(root_dir_utf16) - sizeof(WCHAR));\n\n            Status = RtlUpcaseUnicodeString(&dc->name_uc, &dc->name, true);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n                ExFreePool(dc->utf8.Buffer);\n                ExFreePool(dc->name.Buffer);\n                ExFreePool(dc);\n                goto cont;\n            }\n\n            dc->hash = calc_crc32c(0xffffffff, (uint8_t*)dc->name.Buffer, dc->name.Length);\n            dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)dc->name_uc.Buffer, dc->name_uc.Length);\n\n            InsertTailList(&fcb->dir_children_index, &dc->list_entry_index);\n\n            insert_dir_child_into_hash_lists(fcb, dc);\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS open_fcb(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb,\n                  root* subvol, uint64_t inode, uint8_t type, PANSI_STRING utf8, bool always_add_hl, fcb* parent, fcb** pfcb, POOL_TYPE pooltype, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp, next_tp;\n    NTSTATUS Status;\n    fcb *fcb, *deleted_fcb = NULL;\n    bool atts_set = false, sd_set = false, no_data;\n    LIST_ENTRY* lastle = NULL;\n    EXTENT_DATA* ed = NULL;\n    uint64_t fcbs_version = 0;\n    uint32_t hash;\n\n    hash = calc_crc32c(0xffffffff, (uint8_t*)&inode, sizeof(uint64_t));\n\n    acquire_fcb_lock_shared(Vcb);\n\n    if (subvol->fcbs_ptrs[hash >> 24]) {\n        LIST_ENTRY* le = subvol->fcbs_ptrs[hash >> 24];\n\n        while (le != &subvol->fcbs) {\n            fcb = CONTAINING_RECORD(le, struct _fcb, list_entry);\n\n            if (fcb->inode == inode) {\n                if (!fcb->ads) {\n                    if (fcb->deleted)\n                        deleted_fcb = fcb;\n                    else {\n#ifdef DEBUG_FCB_REFCOUNTS\n                        LONG rc = InterlockedIncrement(&fcb->refcount);\n\n                        WARN(\"fcb %p: refcount now %i (subvol %I64x, inode %I64x)\\n\", fcb, rc, fcb->subvol->id, fcb->inode);\n#else\n                        InterlockedIncrement(&fcb->refcount);\n#endif\n\n                        *pfcb = fcb;\n                        release_fcb_lock(Vcb);\n                        return STATUS_SUCCESS;\n                    }\n                }\n            } else if (fcb->hash > hash) {\n                if (deleted_fcb) {\n                    InterlockedIncrement(&deleted_fcb->refcount);\n                    *pfcb = deleted_fcb;\n                    release_fcb_lock(Vcb);\n                    return STATUS_SUCCESS;\n                }\n\n                lastle = le->Blink;\n                fcbs_version = subvol->fcbs_version;\n\n                break;\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    release_fcb_lock(Vcb);\n\n    if (deleted_fcb) {\n        InterlockedIncrement(&deleted_fcb->refcount);\n        *pfcb = deleted_fcb;\n        return STATUS_SUCCESS;\n    }\n\n    fcb = create_fcb(Vcb, pooltype);\n    if (!fcb) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    fcb->Vcb = Vcb;\n\n    fcb->subvol = subvol;\n    fcb->inode = inode;\n    fcb->hash = hash;\n    fcb->type = type;\n\n    searchkey.obj_id = inode;\n    searchkey.obj_type = TYPE_INODE_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, subvol, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        reap_fcb(fcb);\n        return Status;\n    }\n\n    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n        WARN(\"couldn't find INODE_ITEM for inode %I64x in subvol %I64x\\n\", inode, subvol->id);\n        reap_fcb(fcb);\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (tp.item->size > 0)\n        RtlCopyMemory(&fcb->inode_item, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size));\n\n    if (fcb->type == 0) { // guess the type from the inode mode, if the caller doesn't know already\n        if ((fcb->inode_item.st_mode & __S_IFDIR) == __S_IFDIR)\n            fcb->type = BTRFS_TYPE_DIRECTORY;\n        else if ((fcb->inode_item.st_mode & __S_IFCHR) == __S_IFCHR)\n            fcb->type = BTRFS_TYPE_CHARDEV;\n        else if ((fcb->inode_item.st_mode & __S_IFBLK) == __S_IFBLK)\n            fcb->type = BTRFS_TYPE_BLOCKDEV;\n        else if ((fcb->inode_item.st_mode & __S_IFIFO) == __S_IFIFO)\n            fcb->type = BTRFS_TYPE_FIFO;\n        else if ((fcb->inode_item.st_mode & __S_IFLNK) == __S_IFLNK)\n            fcb->type = BTRFS_TYPE_SYMLINK;\n        else if ((fcb->inode_item.st_mode & __S_IFSOCK) == __S_IFSOCK)\n            fcb->type = BTRFS_TYPE_SOCKET;\n        else\n            fcb->type = BTRFS_TYPE_FILE;\n    }\n\n    no_data = fcb->inode_item.st_size == 0 || (fcb->type != BTRFS_TYPE_FILE && fcb->type != BTRFS_TYPE_SYMLINK);\n\n    while (find_next_item(Vcb, &tp, &next_tp, false, Irp)) {\n        tp = next_tp;\n\n        if (tp.item->key.obj_id > inode)\n            break;\n\n        if ((no_data && tp.item->key.obj_type > TYPE_XATTR_ITEM) || tp.item->key.obj_type > TYPE_EXTENT_DATA)\n            break;\n\n        if ((always_add_hl || fcb->inode_item.st_nlink > 1) && tp.item->key.obj_type == TYPE_INODE_REF) {\n            ULONG len;\n            INODE_REF* ir;\n\n            len = tp.item->size;\n            ir = (INODE_REF*)tp.item->data;\n\n            while (len >= sizeof(INODE_REF) - 1) {\n                hardlink* hl;\n                ULONG stringlen;\n\n                hl = ExAllocatePoolWithTag(pooltype, sizeof(hardlink), ALLOC_TAG);\n                if (!hl) {\n                    ERR(\"out of memory\\n\");\n                    reap_fcb(fcb);\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                hl->parent = tp.item->key.offset;\n                hl->index = ir->index;\n\n                hl->utf8.Length = hl->utf8.MaximumLength = ir->n;\n\n                if (hl->utf8.Length > 0) {\n                    hl->utf8.Buffer = ExAllocatePoolWithTag(pooltype, hl->utf8.MaximumLength, ALLOC_TAG);\n                    RtlCopyMemory(hl->utf8.Buffer, ir->name, ir->n);\n                }\n\n                Status = utf8_to_utf16(NULL, 0, &stringlen, ir->name, ir->n);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"utf8_to_utf16 1 returned %08lx\\n\", Status);\n                    ExFreePool(hl);\n                    reap_fcb(fcb);\n                    return Status;\n                }\n\n                hl->name.Length = hl->name.MaximumLength = (uint16_t)stringlen;\n\n                if (stringlen == 0)\n                    hl->name.Buffer = NULL;\n                else {\n                    hl->name.Buffer = ExAllocatePoolWithTag(pooltype, hl->name.MaximumLength, ALLOC_TAG);\n\n                    if (!hl->name.Buffer) {\n                        ERR(\"out of memory\\n\");\n                        ExFreePool(hl);\n                        reap_fcb(fcb);\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    Status = utf8_to_utf16(hl->name.Buffer, stringlen, &stringlen, ir->name, ir->n);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"utf8_to_utf16 2 returned %08lx\\n\", Status);\n                        ExFreePool(hl->name.Buffer);\n                        ExFreePool(hl);\n                        reap_fcb(fcb);\n                        return Status;\n                    }\n                }\n\n                InsertTailList(&fcb->hardlinks, &hl->list_entry);\n\n                len -= sizeof(INODE_REF) - 1 + ir->n;\n                ir = (INODE_REF*)&ir->name[ir->n];\n            }\n        } else if ((always_add_hl || fcb->inode_item.st_nlink > 1) && tp.item->key.obj_type == TYPE_INODE_EXTREF) {\n            ULONG len;\n            INODE_EXTREF* ier;\n\n            len = tp.item->size;\n            ier = (INODE_EXTREF*)tp.item->data;\n\n            while (len >= sizeof(INODE_EXTREF) - 1) {\n                hardlink* hl;\n                ULONG stringlen;\n\n                hl = ExAllocatePoolWithTag(pooltype, sizeof(hardlink), ALLOC_TAG);\n                if (!hl) {\n                    ERR(\"out of memory\\n\");\n                    reap_fcb(fcb);\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                hl->parent = ier->dir;\n                hl->index = ier->index;\n\n                hl->utf8.Length = hl->utf8.MaximumLength = ier->n;\n\n                if (hl->utf8.Length > 0) {\n                    hl->utf8.Buffer = ExAllocatePoolWithTag(pooltype, hl->utf8.MaximumLength, ALLOC_TAG);\n                    RtlCopyMemory(hl->utf8.Buffer, ier->name, ier->n);\n                }\n\n                Status = utf8_to_utf16(NULL, 0, &stringlen, ier->name, ier->n);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"utf8_to_utf16 1 returned %08lx\\n\", Status);\n                    ExFreePool(hl);\n                    reap_fcb(fcb);\n                    return Status;\n                }\n\n                hl->name.Length = hl->name.MaximumLength = (uint16_t)stringlen;\n\n                if (stringlen == 0)\n                    hl->name.Buffer = NULL;\n                else {\n                    hl->name.Buffer = ExAllocatePoolWithTag(pooltype, hl->name.MaximumLength, ALLOC_TAG);\n\n                    if (!hl->name.Buffer) {\n                        ERR(\"out of memory\\n\");\n                        ExFreePool(hl);\n                        reap_fcb(fcb);\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    Status = utf8_to_utf16(hl->name.Buffer, stringlen, &stringlen, ier->name, ier->n);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"utf8_to_utf16 2 returned %08lx\\n\", Status);\n                        ExFreePool(hl->name.Buffer);\n                        ExFreePool(hl);\n                        reap_fcb(fcb);\n                        return Status;\n                    }\n                }\n\n                InsertTailList(&fcb->hardlinks, &hl->list_entry);\n\n                len -= sizeof(INODE_EXTREF) - 1 + ier->n;\n                ier = (INODE_EXTREF*)&ier->name[ier->n];\n            }\n        } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) {\n            ULONG len;\n            DIR_ITEM* di;\n\n            static const char xapref[] = \"user.\";\n\n            if (tp.item->size < offsetof(DIR_ITEM, name[0])) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, offsetof(DIR_ITEM, name[0]));\n                continue;\n            }\n\n            len = tp.item->size;\n            di = (DIR_ITEM*)tp.item->data;\n\n            do {\n                if (len < offsetof(DIR_ITEM, name[0]) + di->m + di->n)\n                    break;\n\n                if (tp.item->key.offset == EA_REPARSE_HASH && di->n == sizeof(EA_REPARSE) - 1 && RtlCompareMemory(EA_REPARSE, di->name, di->n) == di->n) {\n                    if (di->m > 0) {\n                        fcb->reparse_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, di->m, ALLOC_TAG);\n                        if (!fcb->reparse_xattr.Buffer) {\n                            ERR(\"out of memory\\n\");\n                            reap_fcb(fcb);\n                            return STATUS_INSUFFICIENT_RESOURCES;\n                        }\n\n                        RtlCopyMemory(fcb->reparse_xattr.Buffer, &di->name[di->n], di->m);\n                    } else\n                        fcb->reparse_xattr.Buffer = NULL;\n\n                    fcb->reparse_xattr.Length = fcb->reparse_xattr.MaximumLength = di->m;\n                } else if (tp.item->key.offset == EA_EA_HASH && di->n == sizeof(EA_EA) - 1 && RtlCompareMemory(EA_EA, di->name, di->n) == di->n) {\n                    if (di->m > 0) {\n                        ULONG offset;\n\n                        Status = IoCheckEaBufferValidity((FILE_FULL_EA_INFORMATION*)&di->name[di->n], di->m, &offset);\n\n                        if (!NT_SUCCESS(Status))\n                            WARN(\"IoCheckEaBufferValidity returned %08lx (error at offset %lu)\\n\", Status, offset);\n                        else {\n                            FILE_FULL_EA_INFORMATION* eainfo;\n\n                            fcb->ea_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, di->m, ALLOC_TAG);\n                            if (!fcb->ea_xattr.Buffer) {\n                                ERR(\"out of memory\\n\");\n                                reap_fcb(fcb);\n                                return STATUS_INSUFFICIENT_RESOURCES;\n                            }\n\n                            RtlCopyMemory(fcb->ea_xattr.Buffer, &di->name[di->n], di->m);\n\n                            fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = di->m;\n\n                            fcb->ealen = 4;\n\n                            // calculate ealen\n                            eainfo = (FILE_FULL_EA_INFORMATION*)&di->name[di->n];\n                            do {\n                                fcb->ealen += 5 + eainfo->EaNameLength + eainfo->EaValueLength;\n\n                                if (eainfo->NextEntryOffset == 0)\n                                    break;\n\n                                eainfo = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)eainfo) + eainfo->NextEntryOffset);\n                            } while (true);\n                        }\n                    }\n                } else if (tp.item->key.offset == EA_DOSATTRIB_HASH && di->n == sizeof(EA_DOSATTRIB) - 1 && RtlCompareMemory(EA_DOSATTRIB, di->name, di->n) == di->n) {\n                    if (di->m > 0) {\n                        if (get_file_attributes_from_xattr(&di->name[di->n], di->m, &fcb->atts)) {\n                            atts_set = true;\n\n                            if (fcb->type == BTRFS_TYPE_DIRECTORY)\n                                fcb->atts |= FILE_ATTRIBUTE_DIRECTORY;\n                            else if (fcb->type == BTRFS_TYPE_SYMLINK)\n                                fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT;\n\n                            if (fcb->type != BTRFS_TYPE_DIRECTORY)\n                                fcb->atts &= ~FILE_ATTRIBUTE_DIRECTORY;\n\n                            if (inode == SUBVOL_ROOT_INODE) {\n                                if (subvol->root_item.flags & BTRFS_SUBVOL_READONLY)\n                                    fcb->atts |= FILE_ATTRIBUTE_READONLY;\n                                else\n                                    fcb->atts &= ~FILE_ATTRIBUTE_READONLY;\n                            }\n                        }\n                    }\n                } else if (tp.item->key.offset == EA_NTACL_HASH && di->n == sizeof(EA_NTACL) - 1 && RtlCompareMemory(EA_NTACL, di->name, di->n) == di->n) {\n                    if (di->m > 0) {\n                        fcb->sd = ExAllocatePoolWithTag(PagedPool, di->m, ALLOC_TAG);\n                        if (!fcb->sd) {\n                            ERR(\"out of memory\\n\");\n                            reap_fcb(fcb);\n                            return STATUS_INSUFFICIENT_RESOURCES;\n                        }\n\n                        RtlCopyMemory(fcb->sd, &di->name[di->n], di->m);\n\n                        // We have to test against our copy rather than the source, as RtlValidRelativeSecurityDescriptor\n                        // will fail if the ACLs aren't 32-bit aligned.\n                        if (!RtlValidRelativeSecurityDescriptor(fcb->sd, di->m, 0))\n                            ExFreePool(fcb->sd);\n                        else\n                            sd_set = true;\n                    }\n                } else if (tp.item->key.offset == EA_PROP_COMPRESSION_HASH && di->n == sizeof(EA_PROP_COMPRESSION) - 1 && RtlCompareMemory(EA_PROP_COMPRESSION, di->name, di->n) == di->n) {\n                    if (di->m > 0) {\n                        static const char lzo[] = \"lzo\";\n                        static const char zlib[] = \"zlib\";\n                        static const char zstd[] = \"zstd\";\n\n                        if (di->m == sizeof(lzo) - 1 && RtlCompareMemory(&di->name[di->n], lzo, di->m) == di->m)\n                            fcb->prop_compression = PropCompression_LZO;\n                        else if (di->m == sizeof(zlib) - 1 && RtlCompareMemory(&di->name[di->n], zlib, di->m) == di->m)\n                            fcb->prop_compression = PropCompression_Zlib;\n                        else if (di->m == sizeof(zstd) - 1 && RtlCompareMemory(&di->name[di->n], zstd, di->m) == di->m)\n                            fcb->prop_compression = PropCompression_ZSTD;\n                        else\n                            fcb->prop_compression = PropCompression_None;\n                    }\n                } else if (tp.item->key.offset == EA_CASE_SENSITIVE_HASH && di->n == sizeof(EA_CASE_SENSITIVE) - 1 && RtlCompareMemory(EA_CASE_SENSITIVE, di->name, di->n) == di->n) {\n                    if (di->m > 0) {\n                        fcb->case_sensitive = di->m == 1 && di->name[di->n] == '1';\n                        fcb->case_sensitive_set = true;\n                    }\n                } else if (di->n > sizeof(xapref) - 1 && RtlCompareMemory(xapref, di->name, sizeof(xapref) - 1) == sizeof(xapref) - 1) {\n                    dir_child* dc;\n                    ULONG utf16len;\n\n                    Status = utf8_to_utf16(NULL, 0, &utf16len, &di->name[sizeof(xapref) - 1], di->n + 1 - sizeof(xapref));\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"utf8_to_utf16 1 returned %08lx\\n\", Status);\n                        reap_fcb(fcb);\n                        return Status;\n                    }\n\n                    dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG);\n                    if (!dc) {\n                        ERR(\"out of memory\\n\");\n                        reap_fcb(fcb);\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    RtlZeroMemory(dc, sizeof(dir_child));\n\n                    dc->utf8.MaximumLength = dc->utf8.Length = di->n + 1 - sizeof(xapref);\n                    dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, dc->utf8.MaximumLength, ALLOC_TAG);\n                    if (!dc->utf8.Buffer) {\n                        ERR(\"out of memory\\n\");\n                        ExFreePool(dc);\n                        reap_fcb(fcb);\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    RtlCopyMemory(dc->utf8.Buffer, &di->name[sizeof(xapref) - 1], dc->utf8.Length);\n\n                    dc->name.MaximumLength = dc->name.Length = (uint16_t)utf16len;\n                    dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, dc->name.MaximumLength, ALLOC_TAG);\n                    if (!dc->name.Buffer) {\n                        ERR(\"out of memory\\n\");\n                        ExFreePool(dc->utf8.Buffer);\n                        ExFreePool(dc);\n                        reap_fcb(fcb);\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    Status = utf8_to_utf16(dc->name.Buffer, utf16len, &utf16len, dc->utf8.Buffer, dc->utf8.Length);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"utf8_to_utf16 2 returned %08lx\\n\", Status);\n                        ExFreePool(dc->utf8.Buffer);\n                        ExFreePool(dc->name.Buffer);\n                        ExFreePool(dc);\n                        reap_fcb(fcb);\n                        return Status;\n                    }\n\n                    Status = RtlUpcaseUnicodeString(&dc->name_uc, &dc->name, true);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n                        ExFreePool(dc->utf8.Buffer);\n                        ExFreePool(dc->name.Buffer);\n                        ExFreePool(dc);\n                        reap_fcb(fcb);\n                        return Status;\n                    }\n\n                    dc->size = di->m;\n\n                    InsertTailList(&fcb->dir_children_index, &dc->list_entry_index);\n                } else {\n                    xattr* xa;\n\n                    xa = ExAllocatePoolWithTag(PagedPool, offsetof(xattr, data[0]) + di->m + di->n, ALLOC_TAG);\n                    if (!xa) {\n                        ERR(\"out of memory\\n\");\n                        reap_fcb(fcb);\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    xa->namelen = di->n;\n                    xa->valuelen = di->m;\n                    xa->dirty = false;\n                    RtlCopyMemory(xa->data, di->name, di->m + di->n);\n\n                    InsertTailList(&fcb->xattrs, &xa->list_entry);\n                }\n\n                len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n;\n\n                if (len < offsetof(DIR_ITEM, name[0]))\n                    break;\n\n                di = (DIR_ITEM*)&di->name[di->m + di->n];\n            } while (true);\n        } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) {\n            extent* ext;\n            bool unique = false;\n\n            ed = (EXTENT_DATA*)tp.item->data;\n\n            if (tp.item->size < sizeof(EXTENT_DATA)) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n                    tp.item->size, sizeof(EXTENT_DATA));\n\n                reap_fcb(fcb);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {\n                EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ed->data[0];\n\n                if (tp.item->size < sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {\n                    ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n                        tp.item->size, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2));\n\n                    reap_fcb(fcb);\n                    return STATUS_INTERNAL_ERROR;\n                }\n\n                if (ed2->address == 0 || ed2->size == 0) // sparse\n                    continue;\n\n                if (ed2->size != 0 && is_tree_unique(Vcb, tp.tree, Irp))\n                    unique = is_extent_unique(Vcb, ed2->address, ed2->size, Irp);\n            }\n\n            ext = ExAllocatePoolWithTag(pooltype, offsetof(extent, extent_data) + tp.item->size, ALLOC_TAG);\n            if (!ext) {\n                ERR(\"out of memory\\n\");\n                reap_fcb(fcb);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            ext->offset = tp.item->key.offset;\n            RtlCopyMemory(&ext->extent_data, tp.item->data, tp.item->size);\n            ext->datalen = tp.item->size;\n            ext->unique = unique;\n            ext->ignore = false;\n            ext->inserted = false;\n            ext->csum = NULL;\n\n            InsertTailList(&fcb->extents, &ext->list_entry);\n        }\n    }\n\n    if (fcb->type == BTRFS_TYPE_DIRECTORY) {\n        Status = load_dir_children(Vcb, fcb, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"load_dir_children returned %08lx\\n\", Status);\n            reap_fcb(fcb);\n            return Status;\n        }\n    }\n\n    if (no_data) {\n        fcb->Header.AllocationSize.QuadPart = 0;\n        fcb->Header.FileSize.QuadPart = 0;\n        fcb->Header.ValidDataLength.QuadPart = 0;\n    } else {\n        if (ed && ed->type == EXTENT_TYPE_INLINE)\n            fcb->Header.AllocationSize.QuadPart = fcb->inode_item.st_size;\n        else\n            fcb->Header.AllocationSize.QuadPart = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);\n\n        fcb->Header.FileSize.QuadPart = fcb->inode_item.st_size;\n        fcb->Header.ValidDataLength.QuadPart = fcb->inode_item.st_size;\n    }\n\n    if (!atts_set)\n        fcb->atts = get_file_attributes(Vcb, fcb->subvol, fcb->inode, fcb->type, utf8 && utf8->Buffer[0] == '.', true, Irp);\n\n    if (!sd_set)\n        fcb_get_sd(fcb, parent, false, Irp);\n\n    acquire_fcb_lock_exclusive(Vcb);\n\n    if (lastle && subvol->fcbs_version == fcbs_version) {\n        InsertHeadList(lastle, &fcb->list_entry);\n\n        if (!subvol->fcbs_ptrs[hash >> 24] || CONTAINING_RECORD(subvol->fcbs_ptrs[hash >> 24], struct _fcb, list_entry)->hash > hash)\n            subvol->fcbs_ptrs[hash >> 24] = &fcb->list_entry;\n    } else {\n        lastle = NULL;\n\n        if (subvol->fcbs_ptrs[hash >> 24]) {\n            LIST_ENTRY* le = subvol->fcbs_ptrs[hash >> 24];\n\n            while (le != &subvol->fcbs) {\n                struct _fcb* fcb2 = CONTAINING_RECORD(le, struct _fcb, list_entry);\n\n                if (fcb2->inode == inode) {\n                    if (!fcb2->ads) {\n                        if (fcb2->deleted)\n                            deleted_fcb = fcb2;\n                        else {\n#ifdef DEBUG_FCB_REFCOUNTS\n                            LONG rc = InterlockedIncrement(&fcb2->refcount);\n\n                            WARN(\"fcb %p: refcount now %i (subvol %I64x, inode %I64x)\\n\", fcb2, rc, fcb2->subvol->id, fcb2->inode);\n#else\n                            InterlockedIncrement(&fcb2->refcount);\n#endif\n\n                            *pfcb = fcb2;\n                            reap_fcb(fcb);\n                            release_fcb_lock(Vcb);\n                            return STATUS_SUCCESS;\n                        }\n                    }\n                } else if (fcb2->hash > hash) {\n                    if (deleted_fcb) {\n                        InterlockedIncrement(&deleted_fcb->refcount);\n                        *pfcb = deleted_fcb;\n                        reap_fcb(fcb);\n                        release_fcb_lock(Vcb);\n                        return STATUS_SUCCESS;\n                    }\n\n                    lastle = le->Blink;\n                    break;\n                }\n\n                le = le->Flink;\n            }\n        }\n\n        if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT && fcb->reparse_xattr.Length == 0) {\n            fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;\n\n            if (!Vcb->readonly && !is_subvol_readonly(subvol, Irp)) {\n                fcb->atts_changed = true;\n                mark_fcb_dirty(fcb);\n            }\n        }\n\n        if (!lastle) {\n            uint8_t c = hash >> 24;\n\n            if (c != 0xff) {\n                uint8_t d = c + 1;\n\n                do {\n                    if (subvol->fcbs_ptrs[d]) {\n                        lastle = subvol->fcbs_ptrs[d]->Blink;\n                        break;\n                    }\n\n                    d++;\n                } while (d != 0);\n            }\n        }\n\n        if (lastle) {\n            InsertHeadList(lastle, &fcb->list_entry);\n\n            if (lastle == &subvol->fcbs || (CONTAINING_RECORD(lastle, struct _fcb, list_entry)->hash >> 24) != (hash >> 24))\n                subvol->fcbs_ptrs[hash >> 24] = &fcb->list_entry;\n        } else {\n            InsertTailList(&subvol->fcbs, &fcb->list_entry);\n\n            if (fcb->list_entry.Blink == &subvol->fcbs || (CONTAINING_RECORD(fcb->list_entry.Blink, struct _fcb, list_entry)->hash >> 24) != (hash >> 24))\n                subvol->fcbs_ptrs[hash >> 24] = &fcb->list_entry;\n        }\n    }\n\n    if (fcb->inode == SUBVOL_ROOT_INODE && fcb->subvol->id == BTRFS_ROOT_FSTREE && fcb->subvol != Vcb->root_fileref->fcb->subvol)\n        fcb->atts |= FILE_ATTRIBUTE_HIDDEN;\n\n    subvol->fcbs_version++;\n\n    InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all);\n\n    release_fcb_lock(Vcb);\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n\n    *pfcb = fcb;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS open_fcb_stream(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb,\n                                dir_child* dc, fcb* parent, fcb** pfcb, PIRP Irp) {\n    fcb* fcb;\n    uint8_t* xattrdata;\n    uint16_t xattrlen, overhead;\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    static const char xapref[] = \"user.\";\n    ANSI_STRING xattr;\n    uint32_t crc32;\n\n    xattr.Length = sizeof(xapref) - 1 + dc->utf8.Length;\n    xattr.MaximumLength = xattr.Length + 1;\n    xattr.Buffer = ExAllocatePoolWithTag(PagedPool, xattr.MaximumLength, ALLOC_TAG);\n    if (!xattr.Buffer) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(xattr.Buffer, xapref, sizeof(xapref) - 1);\n    RtlCopyMemory(&xattr.Buffer[sizeof(xapref) - 1], dc->utf8.Buffer, dc->utf8.Length);\n    xattr.Buffer[xattr.Length] = 0;\n\n    fcb = create_fcb(Vcb, PagedPool);\n    if (!fcb) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(xattr.Buffer);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    fcb->Vcb = Vcb;\n\n    crc32 = calc_crc32c(0xfffffffe, (uint8_t*)xattr.Buffer, xattr.Length);\n\n    if (!get_xattr(Vcb, parent->subvol, parent->inode, xattr.Buffer, crc32, &xattrdata, &xattrlen, Irp)) {\n        ERR(\"get_xattr failed\\n\");\n        reap_fcb(fcb);\n        ExFreePool(xattr.Buffer);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    fcb->subvol = parent->subvol;\n    fcb->inode = parent->inode;\n    fcb->type = parent->type;\n    fcb->ads = true;\n    fcb->adshash = crc32;\n    fcb->adsxattr = xattr;\n\n    // find XATTR_ITEM overhead and hence calculate maximum length\n\n    searchkey.obj_id = parent->inode;\n    searchkey.obj_type = TYPE_XATTR_ITEM;\n    searchkey.offset = crc32;\n\n    Status = find_item(Vcb, parent->subvol, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        reap_fcb(fcb);\n        return Status;\n    }\n\n    if (keycmp(tp.item->key, searchkey)) {\n        ERR(\"error - could not find key for xattr\\n\");\n        reap_fcb(fcb);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (tp.item->size < xattrlen) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %u\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, xattrlen);\n        reap_fcb(fcb);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    overhead = tp.item->size - xattrlen;\n\n    fcb->adsmaxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) - overhead;\n\n    fcb->adsdata.Buffer = (char*)xattrdata;\n    fcb->adsdata.Length = fcb->adsdata.MaximumLength = xattrlen;\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n    fcb->Header.AllocationSize.QuadPart = xattrlen;\n    fcb->Header.FileSize.QuadPart = xattrlen;\n    fcb->Header.ValidDataLength.QuadPart = xattrlen;\n\n    TRACE(\"stream found: size = %x, hash = %08x\\n\", xattrlen, fcb->adshash);\n\n    *pfcb = fcb;\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS open_fileref_child(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb,\n                            _In_ file_ref* sf, _In_ PUNICODE_STRING name, _In_ bool case_sensitive, _In_ bool lastpart, _In_ bool streampart,\n                            _In_ POOL_TYPE pooltype, _Out_ file_ref** psf2, _In_opt_ PIRP Irp) {\n    NTSTATUS Status;\n    file_ref* sf2;\n\n    if (sf->fcb == Vcb->dummy_fcb)\n        return STATUS_OBJECT_NAME_NOT_FOUND;\n\n    if (streampart) {\n        bool locked = false;\n        LIST_ENTRY* le;\n        UNICODE_STRING name_uc;\n        dir_child* dc = NULL;\n        fcb* fcb;\n        struct _fcb* duff_fcb = NULL;\n        file_ref* duff_fr = NULL;\n\n        if (!case_sensitive) {\n            Status = RtlUpcaseUnicodeString(&name_uc, name, true);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        if (!ExIsResourceAcquiredSharedLite(&sf->fcb->nonpaged->dir_children_lock)) {\n            ExAcquireResourceSharedLite(&sf->fcb->nonpaged->dir_children_lock, true);\n            locked = true;\n        }\n\n        le = sf->fcb->dir_children_index.Flink;\n        while (le != &sf->fcb->dir_children_index) {\n            dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_index);\n\n            if (dc2->index == 0) {\n                if ((case_sensitive && dc2->name.Length == name->Length && RtlCompareMemory(dc2->name.Buffer, name->Buffer, dc2->name.Length) == dc2->name.Length) ||\n                    (!case_sensitive && dc2->name_uc.Length == name_uc.Length && RtlCompareMemory(dc2->name_uc.Buffer, name_uc.Buffer, dc2->name_uc.Length) == dc2->name_uc.Length)\n                ) {\n                    dc = dc2;\n                    break;\n                }\n            } else\n                break;\n\n            le = le->Flink;\n        }\n\n        if (!dc) {\n            if (locked)\n                ExReleaseResourceLite(&sf->fcb->nonpaged->dir_children_lock);\n\n            if (!case_sensitive)\n                ExFreePool(name_uc.Buffer);\n\n            return STATUS_OBJECT_NAME_NOT_FOUND;\n        }\n\n        if (dc->fileref) {\n            if (locked)\n                ExReleaseResourceLite(&sf->fcb->nonpaged->dir_children_lock);\n\n            if (!case_sensitive)\n                ExFreePool(name_uc.Buffer);\n\n            increase_fileref_refcount(dc->fileref);\n            *psf2 = dc->fileref;\n            return STATUS_SUCCESS;\n        }\n\n        if (locked)\n            ExReleaseResourceLite(&sf->fcb->nonpaged->dir_children_lock);\n\n        if (!case_sensitive)\n            ExFreePool(name_uc.Buffer);\n\n        Status = open_fcb_stream(Vcb, dc, sf->fcb, &fcb, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"open_fcb_stream returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        fcb->hash = sf->fcb->hash;\n\n        acquire_fcb_lock_exclusive(Vcb);\n\n        if (sf->fcb->subvol->fcbs_ptrs[fcb->hash >> 24]) {\n            le = sf->fcb->subvol->fcbs_ptrs[fcb->hash >> 24];\n\n            while (le != &sf->fcb->subvol->fcbs) {\n                struct _fcb* fcb2 = CONTAINING_RECORD(le, struct _fcb, list_entry);\n\n                if (fcb2->inode == fcb->inode) {\n                    if (fcb2->ads && fcb2->adshash == fcb->adshash) { // FIXME - handle hash collisions\n                        duff_fcb = fcb;\n                        fcb = fcb2;\n                        break;\n                    }\n                } else if (fcb2->hash > fcb->hash)\n                    break;\n\n                le = le->Flink;\n            }\n        }\n\n        if (!duff_fcb) {\n            InsertHeadList(&sf->fcb->list_entry, &fcb->list_entry);\n            InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all);\n            fcb->subvol->fcbs_version++;\n        }\n\n        release_fcb_lock(Vcb);\n\n        if (duff_fcb) {\n            reap_fcb(duff_fcb);\n            InterlockedIncrement(&fcb->refcount);\n        }\n\n        sf2 = create_fileref(Vcb);\n        if (!sf2) {\n            ERR(\"out of memory\\n\");\n            free_fcb(fcb);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        ExAcquireResourceExclusiveLite(&sf->fcb->nonpaged->dir_children_lock, true);\n\n        if (dc->fileref) {\n            duff_fr = sf2;\n            sf2 = dc->fileref;\n            increase_fileref_refcount(sf2);\n        } else {\n            sf2->fcb = fcb;\n            sf2->parent = (struct _file_ref*)sf;\n            sf2->dc = dc;\n            dc->fileref = sf2;\n            increase_fileref_refcount(sf);\n            InsertTailList(&sf->children, &sf2->list_entry);\n        }\n\n        ExReleaseResourceLite(&sf->fcb->nonpaged->dir_children_lock);\n\n        if (duff_fr)\n            ExFreeToPagedLookasideList(&Vcb->fileref_lookaside, duff_fr);\n    } else {\n        root* subvol;\n        uint64_t inode;\n        dir_child* dc;\n\n        Status = find_file_in_dir(name, sf->fcb, &subvol, &inode, &dc, case_sensitive);\n        if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {\n            TRACE(\"could not find %.*S\\n\", (int)(name->Length / sizeof(WCHAR)), name->Buffer);\n\n            return lastpart ? STATUS_OBJECT_NAME_NOT_FOUND : STATUS_OBJECT_PATH_NOT_FOUND;\n        } else if (Status == STATUS_OBJECT_NAME_INVALID) {\n            TRACE(\"invalid filename: %.*S\\n\", (int)(name->Length / sizeof(WCHAR)), name->Buffer);\n            return Status;\n        } else if (!NT_SUCCESS(Status)) {\n            ERR(\"find_file_in_dir returned %08lx\\n\", Status);\n            return Status;\n        } else {\n            fcb* fcb;\n            file_ref* duff_fr = NULL;\n\n            if (dc->fileref) {\n                if (!lastpart && dc->type != BTRFS_TYPE_DIRECTORY) {\n                    TRACE(\"passed path including file as subdirectory\\n\");\n                    return STATUS_OBJECT_PATH_NOT_FOUND;\n                }\n\n                InterlockedIncrement(&dc->fileref->refcount);\n                *psf2 = dc->fileref;\n                return STATUS_SUCCESS;\n            }\n\n            if (!subvol || (subvol != Vcb->root_fileref->fcb->subvol && inode == SUBVOL_ROOT_INODE && subvol->parent != sf->fcb->subvol->id && !dc->root_dir)) {\n                fcb = Vcb->dummy_fcb;\n                InterlockedIncrement(&fcb->refcount);\n            } else {\n                Status = open_fcb(Vcb, subvol, inode, dc->type, &dc->utf8, false, sf->fcb, &fcb, pooltype, Irp);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"open_fcb returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n\n            if (dc->type != BTRFS_TYPE_DIRECTORY && !lastpart && !(fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT)) {\n                TRACE(\"passed path including file as subdirectory\\n\");\n                free_fcb(fcb);\n                return STATUS_OBJECT_PATH_NOT_FOUND;\n            }\n\n            sf2 = create_fileref(Vcb);\n            if (!sf2) {\n                ERR(\"out of memory\\n\");\n                free_fcb(fcb);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            sf2->fcb = fcb;\n\n            ExAcquireResourceExclusiveLite(&sf->fcb->nonpaged->dir_children_lock, true);\n\n            if (!dc->fileref) {\n                sf2->parent = (struct _file_ref*)sf;\n                sf2->dc = dc;\n                dc->fileref = sf2;\n                InsertTailList(&sf->children, &sf2->list_entry);\n                increase_fileref_refcount(sf);\n\n                if (dc->type == BTRFS_TYPE_DIRECTORY)\n                    fcb->fileref = sf2;\n            } else {\n                duff_fr = sf2;\n                sf2 = dc->fileref;\n                increase_fileref_refcount(sf2);\n            }\n\n            ExReleaseResourceLite(&sf->fcb->nonpaged->dir_children_lock);\n\n            if (duff_fr)\n                reap_fileref(Vcb, duff_fr);\n        }\n    }\n\n    *psf2 = sf2;\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS open_fileref(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb, _Out_ file_ref** pfr,\n                      _In_ PUNICODE_STRING fnus, _In_opt_ file_ref* related, _In_ bool parent, _Out_opt_ USHORT* parsed, _Out_opt_ ULONG* fn_offset, _In_ POOL_TYPE pooltype,\n                      _In_ bool case_sensitive, _In_opt_ PIRP Irp) {\n    UNICODE_STRING fnus2;\n    file_ref *dir, *sf, *sf2;\n    LIST_ENTRY parts;\n    bool has_stream = false;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n\n    TRACE(\"(%p, %p, %p, %u, %p)\\n\", Vcb, pfr, related, parent, parsed);\n\n    if (Vcb->removing || Vcb->locked)\n        return STATUS_ACCESS_DENIED;\n\n    fnus2 = *fnus;\n\n    if (fnus2.Length < sizeof(WCHAR) && !related) {\n        ERR(\"error - fnus was too short\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (related && fnus->Length == 0) {\n        increase_fileref_refcount(related);\n\n        *pfr = related;\n        return STATUS_SUCCESS;\n    }\n\n    if (related) {\n        dir = related;\n    } else {\n        if (fnus2.Buffer[0] != '\\\\') {\n            ERR(\"error - filename %.*S did not begin with \\\\\\n\", (int)(fnus2.Length / sizeof(WCHAR)), fnus2.Buffer);\n            return STATUS_OBJECT_PATH_NOT_FOUND;\n        }\n\n        // if path starts with two backslashes, ignore one of them\n        if (fnus2.Length >= 2 * sizeof(WCHAR) && fnus2.Buffer[1] == '\\\\') {\n            fnus2.Buffer++;\n            fnus2.Length -= sizeof(WCHAR);\n            fnus2.MaximumLength -= sizeof(WCHAR);\n        }\n\n        if (fnus2.Length == sizeof(WCHAR)) {\n            if (Vcb->root_fileref->open_count == 0 && !(Vcb->Vpb->Flags & VPB_MOUNTED)) // don't allow root to be opened on unmounted FS\n                return STATUS_DEVICE_NOT_READY;\n\n            increase_fileref_refcount(Vcb->root_fileref);\n            *pfr = Vcb->root_fileref;\n\n            if (fn_offset)\n                *fn_offset = 0;\n\n            return STATUS_SUCCESS;\n        } else if (fnus2.Length >= 2 * sizeof(WCHAR) && fnus2.Buffer[1] == '\\\\')\n            return STATUS_OBJECT_NAME_INVALID;\n\n        dir = Vcb->root_fileref;\n\n        fnus2.Buffer++;\n        fnus2.Length -= sizeof(WCHAR);\n        fnus2.MaximumLength -= sizeof(WCHAR);\n    }\n\n    if (dir->fcb->type != BTRFS_TYPE_DIRECTORY && (fnus->Length < sizeof(WCHAR) || fnus->Buffer[0] != ':')) {\n        WARN(\"passed related fileref which isn't a directory (fnus = %.*S)\\n\",\n             (int)(fnus->Length / sizeof(WCHAR)), fnus->Buffer);\n        return STATUS_OBJECT_PATH_NOT_FOUND;\n    }\n\n    InitializeListHead(&parts);\n\n    if (fnus->Length != 0 &&\n        (fnus->Length != sizeof(datastring) - sizeof(WCHAR) || RtlCompareMemory(fnus->Buffer, datastring, sizeof(datastring) - sizeof(WCHAR)) != sizeof(datastring) - sizeof(WCHAR))) {\n        Status = split_path(Vcb, &fnus2, &parts, &has_stream);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"split_path returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    sf = dir;\n    increase_fileref_refcount(dir);\n\n    if (parent && !IsListEmpty(&parts)) {\n        name_bit* nb;\n\n        nb = CONTAINING_RECORD(RemoveTailList(&parts), name_bit, list_entry);\n        ExFreeToPagedLookasideList(&Vcb->name_bit_lookaside, nb);\n\n        if (has_stream && !IsListEmpty(&parts)) {\n            nb = CONTAINING_RECORD(RemoveTailList(&parts), name_bit, list_entry);\n            ExFreeToPagedLookasideList(&Vcb->name_bit_lookaside, nb);\n\n            has_stream = false;\n        }\n    }\n\n    if (IsListEmpty(&parts)) {\n        Status = STATUS_SUCCESS;\n        *pfr = dir;\n\n        if (fn_offset)\n            *fn_offset = 0;\n\n        goto end2;\n    }\n\n    le = parts.Flink;\n    do {\n        name_bit* nb = CONTAINING_RECORD(le, name_bit, list_entry);\n        bool lastpart = le->Flink == &parts || (has_stream && le->Flink->Flink == &parts);\n        bool streampart = has_stream && le->Flink == &parts;\n        bool cs = case_sensitive;\n\n        if (!cs) {\n            if (streampart && sf->parent)\n                cs = sf->parent->fcb->case_sensitive;\n            else\n                cs = sf->fcb->case_sensitive;\n        }\n\n        Status = open_fileref_child(Vcb, sf, &nb->us, cs, lastpart, streampart, pooltype, &sf2, Irp);\n\n        if (!NT_SUCCESS(Status)) {\n            if (Status == STATUS_OBJECT_PATH_NOT_FOUND || Status == STATUS_OBJECT_NAME_NOT_FOUND || Status == STATUS_OBJECT_NAME_INVALID)\n                TRACE(\"open_fileref_child returned %08lx\\n\", Status);\n            else\n                ERR(\"open_fileref_child returned %08lx\\n\", Status);\n\n            goto end;\n        }\n\n        if (le->Flink == &parts) { // last entry\n            if (fn_offset) {\n                if (has_stream)\n                    nb = CONTAINING_RECORD(le->Blink, name_bit, list_entry);\n\n                *fn_offset = (ULONG)(nb->us.Buffer - fnus->Buffer);\n            }\n\n            break;\n        }\n\n        if (sf2->fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) {\n            Status = STATUS_REPARSE;\n\n            if (parsed) {\n                name_bit* nb2 = CONTAINING_RECORD(le->Flink, name_bit, list_entry);\n\n                *parsed = (USHORT)(nb2->us.Buffer - fnus->Buffer - 1) * sizeof(WCHAR);\n            }\n\n            break;\n        }\n\n        free_fileref(sf);\n        sf = sf2;\n\n        le = le->Flink;\n    } while (le != &parts);\n\n    if (Status != STATUS_REPARSE)\n        Status = STATUS_SUCCESS;\n    *pfr = sf2;\n\nend:\n    free_fileref(sf);\n\n    while (!IsListEmpty(&parts)) {\n        name_bit* nb = CONTAINING_RECORD(RemoveHeadList(&parts), name_bit, list_entry);\n        ExFreeToPagedLookasideList(&Vcb->name_bit_lookaside, nb);\n    }\n\nend2:\n    TRACE(\"returning %08lx\\n\", Status);\n\n    return Status;\n}\n\nNTSTATUS add_dir_child(fcb* fcb, uint64_t inode, bool subvol, PANSI_STRING utf8, PUNICODE_STRING name, uint8_t type, dir_child** pdc) {\n    NTSTATUS Status;\n    dir_child* dc;\n    bool locked;\n\n    dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG);\n    if (!dc) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(dc, sizeof(dir_child));\n\n    dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8->Length, ALLOC_TAG);\n    if (!dc->utf8.Buffer) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(dc);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, name->Length, ALLOC_TAG);\n    if (!dc->name.Buffer) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(dc->utf8.Buffer);\n        ExFreePool(dc);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    dc->key.obj_id = inode;\n    dc->key.obj_type = subvol ? TYPE_ROOT_ITEM : TYPE_INODE_ITEM;\n    dc->key.offset = subvol ? 0xffffffffffffffff : 0;\n    dc->type = type;\n    dc->fileref = NULL;\n\n    dc->utf8.Length = dc->utf8.MaximumLength = utf8->Length;\n    RtlCopyMemory(dc->utf8.Buffer, utf8->Buffer, utf8->Length);\n\n    dc->name.Length = dc->name.MaximumLength = name->Length;\n    RtlCopyMemory(dc->name.Buffer, name->Buffer, name->Length);\n\n    Status = RtlUpcaseUnicodeString(&dc->name_uc, name, true);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n        ExFreePool(dc->utf8.Buffer);\n        ExFreePool(dc->name.Buffer);\n        ExFreePool(dc);\n        return Status;\n    }\n\n    dc->hash = calc_crc32c(0xffffffff, (uint8_t*)dc->name.Buffer, dc->name.Length);\n    dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)dc->name_uc.Buffer, dc->name_uc.Length);\n\n    locked = ExIsResourceAcquiredExclusive(&fcb->nonpaged->dir_children_lock);\n\n    if (!locked)\n        ExAcquireResourceExclusiveLite(&fcb->nonpaged->dir_children_lock, true);\n\n    if (IsListEmpty(&fcb->dir_children_index))\n        dc->index = 2;\n    else {\n        dir_child* dc2 = CONTAINING_RECORD(fcb->dir_children_index.Blink, dir_child, list_entry_index);\n\n        dc->index = max(2, dc2->index + 1);\n    }\n\n    InsertTailList(&fcb->dir_children_index, &dc->list_entry_index);\n\n    insert_dir_child_into_hash_lists(fcb, dc);\n\n    if (!locked)\n        ExReleaseResourceLite(&fcb->nonpaged->dir_children_lock);\n\n    *pdc = dc;\n\n    return STATUS_SUCCESS;\n}\n\nuint32_t inherit_mode(fcb* parfcb, bool is_dir) {\n    uint32_t mode;\n\n    if (!parfcb)\n        return 0755;\n\n    mode = parfcb->inode_item.st_mode & ~S_IFDIR;\n    mode &= ~S_ISVTX; // clear sticky bit\n    mode &= ~S_ISUID; // clear setuid bit\n\n    if (!is_dir)\n        mode &= ~S_ISGID; // if not directory, clear setgid bit\n\n    return mode;\n}\n\nstatic NTSTATUS file_create_parse_ea(fcb* fcb, FILE_FULL_EA_INFORMATION* ea) {\n    NTSTATUS Status;\n    LIST_ENTRY ealist, *le;\n    uint16_t size = 0;\n    char* buf;\n\n    InitializeListHead(&ealist);\n\n    do {\n        STRING s;\n        bool found = false;\n\n        s.Length = s.MaximumLength = ea->EaNameLength;\n        s.Buffer = ea->EaName;\n\n        RtlUpperString(&s, &s);\n\n        le = ealist.Flink;\n        while (le != &ealist) {\n            ea_item* item = CONTAINING_RECORD(le, ea_item, list_entry);\n\n            if (item->name.Length == s.Length && RtlCompareMemory(item->name.Buffer, s.Buffer, s.Length) == s.Length) {\n                item->flags = ea->Flags;\n                item->value.Length = item->value.MaximumLength = ea->EaValueLength;\n                item->value.Buffer = &ea->EaName[ea->EaNameLength + 1];\n                found = true;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        if (!found) {\n            ea_item* item = ExAllocatePoolWithTag(PagedPool, sizeof(ea_item), ALLOC_TAG);\n            if (!item) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            item->name.Length = item->name.MaximumLength = ea->EaNameLength;\n            item->name.Buffer = ea->EaName;\n\n            item->value.Length = item->value.MaximumLength = ea->EaValueLength;\n            item->value.Buffer = &ea->EaName[ea->EaNameLength + 1];\n\n            item->flags = ea->Flags;\n\n            InsertTailList(&ealist, &item->list_entry);\n        }\n\n        if (ea->NextEntryOffset == 0)\n            break;\n\n        ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);\n    } while (true);\n\n    // handle LXSS values\n    le = ealist.Flink;\n    while (le != &ealist) {\n        LIST_ENTRY* le2 = le->Flink;\n        ea_item* item = CONTAINING_RECORD(le, ea_item, list_entry);\n\n        if (item->name.Length == sizeof(lxuid) - 1 && RtlCompareMemory(item->name.Buffer, lxuid, item->name.Length) == item->name.Length) {\n            if (item->value.Length < sizeof(uint32_t)) {\n                ERR(\"uid value was shorter than expected\\n\");\n                Status = STATUS_INVALID_PARAMETER;\n                goto end;\n            }\n\n            RtlCopyMemory(&fcb->inode_item.st_uid, item->value.Buffer, sizeof(uint32_t));\n            fcb->sd_dirty = true;\n            fcb->sd_deleted = false;\n\n            RemoveEntryList(&item->list_entry);\n            ExFreePool(item);\n        } else if (item->name.Length == sizeof(lxgid) - 1 && RtlCompareMemory(item->name.Buffer, lxgid, item->name.Length) == item->name.Length) {\n            if (item->value.Length < sizeof(uint32_t)) {\n                ERR(\"gid value was shorter than expected\\n\");\n                Status = STATUS_INVALID_PARAMETER;\n                goto end;\n            }\n\n            RtlCopyMemory(&fcb->inode_item.st_gid, item->value.Buffer, sizeof(uint32_t));\n\n            RemoveEntryList(&item->list_entry);\n            ExFreePool(item);\n        } else if (item->name.Length == sizeof(lxmod) - 1 && RtlCompareMemory(item->name.Buffer, lxmod, item->name.Length) == item->name.Length) {\n            uint32_t allowed = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH | S_ISGID | S_ISVTX | S_ISUID;\n            uint32_t val;\n\n            if (item->value.Length < sizeof(uint32_t)) {\n                ERR(\"mode value was shorter than expected\\n\");\n                Status = STATUS_INVALID_PARAMETER;\n                goto end;\n            }\n\n            val = *(uint32_t*)item->value.Buffer;\n\n            fcb->inode_item.st_mode &= ~allowed;\n            fcb->inode_item.st_mode |= val & allowed;\n\n            if (fcb->type != BTRFS_TYPE_DIRECTORY) {\n                if (__S_ISTYPE(val, __S_IFCHR)) {\n                    fcb->type = BTRFS_TYPE_CHARDEV;\n                    fcb->inode_item.st_mode &= ~__S_IFMT;\n                    fcb->inode_item.st_mode |= __S_IFCHR;\n                } else if (__S_ISTYPE(val, __S_IFBLK)) {\n                    fcb->type = BTRFS_TYPE_BLOCKDEV;\n                    fcb->inode_item.st_mode &= ~__S_IFMT;\n                    fcb->inode_item.st_mode |= __S_IFBLK;\n                } else if (__S_ISTYPE(val, __S_IFIFO)) {\n                    fcb->type = BTRFS_TYPE_FIFO;\n                    fcb->inode_item.st_mode &= ~__S_IFMT;\n                    fcb->inode_item.st_mode |= __S_IFIFO;\n                } else if (__S_ISTYPE(val, __S_IFSOCK)) {\n                    fcb->type = BTRFS_TYPE_SOCKET;\n                    fcb->inode_item.st_mode &= ~__S_IFMT;\n                    fcb->inode_item.st_mode |= __S_IFSOCK;\n                }\n            }\n\n            RemoveEntryList(&item->list_entry);\n            ExFreePool(item);\n        } else if (item->name.Length == sizeof(lxdev) - 1 && RtlCompareMemory(item->name.Buffer, lxdev, item->name.Length) == item->name.Length) {\n            uint32_t major, minor;\n\n            if (item->value.Length < sizeof(uint64_t)) {\n                ERR(\"dev value was shorter than expected\\n\");\n                Status = STATUS_INVALID_PARAMETER;\n                goto end;\n            }\n\n            major = *(uint32_t*)item->value.Buffer;\n            minor = *(uint32_t*)&item->value.Buffer[sizeof(uint32_t)];\n\n            fcb->inode_item.st_rdev = (minor & 0xFFFFF) | ((major & 0xFFFFFFFFFFF) << 20);\n\n            RemoveEntryList(&item->list_entry);\n            ExFreePool(item);\n        }\n\n        le = le2;\n    }\n\n    if (fcb->type != BTRFS_TYPE_CHARDEV && fcb->type != BTRFS_TYPE_BLOCKDEV)\n        fcb->inode_item.st_rdev = 0;\n\n    if (IsListEmpty(&ealist))\n        return STATUS_SUCCESS;\n\n    le = ealist.Flink;\n    while (le != &ealist) {\n        ea_item* item = CONTAINING_RECORD(le, ea_item, list_entry);\n\n        if (size % 4 > 0)\n            size += 4 - (size % 4);\n\n        size += (uint16_t)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + item->name.Length + 1 + item->value.Length;\n\n        le = le->Flink;\n    }\n\n    buf = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG);\n    if (!buf) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = size;\n    fcb->ea_xattr.Buffer = buf;\n\n    fcb->ealen = 4;\n    ea = NULL;\n\n    le = ealist.Flink;\n    while (le != &ealist) {\n        ea_item* item = CONTAINING_RECORD(le, ea_item, list_entry);\n\n        if (ea) {\n            ea->NextEntryOffset = (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + ea->EaValueLength;\n\n            if (ea->NextEntryOffset % 4 > 0)\n                ea->NextEntryOffset += 4 - (ea->NextEntryOffset % 4);\n\n            ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);\n        } else\n            ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer;\n\n        ea->NextEntryOffset = 0;\n        ea->Flags = item->flags;\n        ea->EaNameLength = (UCHAR)item->name.Length;\n        ea->EaValueLength = item->value.Length;\n\n        RtlCopyMemory(ea->EaName, item->name.Buffer, item->name.Length);\n        ea->EaName[item->name.Length] = 0;\n        RtlCopyMemory(&ea->EaName[item->name.Length + 1], item->value.Buffer, item->value.Length);\n\n        fcb->ealen += 5 + item->name.Length + item->value.Length;\n\n        le = le->Flink;\n    }\n\n    fcb->ea_changed = true;\n\n    Status = STATUS_SUCCESS;\n\nend:\n    while (!IsListEmpty(&ealist)) {\n        ea_item* item = CONTAINING_RECORD(RemoveHeadList(&ealist), ea_item, list_entry);\n\n        ExFreePool(item);\n    }\n\n    return Status;\n}\n\nstatic NTSTATUS file_create2(_In_ PIRP Irp, _Requires_exclusive_lock_held_(_Curr_->fcb_lock) _In_ device_extension* Vcb, _In_ PUNICODE_STRING fpus,\n                             _In_ file_ref* parfileref, _In_ ULONG options, _In_reads_bytes_opt_(ealen) FILE_FULL_EA_INFORMATION* ea, _In_ ULONG ealen,\n                             _Out_ file_ref** pfr, bool case_sensitive, _In_ LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    fcb* fcb;\n    ULONG utf8len;\n    char* utf8 = NULL;\n    uint64_t inode;\n    uint8_t type;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    POOL_TYPE pool_type = IrpSp->Flags & SL_OPEN_PAGING_FILE ? NonPagedPool : PagedPool;\n    USHORT defda;\n    file_ref* fileref;\n    dir_child* dc;\n    ANSI_STRING utf8as;\n    LIST_ENTRY* lastle = NULL;\n    file_ref* existing_fileref = NULL;\n#ifdef DEBUG_FCB_REFCOUNTS\n    LONG rc;\n#endif\n\n    if (parfileref->fcb == Vcb->dummy_fcb)\n        return STATUS_ACCESS_DENIED;\n\n    if (options & FILE_DIRECTORY_FILE && IrpSp->Parameters.Create.FileAttributes & FILE_ATTRIBUTE_TEMPORARY)\n        return STATUS_INVALID_PARAMETER;\n\n    Status = utf16_to_utf8(NULL, 0, &utf8len, fpus->Buffer, fpus->Length);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"utf16_to_utf8 returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    utf8 = ExAllocatePoolWithTag(pool_type, utf8len + 1, ALLOC_TAG);\n    if (!utf8) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    Status = utf16_to_utf8(utf8, utf8len, &utf8len, fpus->Buffer, fpus->Length);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"utf16_to_utf8 returned %08lx\\n\", Status);\n        ExFreePool(utf8);\n        return Status;\n    }\n\n    utf8[utf8len] = 0;\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    TRACE(\"create file %.*S\\n\", (int)(fpus->Length / sizeof(WCHAR)), fpus->Buffer);\n    ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n    TRACE(\"parfileref->fcb->inode_item.st_size (inode %I64x) was %I64x\\n\", parfileref->fcb->inode, parfileref->fcb->inode_item.st_size);\n    parfileref->fcb->inode_item.st_size += utf8len * 2;\n    TRACE(\"parfileref->fcb->inode_item.st_size (inode %I64x) now %I64x\\n\", parfileref->fcb->inode, parfileref->fcb->inode_item.st_size);\n    parfileref->fcb->inode_item.transid = Vcb->superblock.generation;\n    parfileref->fcb->inode_item.sequence++;\n    parfileref->fcb->inode_item.st_ctime = now;\n    parfileref->fcb->inode_item.st_mtime = now;\n    ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n\n    parfileref->fcb->inode_item_changed = true;\n    mark_fcb_dirty(parfileref->fcb);\n\n    inode = InterlockedIncrement64(&parfileref->fcb->subvol->lastinode);\n\n    type = options & FILE_DIRECTORY_FILE ? BTRFS_TYPE_DIRECTORY : BTRFS_TYPE_FILE;\n\n    // FIXME - link FILE_ATTRIBUTE_READONLY to st_mode\n\n    TRACE(\"requested attributes = %x\\n\", IrpSp->Parameters.Create.FileAttributes);\n\n    defda = 0;\n\n    if (utf8[0] == '.')\n        defda |= FILE_ATTRIBUTE_HIDDEN;\n\n    if (options & FILE_DIRECTORY_FILE) {\n        defda |= FILE_ATTRIBUTE_DIRECTORY;\n        IrpSp->Parameters.Create.FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;\n    } else\n        IrpSp->Parameters.Create.FileAttributes &= ~FILE_ATTRIBUTE_DIRECTORY;\n\n    if (!(IrpSp->Parameters.Create.FileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {\n        IrpSp->Parameters.Create.FileAttributes |= FILE_ATTRIBUTE_ARCHIVE;\n        defda |= FILE_ATTRIBUTE_ARCHIVE;\n    }\n\n    TRACE(\"defda = %x\\n\", defda);\n\n    if (IrpSp->Parameters.Create.FileAttributes == FILE_ATTRIBUTE_NORMAL)\n        IrpSp->Parameters.Create.FileAttributes = defda;\n\n    fcb = create_fcb(Vcb, pool_type);\n    if (!fcb) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(utf8);\n\n        ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n        parfileref->fcb->inode_item.st_size -= utf8len * 2;\n        ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    fcb->Vcb = Vcb;\n\n    if (IrpSp->Flags & SL_OPEN_PAGING_FILE)\n        fcb->Header.Flags2 |= FSRTL_FLAG2_IS_PAGING_FILE;\n\n    fcb->inode_item.generation = Vcb->superblock.generation;\n    fcb->inode_item.transid = Vcb->superblock.generation;\n    fcb->inode_item.st_size = 0;\n    fcb->inode_item.st_blocks = 0;\n    fcb->inode_item.block_group = 0;\n    fcb->inode_item.st_nlink = 1;\n    fcb->inode_item.st_gid = GID_NOBODY; // FIXME?\n    fcb->inode_item.st_mode = inherit_mode(parfileref->fcb, type == BTRFS_TYPE_DIRECTORY); // use parent's permissions by default\n    fcb->inode_item.st_rdev = 0;\n    fcb->inode_item.flags = 0;\n    fcb->inode_item.sequence = 1;\n    fcb->inode_item.st_atime = now;\n    fcb->inode_item.st_ctime = now;\n    fcb->inode_item.st_mtime = now;\n    fcb->inode_item.otime = now;\n\n    if (type == BTRFS_TYPE_DIRECTORY)\n        fcb->inode_item.st_mode |= S_IFDIR;\n    else {\n        fcb->inode_item.st_mode |= S_IFREG;\n        fcb->inode_item.st_mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH); // remove executable bit if not directory\n    }\n\n    if (IrpSp->Flags & SL_OPEN_PAGING_FILE) {\n        fcb->inode_item.flags = BTRFS_INODE_NODATACOW | BTRFS_INODE_NODATASUM | BTRFS_INODE_NOCOMPRESS;\n    } else {\n        // inherit nodatacow flag from parent directory\n        if (parfileref->fcb->inode_item.flags & BTRFS_INODE_NODATACOW || Vcb->options.nodatacow) {\n            fcb->inode_item.flags |= BTRFS_INODE_NODATACOW;\n\n            if (type != BTRFS_TYPE_DIRECTORY)\n                fcb->inode_item.flags |= BTRFS_INODE_NODATASUM;\n        }\n\n        if (parfileref->fcb->inode_item.flags & BTRFS_INODE_COMPRESS &&\n            !(fcb->inode_item.flags & BTRFS_INODE_NODATACOW)) {\n            fcb->inode_item.flags |= BTRFS_INODE_COMPRESS;\n        }\n    }\n\n    if (!(fcb->inode_item.flags & BTRFS_INODE_NODATACOW)) {\n        fcb->prop_compression = parfileref->fcb->prop_compression;\n        fcb->prop_compression_changed = fcb->prop_compression != PropCompression_None;\n    } else\n        fcb->prop_compression = PropCompression_None;\n\n    fcb->inode_item_changed = true;\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n    fcb->Header.AllocationSize.QuadPart = 0;\n    fcb->Header.FileSize.QuadPart = 0;\n    fcb->Header.ValidDataLength.QuadPart = 0;\n\n    fcb->atts = IrpSp->Parameters.Create.FileAttributes & ~FILE_ATTRIBUTE_NORMAL;\n    fcb->atts_changed = fcb->atts != defda;\n\n#ifdef DEBUG_FCB_REFCOUNTS\n    rc = InterlockedIncrement(&parfileref->fcb->refcount);\n    WARN(\"fcb %p: refcount now %i\\n\", parfileref->fcb, rc);\n#else\n    InterlockedIncrement(&parfileref->fcb->refcount);\n#endif\n    fcb->subvol = parfileref->fcb->subvol;\n    fcb->inode = inode;\n    fcb->type = type;\n    fcb->created = true;\n    fcb->deleted = true;\n\n    fcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&inode, sizeof(uint64_t));\n\n    acquire_fcb_lock_exclusive(Vcb);\n\n    if (fcb->subvol->fcbs_ptrs[fcb->hash >> 24]) {\n        LIST_ENTRY* le = fcb->subvol->fcbs_ptrs[fcb->hash >> 24];\n\n        while (le != &fcb->subvol->fcbs) {\n            struct _fcb* fcb2 = CONTAINING_RECORD(le, struct _fcb, list_entry);\n\n            if (fcb2->hash > fcb->hash) {\n                lastle = le->Blink;\n                break;\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    if (!lastle) {\n        uint8_t c = fcb->hash >> 24;\n\n        if (c != 0xff) {\n            uint8_t d = c + 1;\n\n            do {\n                if (fcb->subvol->fcbs_ptrs[d]) {\n                    lastle = fcb->subvol->fcbs_ptrs[d]->Blink;\n                    break;\n                }\n\n                d++;\n            } while (d != 0);\n        }\n    }\n\n    if (lastle) {\n        InsertHeadList(lastle, &fcb->list_entry);\n\n        if (lastle == &fcb->subvol->fcbs || (CONTAINING_RECORD(lastle, struct _fcb, list_entry)->hash >> 24) != (fcb->hash >> 24))\n            fcb->subvol->fcbs_ptrs[fcb->hash >> 24] = &fcb->list_entry;\n    } else {\n        InsertTailList(&fcb->subvol->fcbs, &fcb->list_entry);\n\n        if (fcb->list_entry.Blink == &fcb->subvol->fcbs || (CONTAINING_RECORD(fcb->list_entry.Blink, struct _fcb, list_entry)->hash >> 24) != (fcb->hash >> 24))\n            fcb->subvol->fcbs_ptrs[fcb->hash >> 24] = &fcb->list_entry;\n    }\n\n    InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all);\n\n    fcb->subvol->fcbs_version++;\n\n    release_fcb_lock(Vcb);\n\n    mark_fcb_dirty(fcb);\n\n    Status = fcb_get_new_sd(fcb, parfileref, IrpSp->Parameters.Create.SecurityContext->AccessState);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"fcb_get_new_sd returned %08lx\\n\", Status);\n        free_fcb(fcb);\n\n        ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n        parfileref->fcb->inode_item.st_size -= utf8len * 2;\n        ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n\n        ExFreePool(utf8);\n\n        return Status;\n    }\n\n    fcb->sd_dirty = true;\n\n    if (ea && ealen > 0) {\n        Status = file_create_parse_ea(fcb, ea);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"file_create_parse_ea returned %08lx\\n\", Status);\n            free_fcb(fcb);\n\n            ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n            parfileref->fcb->inode_item.st_size -= utf8len * 2;\n            ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n\n            ExFreePool(utf8);\n\n            return Status;\n        }\n    }\n\n    fileref = create_fileref(Vcb);\n    if (!fileref) {\n        ERR(\"out of memory\\n\");\n        free_fcb(fcb);\n\n        ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n        parfileref->fcb->inode_item.st_size -= utf8len * 2;\n        ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n\n        ExFreePool(utf8);\n\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    fileref->fcb = fcb;\n\n    if (Irp->Overlay.AllocationSize.QuadPart > 0 && !write_fcb_compressed(fcb) && fcb->type != BTRFS_TYPE_DIRECTORY) {\n        Status = extend_file(fcb, fileref, Irp->Overlay.AllocationSize.QuadPart, true, NULL, rollback);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"extend_file returned %08lx\\n\", Status);\n            reap_fileref(Vcb, fileref);\n\n            ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n            parfileref->fcb->inode_item.st_size -= utf8len * 2;\n            ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n\n            ExFreePool(utf8);\n\n            return Status;\n        }\n    }\n\n    if (fcb->type == BTRFS_TYPE_DIRECTORY) {\n        fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);\n        if (!fcb->hash_ptrs) {\n            ERR(\"out of memory\\n\");\n            reap_fileref(Vcb, fileref);\n\n            ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n            parfileref->fcb->inode_item.st_size -= utf8len * 2;\n            ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n\n            ExFreePool(utf8);\n\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlZeroMemory(fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256);\n\n        fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);\n        if (!fcb->hash_ptrs_uc) {\n            ERR(\"out of memory\\n\");\n            reap_fileref(Vcb, fileref);\n\n            ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n            parfileref->fcb->inode_item.st_size -= utf8len * 2;\n            ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n\n            ExFreePool(utf8);\n\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlZeroMemory(fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256);\n    }\n\n    fcb->deleted = false;\n\n    fileref->created = true;\n\n    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;\n    fcb->subvol->root_item.ctime = now;\n\n    utf8as.Buffer = utf8;\n    utf8as.Length = utf8as.MaximumLength = (uint16_t)utf8len;\n\n    ExAcquireResourceExclusiveLite(&parfileref->fcb->nonpaged->dir_children_lock, true);\n\n    // check again doesn't already exist\n    if (case_sensitive) {\n        uint32_t dc_hash = calc_crc32c(0xffffffff, (uint8_t*)fpus->Buffer, fpus->Length);\n\n        if (parfileref->fcb->hash_ptrs[dc_hash >> 24]) {\n            LIST_ENTRY* le = parfileref->fcb->hash_ptrs[dc_hash >> 24];\n            while (le != &parfileref->fcb->dir_children_hash) {\n                dc = CONTAINING_RECORD(le, dir_child, list_entry_hash);\n\n                if (dc->hash == dc_hash && dc->name.Length == fpus->Length && RtlCompareMemory(dc->name.Buffer, fpus->Buffer, fpus->Length) == fpus->Length) {\n                    existing_fileref = dc->fileref;\n                    break;\n                } else if (dc->hash > dc_hash)\n                    break;\n\n                le = le->Flink;\n            }\n        }\n    } else {\n        UNICODE_STRING fpusuc;\n\n        Status = RtlUpcaseUnicodeString(&fpusuc, fpus, true);\n        if (!NT_SUCCESS(Status)) {\n            ExReleaseResourceLite(&parfileref->fcb->nonpaged->dir_children_lock);\n            ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n            reap_fileref(Vcb, fileref);\n\n            ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n            parfileref->fcb->inode_item.st_size -= utf8len * 2;\n            ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n\n            ExFreePool(utf8);\n\n            return Status;\n        }\n\n        uint32_t dc_hash = calc_crc32c(0xffffffff, (uint8_t*)fpusuc.Buffer, fpusuc.Length);\n\n        if (parfileref->fcb->hash_ptrs_uc[dc_hash >> 24]) {\n            LIST_ENTRY* le = parfileref->fcb->hash_ptrs_uc[dc_hash >> 24];\n            while (le != &parfileref->fcb->dir_children_hash_uc) {\n                dc = CONTAINING_RECORD(le, dir_child, list_entry_hash_uc);\n\n                if (dc->hash_uc == dc_hash && dc->name.Length == fpusuc.Length && RtlCompareMemory(dc->name.Buffer, fpusuc.Buffer, fpusuc.Length) == fpusuc.Length) {\n                    existing_fileref = dc->fileref;\n                    break;\n                } else if (dc->hash_uc > dc_hash)\n                    break;\n\n                le = le->Flink;\n            }\n        }\n\n        ExFreePool(fpusuc.Buffer);\n    }\n\n    if (existing_fileref) {\n        ExReleaseResourceLite(&parfileref->fcb->nonpaged->dir_children_lock);\n        reap_fileref(Vcb, fileref);\n\n        ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n        parfileref->fcb->inode_item.st_size -= utf8len * 2;\n        ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n\n        ExFreePool(utf8);\n\n        increase_fileref_refcount(existing_fileref);\n        *pfr = existing_fileref;\n\n        return STATUS_OBJECT_NAME_COLLISION;\n    }\n\n    Status = add_dir_child(parfileref->fcb, fcb->inode, false, &utf8as, fpus, fcb->type, &dc);\n    if (!NT_SUCCESS(Status)) {\n        ExReleaseResourceLite(&parfileref->fcb->nonpaged->dir_children_lock);\n        ERR(\"add_dir_child returned %08lx\\n\", Status);\n        reap_fileref(Vcb, fileref);\n\n        ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n        parfileref->fcb->inode_item.st_size -= utf8len * 2;\n        ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n\n        ExFreePool(utf8);\n\n        return Status;\n    }\n\n    fileref->parent = parfileref;\n    fileref->dc = dc;\n    dc->fileref = fileref;\n\n    if (type == BTRFS_TYPE_DIRECTORY)\n        fileref->fcb->fileref = fileref;\n\n    InsertTailList(&parfileref->children, &fileref->list_entry);\n    ExReleaseResourceLite(&parfileref->fcb->nonpaged->dir_children_lock);\n\n    ExFreePool(utf8);\n\n    mark_fileref_dirty(fileref);\n    increase_fileref_refcount(parfileref);\n\n    *pfr = fileref;\n\n    TRACE(\"created new file in subvol %I64x, inode %I64x\\n\", fcb->subvol->id, fcb->inode);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS create_stream(_Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb,\n                              file_ref** pfileref, file_ref** pparfileref, PUNICODE_STRING fpus, PUNICODE_STRING stream, PIRP Irp,\n                              ULONG options, POOL_TYPE pool_type, bool case_sensitive, LIST_ENTRY* rollback) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    file_ref *fileref, *newpar, *parfileref;\n    fcb* fcb;\n    static const char xapref[] = \"user.\";\n    static const WCHAR DOSATTRIB[] = L\"DOSATTRIB\";\n    static const WCHAR EA[] = L\"EA\";\n    static const WCHAR reparse[] = L\"reparse\";\n    static const WCHAR casesensitive_str[] = L\"casesensitive\";\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n    ULONG utf8len, overhead;\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    dir_child* dc;\n    dir_child* existing_dc = NULL;\n    ACCESS_MASK granted_access;\n#ifdef DEBUG_FCB_REFCOUNTS\n    LONG rc;\n#endif\n\n    TRACE(\"fpus = %.*S\\n\", (int)(fpus->Length / sizeof(WCHAR)), fpus->Buffer);\n    TRACE(\"stream = %.*S\\n\", (int)(stream->Length / sizeof(WCHAR)), stream->Buffer);\n\n    parfileref = *pparfileref;\n\n    if (parfileref->fcb == Vcb->dummy_fcb)\n        return STATUS_ACCESS_DENIED;\n\n    Status = check_file_name_valid(stream, false, true);\n    if (!NT_SUCCESS(Status))\n        return Status;\n\n    Status = open_fileref(Vcb, &newpar, fpus, parfileref, false, NULL, NULL, PagedPool, case_sensitive, Irp);\n\n    if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {\n        UNICODE_STRING fpus2;\n\n        Status = check_file_name_valid(fpus, false, false);\n        if (!NT_SUCCESS(Status))\n            return Status;\n\n        fpus2.Length = fpus2.MaximumLength = fpus->Length;\n        fpus2.Buffer = ExAllocatePoolWithTag(pool_type, fpus2.MaximumLength, ALLOC_TAG);\n\n        if (!fpus2.Buffer) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(fpus2.Buffer, fpus->Buffer, fpus2.Length);\n\n        SeLockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext);\n\n        if (!SeAccessCheck(parfileref->fcb->sd, &IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext,\n                           true, options & FILE_DIRECTORY_FILE ? FILE_ADD_SUBDIRECTORY : FILE_ADD_FILE, 0, NULL,\n                           IoGetFileObjectGenericMapping(), IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode,\n                           &granted_access, &Status)) {\n            SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext);\n            return Status;\n        }\n\n        SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext);\n\n        Status = file_create2(Irp, Vcb, &fpus2, parfileref, options, NULL, 0, &newpar, case_sensitive, rollback);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"file_create2 returned %08lx\\n\", Status);\n            ExFreePool(fpus2.Buffer);\n            return Status;\n        } else if (Status != STATUS_OBJECT_NAME_COLLISION) {\n            send_notification_fileref(newpar, options & FILE_DIRECTORY_FILE ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL);\n            queue_notification_fcb(newpar->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n        }\n\n        ExFreePool(fpus2.Buffer);\n    } else if (!NT_SUCCESS(Status)) {\n        ERR(\"open_fileref returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    parfileref = newpar;\n    *pparfileref = parfileref;\n\n    if (parfileref->fcb->type != BTRFS_TYPE_FILE && parfileref->fcb->type != BTRFS_TYPE_SYMLINK && parfileref->fcb->type != BTRFS_TYPE_DIRECTORY) {\n        WARN(\"parent not file, directory, or symlink\\n\");\n        free_fileref(parfileref);\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (options & FILE_DIRECTORY_FILE) {\n        WARN(\"tried to create directory as stream\\n\");\n        free_fileref(parfileref);\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (parfileref->fcb->atts & FILE_ATTRIBUTE_READONLY && !(IrpSp->Flags & SL_IGNORE_READONLY_ATTRIBUTE)) {\n        free_fileref(parfileref);\n        return STATUS_ACCESS_DENIED;\n    }\n\n    SeLockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext);\n\n    if (!SeAccessCheck(parfileref->fcb->sd, &IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext,\n                       true, FILE_WRITE_DATA, 0, NULL, IoGetFileObjectGenericMapping(), IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode,\n                       &granted_access, &Status)) {\n        SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext);\n        free_fileref(parfileref);\n        return Status;\n    }\n\n    SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext);\n\n    if ((stream->Length == sizeof(DOSATTRIB) - sizeof(WCHAR) && RtlCompareMemory(stream->Buffer, DOSATTRIB, stream->Length) == stream->Length) ||\n        (stream->Length == sizeof(EA) - sizeof(WCHAR) && RtlCompareMemory(stream->Buffer, EA, stream->Length) == stream->Length) ||\n        (stream->Length == sizeof(reparse) - sizeof(WCHAR) && RtlCompareMemory(stream->Buffer, reparse, stream->Length) == stream->Length) ||\n        (stream->Length == sizeof(casesensitive_str) - sizeof(WCHAR) && RtlCompareMemory(stream->Buffer, casesensitive_str, stream->Length) == stream->Length)) {\n        free_fileref(parfileref);\n        return STATUS_OBJECT_NAME_INVALID;\n    }\n\n    fcb = create_fcb(Vcb, pool_type);\n    if (!fcb) {\n        ERR(\"out of memory\\n\");\n        free_fileref(parfileref);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    fcb->Vcb = Vcb;\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n    fcb->Header.AllocationSize.QuadPart = 0;\n    fcb->Header.FileSize.QuadPart = 0;\n    fcb->Header.ValidDataLength.QuadPart = 0;\n\n#ifdef DEBUG_FCB_REFCOUNTS\n    rc = InterlockedIncrement(&parfileref->fcb->refcount);\n    WARN(\"fcb %p: refcount now %i\\n\", parfileref->fcb, rc);\n#else\n    InterlockedIncrement(&parfileref->fcb->refcount);\n#endif\n    fcb->subvol = parfileref->fcb->subvol;\n    fcb->inode = parfileref->fcb->inode;\n    fcb->hash = parfileref->fcb->hash;\n    fcb->type = parfileref->fcb->type;\n\n    fcb->ads = true;\n\n    Status = utf16_to_utf8(NULL, 0, &utf8len, stream->Buffer, stream->Length);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"utf16_to_utf8 1 returned %08lx\\n\", Status);\n        reap_fcb(fcb);\n        free_fileref(parfileref);\n        return Status;\n    }\n\n    fcb->adsxattr.Length = (uint16_t)utf8len + sizeof(xapref) - 1;\n    fcb->adsxattr.MaximumLength = fcb->adsxattr.Length + 1;\n    fcb->adsxattr.Buffer = ExAllocatePoolWithTag(pool_type, fcb->adsxattr.MaximumLength, ALLOC_TAG);\n    if (!fcb->adsxattr.Buffer) {\n        ERR(\"out of memory\\n\");\n        reap_fcb(fcb);\n        free_fileref(parfileref);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(fcb->adsxattr.Buffer, xapref, sizeof(xapref) - 1);\n\n    Status = utf16_to_utf8(&fcb->adsxattr.Buffer[sizeof(xapref) - 1], utf8len, &utf8len, stream->Buffer, stream->Length);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"utf16_to_utf8 2 returned %08lx\\n\", Status);\n        reap_fcb(fcb);\n        free_fileref(parfileref);\n        return Status;\n    }\n\n    fcb->adsxattr.Buffer[fcb->adsxattr.Length] = 0;\n\n    TRACE(\"adsxattr = %s\\n\", fcb->adsxattr.Buffer);\n\n    fcb->adshash = calc_crc32c(0xfffffffe, (uint8_t*)fcb->adsxattr.Buffer, fcb->adsxattr.Length);\n    TRACE(\"adshash = %08x\\n\", fcb->adshash);\n\n    searchkey.obj_id = parfileref->fcb->inode;\n    searchkey.obj_type = TYPE_XATTR_ITEM;\n    searchkey.offset = fcb->adshash;\n\n    Status = find_item(Vcb, parfileref->fcb->subvol, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        reap_fcb(fcb);\n        free_fileref(parfileref);\n        return Status;\n    }\n\n    if (!keycmp(tp.item->key, searchkey))\n        overhead = tp.item->size;\n    else\n        overhead = 0;\n\n    fcb->adsmaxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) - (sizeof(DIR_ITEM) - 1);\n\n    if (utf8len + sizeof(xapref) - 1 + overhead > fcb->adsmaxlen) {\n        WARN(\"not enough room for new DIR_ITEM (%Iu + %lu > %lu)\\n\", utf8len + sizeof(xapref) - 1, overhead, fcb->adsmaxlen);\n        reap_fcb(fcb);\n        free_fileref(parfileref);\n        return STATUS_DISK_FULL;\n    } else\n        fcb->adsmaxlen -= overhead + utf8len + sizeof(xapref) - 1;\n\n    fcb->created = true;\n    fcb->deleted = true;\n\n    acquire_fcb_lock_exclusive(Vcb);\n    InsertHeadList(&parfileref->fcb->list_entry, &fcb->list_entry); // insert in list after parent fcb\n    InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all);\n    parfileref->fcb->subvol->fcbs_version++;\n    release_fcb_lock(Vcb);\n\n    mark_fcb_dirty(fcb);\n\n    fileref = create_fileref(Vcb);\n    if (!fileref) {\n        ERR(\"out of memory\\n\");\n        free_fcb(fcb);\n        free_fileref(parfileref);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    fileref->fcb = fcb;\n\n    dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG);\n    if (!dc) {\n        ERR(\"out of memory\\n\");\n        reap_fileref(Vcb, fileref);\n        free_fileref(parfileref);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(dc, sizeof(dir_child));\n\n    dc->utf8.MaximumLength = dc->utf8.Length = fcb->adsxattr.Length + 1 - sizeof(xapref);\n    dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, dc->utf8.MaximumLength, ALLOC_TAG);\n    if (!dc->utf8.Buffer) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(dc);\n        reap_fileref(Vcb, fileref);\n        free_fileref(parfileref);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(dc->utf8.Buffer, &fcb->adsxattr.Buffer[sizeof(xapref) - 1], fcb->adsxattr.Length + 1 - sizeof(xapref));\n\n    dc->name.MaximumLength = dc->name.Length = stream->Length;\n    dc->name.Buffer = ExAllocatePoolWithTag(pool_type, dc->name.MaximumLength, ALLOC_TAG);\n    if (!dc->name.Buffer) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(dc->utf8.Buffer);\n        ExFreePool(dc);\n        reap_fileref(Vcb, fileref);\n        free_fileref(parfileref);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(dc->name.Buffer, stream->Buffer, stream->Length);\n\n    Status = RtlUpcaseUnicodeString(&dc->name_uc, &dc->name, true);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n        ExFreePool(dc->utf8.Buffer);\n        ExFreePool(dc->name.Buffer);\n        ExFreePool(dc);\n        reap_fileref(Vcb, fileref);\n        free_fileref(parfileref);\n        return Status;\n    }\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    ExAcquireResourceExclusiveLite(&parfileref->fcb->nonpaged->dir_children_lock, true);\n\n    LIST_ENTRY* le = parfileref->fcb->dir_children_index.Flink;\n    while (le != &parfileref->fcb->dir_children_index) {\n        dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_index);\n\n        if (dc2->index == 0) {\n            if ((case_sensitive && dc2->name.Length == dc->name.Length && RtlCompareMemory(dc2->name.Buffer, dc->name.Buffer, dc2->name.Length) == dc2->name.Length) ||\n                (!case_sensitive && dc2->name_uc.Length == dc->name_uc.Length && RtlCompareMemory(dc2->name_uc.Buffer, dc->name_uc.Buffer, dc2->name_uc.Length) == dc2->name_uc.Length)\n            ) {\n                existing_dc = dc2;\n                break;\n            }\n        } else\n            break;\n\n        le = le->Flink;\n    }\n\n    if (existing_dc) {\n        ExFreePool(dc->utf8.Buffer);\n        ExFreePool(dc->name.Buffer);\n        ExFreePool(dc);\n        reap_fileref(Vcb, fileref);\n        free_fileref(parfileref);\n\n        increase_fileref_refcount(existing_dc->fileref);\n        *pfileref = existing_dc->fileref;\n\n        return STATUS_OBJECT_NAME_COLLISION;\n    }\n\n    dc->fileref = fileref;\n    fileref->dc = dc;\n    fileref->parent = (struct _file_ref*)parfileref;\n    fcb->deleted = false;\n\n    InsertHeadList(&parfileref->fcb->dir_children_index, &dc->list_entry_index);\n\n    InsertTailList(&parfileref->children, &fileref->list_entry);\n\n    ExReleaseResourceLite(&parfileref->fcb->nonpaged->dir_children_lock);\n\n    mark_fileref_dirty(fileref);\n\n    parfileref->fcb->inode_item.transid = Vcb->superblock.generation;\n    parfileref->fcb->inode_item.sequence++;\n    parfileref->fcb->inode_item.st_ctime = now;\n    parfileref->fcb->inode_item_changed = true;\n\n    mark_fcb_dirty(parfileref->fcb);\n\n    parfileref->fcb->subvol->root_item.ctransid = Vcb->superblock.generation;\n    parfileref->fcb->subvol->root_item.ctime = now;\n\n    increase_fileref_refcount(parfileref);\n\n    *pfileref = fileref;\n\n    send_notification_fileref(parfileref, FILE_NOTIFY_CHANGE_STREAM_NAME, FILE_ACTION_ADDED_STREAM, &fileref->dc->name);\n\n    return STATUS_SUCCESS;\n}\n\n// LXSS programs can be distinguished by the fact they have a NULL PEB.\n#ifdef _AMD64_\nstatic __inline bool called_from_lxss() {\n    NTSTATUS Status;\n    PROCESS_BASIC_INFORMATION pbi;\n    ULONG retlen;\n\n    Status = ZwQueryInformationProcess(NtCurrentProcess(), ProcessBasicInformation, &pbi, sizeof(pbi), &retlen);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ZwQueryInformationProcess returned %08lx\\n\", Status);\n        return false;\n    }\n\n    return !pbi.PebBaseAddress;\n}\n#else\n#define called_from_lxss() false\n#endif\n\nstatic NTSTATUS file_create(PIRP Irp, _Requires_lock_held_(_Curr_->tree_lock) _Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb,\n                            PFILE_OBJECT FileObject, file_ref* related, bool loaded_related, PUNICODE_STRING fnus, ULONG disposition, ULONG options,\n                            file_ref** existing_fileref, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    file_ref *fileref, *parfileref = NULL;\n    ULONG i, j;\n    ccb* ccb;\n    static const WCHAR datasuf[] = {':','$','D','A','T','A',0};\n    UNICODE_STRING dsus, fpus, stream;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    POOL_TYPE pool_type = IrpSp->Flags & SL_OPEN_PAGING_FILE ? NonPagedPool : PagedPool;\n    ECP_LIST* ecp_list;\n    ATOMIC_CREATE_ECP_CONTEXT* acec = NULL;\n#ifdef DEBUG_FCB_REFCOUNTS\n    LONG oc;\n#endif\n\n    TRACE(\"(%p, %p, %p, %.*S, %lx, %lx)\\n\", Irp, Vcb, FileObject, (int)(fnus->Length / sizeof(WCHAR)), fnus->Buffer, disposition, options);\n\n    if (Vcb->readonly)\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n    if (options & FILE_DELETE_ON_CLOSE && IrpSp->Parameters.Create.FileAttributes & FILE_ATTRIBUTE_READONLY &&\n        !(IrpSp->Flags & SL_IGNORE_READONLY_ATTRIBUTE)) {\n        return STATUS_CANNOT_DELETE;\n    }\n\n    if (fFsRtlGetEcpListFromIrp && fFsRtlGetNextExtraCreateParameter) {\n        if (NT_SUCCESS(fFsRtlGetEcpListFromIrp(Irp, &ecp_list)) && ecp_list) {\n            void* ctx = NULL;\n            GUID type;\n            ULONG ctxsize;\n\n            do {\n                Status = fFsRtlGetNextExtraCreateParameter(ecp_list, ctx, &type, &ctx, &ctxsize);\n\n                if (NT_SUCCESS(Status)) {\n                    if (RtlCompareMemory(&type, &GUID_ECP_ATOMIC_CREATE, sizeof(GUID)) == sizeof(GUID)) {\n                        if (ctxsize >= sizeof(ATOMIC_CREATE_ECP_CONTEXT))\n                            acec = ctx;\n                        else {\n                            ERR(\"GUID_ECP_ATOMIC_CREATE context was too short: %lu bytes, expected %Iu\\n\", ctxsize,\n                                sizeof(ATOMIC_CREATE_ECP_CONTEXT));\n                        }\n                    } else if (RtlCompareMemory(&type, &GUID_ECP_QUERY_ON_CREATE, sizeof(GUID)) == sizeof(GUID))\n                        WARN(\"unhandled ECP GUID_ECP_QUERY_ON_CREATE\\n\");\n                    else if (RtlCompareMemory(&type, &GUID_ECP_CREATE_REDIRECTION, sizeof(GUID)) == sizeof(GUID))\n                        WARN(\"unhandled ECP GUID_ECP_CREATE_REDIRECTION\\n\");\n                    else {\n                        WARN(\"unhandled ECP {%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}\\n\", type.Data1, type.Data2,\n                             type.Data3, type.Data4[0], type.Data4[1], type.Data4[2], type.Data4[3], type.Data4[4], type.Data4[5],\n                             type.Data4[6], type.Data4[7]);\n                    }\n                }\n            } while (NT_SUCCESS(Status));\n        }\n    }\n\n    dsus.Buffer = (WCHAR*)datasuf;\n    dsus.Length = dsus.MaximumLength = sizeof(datasuf) - sizeof(WCHAR);\n    fpus.Buffer = NULL;\n\n    if (!loaded_related) {\n        Status = open_fileref(Vcb, &parfileref, fnus, related, true, NULL, NULL, pool_type, IrpSp->Flags & SL_CASE_SENSITIVE, Irp);\n\n        if (!NT_SUCCESS(Status))\n            goto end;\n    } else\n        parfileref = related;\n\n    if (parfileref->fcb->type != BTRFS_TYPE_DIRECTORY && (fnus->Length < sizeof(WCHAR) || fnus->Buffer[0] != ':')) {\n        Status = STATUS_OBJECT_PATH_NOT_FOUND;\n        goto end;\n    }\n\n    if (is_subvol_readonly(parfileref->fcb->subvol, Irp)) {\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    i = (fnus->Length / sizeof(WCHAR))-1;\n    while ((fnus->Buffer[i] == '\\\\' || fnus->Buffer[i] == '/') && i > 0) { i--; }\n\n    j = i;\n\n    while (i > 0 && fnus->Buffer[i-1] != '\\\\' && fnus->Buffer[i-1] != '/') { i--; }\n\n    fpus.MaximumLength = (USHORT)((j - i + 2) * sizeof(WCHAR));\n    fpus.Buffer = ExAllocatePoolWithTag(pool_type, fpus.MaximumLength, ALLOC_TAG);\n    if (!fpus.Buffer) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    fpus.Length = (USHORT)((j - i + 1) * sizeof(WCHAR));\n\n    RtlCopyMemory(fpus.Buffer, &fnus->Buffer[i], (j - i + 1) * sizeof(WCHAR));\n    fpus.Buffer[j - i + 1] = 0;\n\n    if (fpus.Length > dsus.Length) { // check for :$DATA suffix\n        UNICODE_STRING lb;\n\n        lb.Buffer = &fpus.Buffer[(fpus.Length - dsus.Length)/sizeof(WCHAR)];\n        lb.Length = lb.MaximumLength = dsus.Length;\n\n        TRACE(\"lb = %.*S\\n\", (int)(lb.Length/sizeof(WCHAR)), lb.Buffer);\n\n        if (FsRtlAreNamesEqual(&dsus, &lb, true, NULL)) {\n            TRACE(\"ignoring :$DATA suffix\\n\");\n\n            fpus.Length -= lb.Length;\n\n            if (fpus.Length > sizeof(WCHAR) && fpus.Buffer[(fpus.Length-1)/sizeof(WCHAR)] == ':')\n                fpus.Length -= sizeof(WCHAR);\n\n            TRACE(\"fpus = %.*S\\n\", (int)(fpus.Length / sizeof(WCHAR)), fpus.Buffer);\n        }\n    }\n\n    stream.Length = 0;\n\n    for (i = 0; i < fpus.Length / sizeof(WCHAR); i++) {\n        if (fpus.Buffer[i] == ':') {\n            stream.Length = (USHORT)(fpus.Length - (i * sizeof(WCHAR)) - sizeof(WCHAR));\n            stream.Buffer = &fpus.Buffer[i+1];\n            fpus.Buffer[i] = 0;\n            fpus.Length = (USHORT)(i * sizeof(WCHAR));\n            break;\n        }\n    }\n\n    if (stream.Length > 0) {\n        Status = create_stream(Vcb, &fileref, &parfileref, &fpus, &stream, Irp, options, pool_type, IrpSp->Flags & SL_CASE_SENSITIVE, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"create_stream returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        IoSetShareAccess(IrpSp->Parameters.Create.SecurityContext->DesiredAccess, IrpSp->Parameters.Create.ShareAccess,\n                         FileObject, &fileref->fcb->share_access);\n    } else {\n        ACCESS_MASK granted_access;\n\n        Status = check_file_name_valid(&fpus, false, false);\n        if (!NT_SUCCESS(Status))\n            goto end;\n\n        SeLockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext);\n\n        if (!SeAccessCheck(parfileref->fcb->sd, &IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext,\n                           true, options & FILE_DIRECTORY_FILE ? FILE_ADD_SUBDIRECTORY : FILE_ADD_FILE, 0, NULL,\n                           IoGetFileObjectGenericMapping(), IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode,\n                           &granted_access, &Status)) {\n            SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext);\n            goto end;\n        }\n\n        SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext);\n\n        if (Irp->AssociatedIrp.SystemBuffer && IrpSp->Parameters.Create.EaLength > 0) {\n            ULONG offset;\n\n            Status = IoCheckEaBufferValidity(Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.Create.EaLength, &offset);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"IoCheckEaBufferValidity returned %08lx (error at offset %lu)\\n\", Status, offset);\n                goto end;\n            }\n        }\n\n        Status = file_create2(Irp, Vcb, &fpus, parfileref, options, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.Create.EaLength,\n                              &fileref, IrpSp->Flags & SL_CASE_SENSITIVE, rollback);\n\n        if (Status == STATUS_OBJECT_NAME_COLLISION) {\n            *existing_fileref = fileref;\n            goto end;\n        } else if (!NT_SUCCESS(Status)) {\n            ERR(\"file_create2 returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        IoSetShareAccess(IrpSp->Parameters.Create.SecurityContext->DesiredAccess, IrpSp->Parameters.Create.ShareAccess, FileObject, &fileref->fcb->share_access);\n\n        send_notification_fileref(fileref, options & FILE_DIRECTORY_FILE ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL);\n        queue_notification_fcb(fileref->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n    }\n\n    FileObject->FsContext = fileref->fcb;\n\n    ccb = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ccb), ALLOC_TAG);\n    if (!ccb) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        fileref->deleted = true;\n        fileref->fcb->deleted = true;\n\n        if (stream.Length == 0) {\n            ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n            parfileref->fcb->inode_item.st_size -= fileref->dc->utf8.Length * 2;\n            ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n        }\n\n        free_fileref(fileref);\n        goto end;\n    }\n\n    RtlZeroMemory(ccb, sizeof(*ccb));\n\n    ccb->fileref = fileref;\n\n    ccb->NodeType = BTRFS_NODE_TYPE_CCB;\n    ccb->NodeSize = sizeof(*ccb);\n    ccb->disposition = disposition;\n    ccb->options = options;\n    ccb->query_dir_offset = 0;\n    RtlInitUnicodeString(&ccb->query_string, NULL);\n    ccb->has_wildcard = false;\n    ccb->specific_file = false;\n    ccb->access = IrpSp->Parameters.Create.SecurityContext->DesiredAccess;\n    ccb->case_sensitive = IrpSp->Flags & SL_CASE_SENSITIVE;\n    ccb->reserving = false;\n    ccb->lxss = called_from_lxss();\n\n#ifdef DEBUG_FCB_REFCOUNTS\n    oc = InterlockedIncrement(&fileref->open_count);\n    ERR(\"fileref %p: open_count now %i\\n\", fileref, oc);\n#else\n    InterlockedIncrement(&fileref->open_count);\n#endif\n    InterlockedIncrement(&Vcb->open_files);\n\n    FileObject->FsContext2 = ccb;\n\n    FileObject->SectionObjectPointer = &fileref->fcb->nonpaged->segment_object;\n\n    // FIXME - ATOMIC_CREATE_ECP_IN_FLAG_BEST_EFFORT\n    if (acec && acec->InFlags & ATOMIC_CREATE_ECP_IN_FLAG_REPARSE_POINT_SPECIFIED) {\n        if (acec->ReparseBufferLength > sizeof(uint32_t) && *(uint32_t*)acec->ReparseBuffer == IO_REPARSE_TAG_SYMLINK) {\n            fileref->fcb->inode_item.st_mode &= ~(__S_IFIFO | __S_IFCHR | __S_IFBLK | __S_IFSOCK);\n            fileref->fcb->type = BTRFS_TYPE_FILE;\n            fileref->fcb->atts &= ~FILE_ATTRIBUTE_DIRECTORY;\n        }\n\n        if (fileref->fcb->type == BTRFS_TYPE_SOCKET || fileref->fcb->type == BTRFS_TYPE_FIFO ||\n            fileref->fcb->type == BTRFS_TYPE_CHARDEV || fileref->fcb->type == BTRFS_TYPE_BLOCKDEV) {\n            // NOP. If called from LXSS, humour it - we hardcode the values elsewhere.\n        } else {\n            Status = set_reparse_point2(fileref->fcb, acec->ReparseBuffer, acec->ReparseBufferLength, NULL, NULL, Irp, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"set_reparse_point2 returned %08lx\\n\", Status);\n                fileref->deleted = true;\n                fileref->fcb->deleted = true;\n\n                if (stream.Length == 0) {\n                    ExAcquireResourceExclusiveLite(parfileref->fcb->Header.Resource, true);\n                    parfileref->fcb->inode_item.st_size -= fileref->dc->utf8.Length * 2;\n                    ExReleaseResourceLite(parfileref->fcb->Header.Resource);\n                }\n\n                free_fileref(fileref);\n                return Status;\n            }\n        }\n\n        acec->OutFlags |= ATOMIC_CREATE_ECP_OUT_FLAG_REPARSE_POINT_SET;\n    }\n\n    if (acec && acec->InFlags & ATOMIC_CREATE_ECP_IN_FLAG_OP_FLAGS_SPECIFIED) {\n        if (acec->InOpFlags & ATOMIC_CREATE_ECP_IN_OP_FLAG_CASE_SENSITIVE_FLAGS_SPECIFIED && fileref->fcb->atts & FILE_ATTRIBUTE_DIRECTORY) {\n            if ((acec->InCaseSensitiveFlags & acec->CaseSensitiveFlagsMask) & FILE_CS_FLAG_CASE_SENSITIVE_DIR) {\n                acec->OutCaseSensitiveFlags = FILE_CS_FLAG_CASE_SENSITIVE_DIR;\n                fileref->fcb->case_sensitive = true;\n                ccb->case_sensitive = true;\n            }\n\n            acec->OutOpFlags |= ATOMIC_CREATE_ECP_OUT_OP_FLAG_CASE_SENSITIVE_FLAGS_SET;\n        }\n\n        acec->OutFlags |= ATOMIC_CREATE_ECP_OUT_FLAG_OP_FLAGS_HONORED;\n    }\n\n    fileref->dc->type = fileref->fcb->type;\n\nend:\n    if (fpus.Buffer)\n        ExFreePool(fpus.Buffer);\n\n    if (parfileref && !loaded_related)\n        free_fileref(parfileref);\n\n    return Status;\n}\n\nstatic __inline void debug_create_options(ULONG RequestedOptions) {\n    if (RequestedOptions != 0) {\n        ULONG options = RequestedOptions;\n\n        TRACE(\"requested options:\\n\");\n\n        if (options & FILE_DIRECTORY_FILE) {\n            TRACE(\"    FILE_DIRECTORY_FILE\\n\");\n            options &= ~FILE_DIRECTORY_FILE;\n        }\n\n        if (options & FILE_WRITE_THROUGH) {\n            TRACE(\"    FILE_WRITE_THROUGH\\n\");\n            options &= ~FILE_WRITE_THROUGH;\n        }\n\n        if (options & FILE_SEQUENTIAL_ONLY) {\n            TRACE(\"    FILE_SEQUENTIAL_ONLY\\n\");\n            options &= ~FILE_SEQUENTIAL_ONLY;\n        }\n\n        if (options & FILE_NO_INTERMEDIATE_BUFFERING) {\n            TRACE(\"    FILE_NO_INTERMEDIATE_BUFFERING\\n\");\n            options &= ~FILE_NO_INTERMEDIATE_BUFFERING;\n        }\n\n        if (options & FILE_SYNCHRONOUS_IO_ALERT) {\n            TRACE(\"    FILE_SYNCHRONOUS_IO_ALERT\\n\");\n            options &= ~FILE_SYNCHRONOUS_IO_ALERT;\n        }\n\n        if (options & FILE_SYNCHRONOUS_IO_NONALERT) {\n            TRACE(\"    FILE_SYNCHRONOUS_IO_NONALERT\\n\");\n            options &= ~FILE_SYNCHRONOUS_IO_NONALERT;\n        }\n\n        if (options & FILE_NON_DIRECTORY_FILE) {\n            TRACE(\"    FILE_NON_DIRECTORY_FILE\\n\");\n            options &= ~FILE_NON_DIRECTORY_FILE;\n        }\n\n        if (options & FILE_CREATE_TREE_CONNECTION) {\n            TRACE(\"    FILE_CREATE_TREE_CONNECTION\\n\");\n            options &= ~FILE_CREATE_TREE_CONNECTION;\n        }\n\n        if (options & FILE_COMPLETE_IF_OPLOCKED) {\n            TRACE(\"    FILE_COMPLETE_IF_OPLOCKED\\n\");\n            options &= ~FILE_COMPLETE_IF_OPLOCKED;\n        }\n\n        if (options & FILE_NO_EA_KNOWLEDGE) {\n            TRACE(\"    FILE_NO_EA_KNOWLEDGE\\n\");\n            options &= ~FILE_NO_EA_KNOWLEDGE;\n        }\n\n        if (options & FILE_OPEN_REMOTE_INSTANCE) {\n            TRACE(\"    FILE_OPEN_REMOTE_INSTANCE\\n\");\n            options &= ~FILE_OPEN_REMOTE_INSTANCE;\n        }\n\n        if (options & FILE_RANDOM_ACCESS) {\n            TRACE(\"    FILE_RANDOM_ACCESS\\n\");\n            options &= ~FILE_RANDOM_ACCESS;\n        }\n\n        if (options & FILE_DELETE_ON_CLOSE) {\n            TRACE(\"    FILE_DELETE_ON_CLOSE\\n\");\n            options &= ~FILE_DELETE_ON_CLOSE;\n        }\n\n        if (options & FILE_OPEN_BY_FILE_ID) {\n            TRACE(\"    FILE_OPEN_BY_FILE_ID\\n\");\n            options &= ~FILE_OPEN_BY_FILE_ID;\n        }\n\n        if (options & FILE_OPEN_FOR_BACKUP_INTENT) {\n            TRACE(\"    FILE_OPEN_FOR_BACKUP_INTENT\\n\");\n            options &= ~FILE_OPEN_FOR_BACKUP_INTENT;\n        }\n\n        if (options & FILE_NO_COMPRESSION) {\n            TRACE(\"    FILE_NO_COMPRESSION\\n\");\n            options &= ~FILE_NO_COMPRESSION;\n        }\n\n#if NTDDI_VERSION >= NTDDI_WIN7\n        if (options & FILE_OPEN_REQUIRING_OPLOCK) {\n            TRACE(\"    FILE_OPEN_REQUIRING_OPLOCK\\n\");\n            options &= ~FILE_OPEN_REQUIRING_OPLOCK;\n        }\n\n        if (options & FILE_DISALLOW_EXCLUSIVE) {\n            TRACE(\"    FILE_DISALLOW_EXCLUSIVE\\n\");\n            options &= ~FILE_DISALLOW_EXCLUSIVE;\n        }\n#endif\n\n        if (options & FILE_RESERVE_OPFILTER) {\n            TRACE(\"    FILE_RESERVE_OPFILTER\\n\");\n            options &= ~FILE_RESERVE_OPFILTER;\n        }\n\n        if (options & FILE_OPEN_REPARSE_POINT) {\n            TRACE(\"    FILE_OPEN_REPARSE_POINT\\n\");\n            options &= ~FILE_OPEN_REPARSE_POINT;\n        }\n\n        if (options & FILE_OPEN_NO_RECALL) {\n            TRACE(\"    FILE_OPEN_NO_RECALL\\n\");\n            options &= ~FILE_OPEN_NO_RECALL;\n        }\n\n        if (options & FILE_OPEN_FOR_FREE_SPACE_QUERY) {\n            TRACE(\"    FILE_OPEN_FOR_FREE_SPACE_QUERY\\n\");\n            options &= ~FILE_OPEN_FOR_FREE_SPACE_QUERY;\n        }\n\n        if (options)\n            TRACE(\"    unknown options: %lx\\n\", options);\n    } else {\n        TRACE(\"requested options: (none)\\n\");\n    }\n}\n\nstatic NTSTATUS get_reparse_block(fcb* fcb, uint8_t** data) {\n    NTSTATUS Status;\n\n    if (fcb->type == BTRFS_TYPE_FILE || fcb->type == BTRFS_TYPE_SYMLINK) {\n        ULONG size, bytes_read, i;\n\n        if (fcb->type == BTRFS_TYPE_FILE && fcb->inode_item.st_size < sizeof(ULONG)) {\n            WARN(\"file was too short to be a reparse point\\n\");\n            return STATUS_INVALID_PARAMETER;\n        }\n\n        // 0x10007 = 0xffff (maximum length of data buffer) + 8 bytes header\n        size = (ULONG)min(0x10007, fcb->inode_item.st_size);\n\n        if (size == 0)\n            return STATUS_INVALID_PARAMETER;\n\n        *data = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG);\n        if (!*data) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        Status = read_file(fcb, *data, 0, size, &bytes_read, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"read_file_fcb returned %08lx\\n\", Status);\n            ExFreePool(*data);\n            return Status;\n        }\n\n        if (fcb->type == BTRFS_TYPE_SYMLINK) {\n            ULONG stringlen, reqlen;\n            uint16_t subnamelen, printnamelen;\n            REPARSE_DATA_BUFFER* rdb;\n\n            Status = utf8_to_utf16(NULL, 0, &stringlen, (char*)*data, bytes_read);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"utf8_to_utf16 1 returned %08lx\\n\", Status);\n                ExFreePool(*data);\n                return Status;\n            }\n\n            subnamelen = printnamelen = (USHORT)stringlen;\n\n            reqlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + subnamelen + printnamelen;\n\n            rdb = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG);\n\n            if (!rdb) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(*data);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            rdb->ReparseTag = IO_REPARSE_TAG_SYMLINK;\n            rdb->ReparseDataLength = (USHORT)(reqlen - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer));\n            rdb->Reserved = 0;\n\n            rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;\n            rdb->SymbolicLinkReparseBuffer.SubstituteNameLength = subnamelen;\n            rdb->SymbolicLinkReparseBuffer.PrintNameOffset = subnamelen;\n            rdb->SymbolicLinkReparseBuffer.PrintNameLength = printnamelen;\n            rdb->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE;\n\n            Status = utf8_to_utf16(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],\n                                    stringlen, &stringlen, (char*)*data, size);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"utf8_to_utf16 2 returned %08lx\\n\", Status);\n                ExFreePool(rdb);\n                ExFreePool(*data);\n                return Status;\n            }\n\n            for (i = 0; i < stringlen / sizeof(WCHAR); i++) {\n                if (rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] == '/')\n                    rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] = '\\\\';\n            }\n\n            RtlCopyMemory(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)],\n                        &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],\n                        rdb->SymbolicLinkReparseBuffer.SubstituteNameLength);\n\n            ExFreePool(*data);\n\n            *data = (uint8_t*)rdb;\n        } else {\n            Status = fFsRtlValidateReparsePointBuffer(bytes_read, (REPARSE_DATA_BUFFER*)*data);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"FsRtlValidateReparsePointBuffer returned %08lx\\n\", Status);\n                ExFreePool(*data);\n                return Status;\n            }\n        }\n    } else if (fcb->type == BTRFS_TYPE_DIRECTORY) {\n        if (!fcb->reparse_xattr.Buffer || fcb->reparse_xattr.Length == 0)\n            return STATUS_INTERNAL_ERROR;\n\n        if (fcb->reparse_xattr.Length < sizeof(ULONG)) {\n            WARN(\"xattr was too short to be a reparse point\\n\");\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        Status = fFsRtlValidateReparsePointBuffer(fcb->reparse_xattr.Length, (REPARSE_DATA_BUFFER*)fcb->reparse_xattr.Buffer);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"FsRtlValidateReparsePointBuffer returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        *data = ExAllocatePoolWithTag(PagedPool, fcb->reparse_xattr.Length, ALLOC_TAG);\n        if (!*data) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(*data, fcb->reparse_xattr.Buffer, fcb->reparse_xattr.Length);\n    } else\n        return STATUS_INVALID_PARAMETER;\n\n    return STATUS_SUCCESS;\n}\n\nstatic void fcb_load_csums(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, fcb* fcb, PIRP Irp) {\n    LIST_ENTRY* le;\n    NTSTATUS Status;\n\n    if (fcb->csum_loaded)\n        return;\n\n    if (IsListEmpty(&fcb->extents) || fcb->inode_item.flags & BTRFS_INODE_NODATASUM)\n        goto end;\n\n    le = fcb->extents.Flink;\n    while (le != &fcb->extents) {\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if (!ext->ignore && ext->extent_data.type == EXTENT_TYPE_REGULAR) {\n            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ext->extent_data.data[0];\n            uint64_t len;\n\n            len = (ext->extent_data.compression == BTRFS_COMPRESSION_NONE ? ed2->num_bytes : ed2->size) >> Vcb->sector_shift;\n\n            ext->csum = ExAllocatePoolWithTag(NonPagedPool, (ULONG)(len * Vcb->csum_size), ALLOC_TAG);\n            if (!ext->csum) {\n                ERR(\"out of memory\\n\");\n                goto end;\n            }\n\n            Status = load_csum(Vcb, ext->csum, ed2->address + (ext->extent_data.compression == BTRFS_COMPRESSION_NONE ? ed2->offset : 0), len, Irp);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"load_csum returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        le = le->Flink;\n    }\n\nend:\n    fcb->csum_loaded = true;\n}\n\nstatic NTSTATUS open_file3(device_extension* Vcb, PIRP Irp, ACCESS_MASK granted_access, file_ref* fileref, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    ULONG options = IrpSp->Parameters.Create.Options & FILE_VALID_OPTION_FLAGS;\n    ULONG RequestedDisposition = ((IrpSp->Parameters.Create.Options >> 24) & 0xff);\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    POOL_TYPE pool_type = IrpSp->Flags & SL_OPEN_PAGING_FILE ? NonPagedPool : PagedPool;\n    ccb* ccb;\n\n    if (granted_access & FILE_WRITE_DATA || options & FILE_DELETE_ON_CLOSE) {\n        if (!MmFlushImageSection(&fileref->fcb->nonpaged->segment_object, MmFlushForWrite))\n            return (options & FILE_DELETE_ON_CLOSE) ? STATUS_CANNOT_DELETE : STATUS_SHARING_VIOLATION;\n    }\n\n    if (RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF || RequestedDisposition == FILE_SUPERSEDE) {\n        ULONG defda, oldatts, filter;\n        LARGE_INTEGER time;\n        BTRFS_TIME now;\n\n        if (!fileref->fcb->ads && (IrpSp->Parameters.Create.FileAttributes & (FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM)) != ((fileref->fcb->atts & (FILE_ATTRIBUTE_SYSTEM | FILE_ATTRIBUTE_HIDDEN))))\n            return STATUS_ACCESS_DENIED;\n\n        if (fileref->fcb->ads) {\n            Status = stream_set_end_of_file_information(Vcb, 0, fileref->fcb, fileref, false);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"stream_set_end_of_file_information returned %08lx\\n\", Status);\n                return Status;\n            }\n        } else {\n            Status = truncate_file(fileref->fcb, 0, Irp, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"truncate_file returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        if (Irp->Overlay.AllocationSize.QuadPart > 0) {\n            Status = extend_file(fileref->fcb, fileref, Irp->Overlay.AllocationSize.QuadPart, true, NULL, rollback);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"extend_file returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        if (!fileref->fcb->ads) {\n            LIST_ENTRY* le;\n\n            if (Irp->AssociatedIrp.SystemBuffer && IrpSp->Parameters.Create.EaLength > 0) {\n                ULONG offset;\n                FILE_FULL_EA_INFORMATION* eainfo;\n\n                Status = IoCheckEaBufferValidity(Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.Create.EaLength, &offset);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"IoCheckEaBufferValidity returned %08lx (error at offset %lu)\\n\", Status, offset);\n                    return Status;\n                }\n\n                fileref->fcb->ealen = 4;\n\n                // capitalize EA name\n                eainfo = Irp->AssociatedIrp.SystemBuffer;\n                do {\n                    STRING s;\n\n                    s.Length = s.MaximumLength = eainfo->EaNameLength;\n                    s.Buffer = eainfo->EaName;\n\n                    RtlUpperString(&s, &s);\n\n                    fileref->fcb->ealen += 5 + eainfo->EaNameLength + eainfo->EaValueLength;\n\n                    if (eainfo->NextEntryOffset == 0)\n                        break;\n\n                    eainfo = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)eainfo) + eainfo->NextEntryOffset);\n                } while (true);\n\n                if (fileref->fcb->ea_xattr.Buffer)\n                    ExFreePool(fileref->fcb->ea_xattr.Buffer);\n\n                fileref->fcb->ea_xattr.Buffer = ExAllocatePoolWithTag(pool_type, IrpSp->Parameters.Create.EaLength, ALLOC_TAG);\n                if (!fileref->fcb->ea_xattr.Buffer) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                fileref->fcb->ea_xattr.Length = fileref->fcb->ea_xattr.MaximumLength = (USHORT)IrpSp->Parameters.Create.EaLength;\n                RtlCopyMemory(fileref->fcb->ea_xattr.Buffer, Irp->AssociatedIrp.SystemBuffer, fileref->fcb->ea_xattr.Length);\n            } else {\n                if (fileref->fcb->ea_xattr.Length > 0) {\n                    ExFreePool(fileref->fcb->ea_xattr.Buffer);\n                    fileref->fcb->ea_xattr.Buffer = NULL;\n                    fileref->fcb->ea_xattr.Length = fileref->fcb->ea_xattr.MaximumLength = 0;\n\n                    fileref->fcb->ea_changed = true;\n                    fileref->fcb->ealen = 0;\n                }\n            }\n\n            // remove streams and send notifications\n            le = fileref->fcb->dir_children_index.Flink;\n            while (le != &fileref->fcb->dir_children_index) {\n                dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index);\n                LIST_ENTRY* le2 = le->Flink;\n\n                if (dc->index == 0) {\n                    if (!dc->fileref) {\n                        file_ref* fr2;\n\n                        Status = open_fileref_child(Vcb, fileref, &dc->name, true, true, true, PagedPool, &fr2, NULL);\n                        if (!NT_SUCCESS(Status))\n                            WARN(\"open_fileref_child returned %08lx\\n\", Status);\n                    }\n\n                    if (dc->fileref) {\n                        queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_STREAM_NAME, FILE_ACTION_REMOVED_STREAM, &dc->name);\n\n                        Status = delete_fileref(dc->fileref, NULL, false, NULL, rollback);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"delete_fileref returned %08lx\\n\", Status);\n                            return Status;\n                        }\n                    }\n                } else\n                    break;\n\n                le = le2;\n            }\n        }\n\n        KeQuerySystemTime(&time);\n        win_time_to_unix(time, &now);\n\n        filter = FILE_NOTIFY_CHANGE_SIZE | FILE_NOTIFY_CHANGE_LAST_WRITE;\n\n        if (fileref->fcb->ads) {\n            fileref->parent->fcb->inode_item.st_mtime = now;\n            fileref->parent->fcb->inode_item_changed = true;\n            mark_fcb_dirty(fileref->parent->fcb);\n\n            queue_notification_fcb(fileref->parent, filter, FILE_ACTION_MODIFIED, &fileref->dc->name);\n        } else {\n            mark_fcb_dirty(fileref->fcb);\n\n            oldatts = fileref->fcb->atts;\n\n            defda = get_file_attributes(Vcb, fileref->fcb->subvol, fileref->fcb->inode, fileref->fcb->type,\n                                        fileref->dc && fileref->dc->name.Length >= sizeof(WCHAR) && fileref->dc->name.Buffer[0] == '.', true, Irp);\n\n            if (RequestedDisposition == FILE_SUPERSEDE)\n                fileref->fcb->atts = IrpSp->Parameters.Create.FileAttributes | FILE_ATTRIBUTE_ARCHIVE;\n            else\n                fileref->fcb->atts |= IrpSp->Parameters.Create.FileAttributes | FILE_ATTRIBUTE_ARCHIVE;\n\n            if (fileref->fcb->atts != oldatts) {\n                fileref->fcb->atts_changed = true;\n                fileref->fcb->atts_deleted = IrpSp->Parameters.Create.FileAttributes == defda;\n                filter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;\n            }\n\n            fileref->fcb->inode_item.transid = Vcb->superblock.generation;\n            fileref->fcb->inode_item.sequence++;\n            fileref->fcb->inode_item.st_ctime = now;\n            fileref->fcb->inode_item.st_mtime = now;\n            fileref->fcb->inode_item_changed = true;\n\n            queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL);\n        }\n    } else {\n        if (options & FILE_NO_EA_KNOWLEDGE && fileref->fcb->ea_xattr.Length > 0) {\n            FILE_FULL_EA_INFORMATION* ffei = (FILE_FULL_EA_INFORMATION*)fileref->fcb->ea_xattr.Buffer;\n\n            do {\n                if (ffei->Flags & FILE_NEED_EA) {\n                    WARN(\"returning STATUS_ACCESS_DENIED as no EA knowledge\\n\");\n\n                    return STATUS_ACCESS_DENIED;\n                }\n\n                if (ffei->NextEntryOffset == 0)\n                    break;\n\n                ffei = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ffei) + ffei->NextEntryOffset);\n            } while (true);\n        }\n    }\n\n    FileObject->FsContext = fileref->fcb;\n\n    ccb = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ccb), ALLOC_TAG);\n    if (!ccb) {\n        ERR(\"out of memory\\n\");\n\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(ccb, sizeof(*ccb));\n\n    ccb->NodeType = BTRFS_NODE_TYPE_CCB;\n    ccb->NodeSize = sizeof(*ccb);\n    ccb->disposition = RequestedDisposition;\n    ccb->options = options;\n    ccb->query_dir_offset = 0;\n    RtlInitUnicodeString(&ccb->query_string, NULL);\n    ccb->has_wildcard = false;\n    ccb->specific_file = false;\n    ccb->access = granted_access;\n    ccb->case_sensitive = IrpSp->Flags & SL_CASE_SENSITIVE;\n    ccb->reserving = false;\n    ccb->lxss = called_from_lxss();\n\n    ccb->fileref = fileref;\n\n    FileObject->FsContext2 = ccb;\n    FileObject->SectionObjectPointer = &fileref->fcb->nonpaged->segment_object;\n\n    switch (RequestedDisposition) {\n        case FILE_SUPERSEDE:\n            Irp->IoStatus.Information = FILE_SUPERSEDED;\n            break;\n\n        case FILE_OPEN:\n        case FILE_OPEN_IF:\n            Irp->IoStatus.Information = FILE_OPENED;\n            break;\n\n        case FILE_OVERWRITE:\n        case FILE_OVERWRITE_IF:\n            Irp->IoStatus.Information = FILE_OVERWRITTEN;\n            break;\n    }\n\n    // Make sure paging files don't have any extents marked as being prealloc,\n    // as this would mean we'd have to lock exclusively when writing.\n    if (IrpSp->Flags & SL_OPEN_PAGING_FILE) {\n        LIST_ENTRY* le;\n        bool changed = false;\n\n        ExAcquireResourceExclusiveLite(fileref->fcb->Header.Resource, true);\n\n        le = fileref->fcb->extents.Flink;\n\n        while (le != &fileref->fcb->extents) {\n            extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n            if (ext->extent_data.type == EXTENT_TYPE_PREALLOC) {\n                ext->extent_data.type = EXTENT_TYPE_REGULAR;\n                changed = true;\n            }\n\n            le = le->Flink;\n        }\n\n        ExReleaseResourceLite(fileref->fcb->Header.Resource);\n\n        if (changed) {\n            fileref->fcb->extents_changed = true;\n            mark_fcb_dirty(fileref->fcb);\n        }\n\n        fileref->fcb->Header.Flags2 |= FSRTL_FLAG2_IS_PAGING_FILE;\n    }\n\n#ifdef DEBUG_FCB_REFCOUNTS\n    LONG oc = InterlockedIncrement(&fileref->open_count);\n    ERR(\"fileref %p: open_count now %i\\n\", fileref, oc);\n#else\n    InterlockedIncrement(&fileref->open_count);\n#endif\n    InterlockedIncrement(&Vcb->open_files);\n\n    return STATUS_SUCCESS;\n}\n\nstatic void __stdcall oplock_complete(PVOID Context, PIRP Irp) {\n    NTSTATUS Status;\n    LIST_ENTRY rollback;\n    bool skip_lock;\n    oplock_context* ctx = Context;\n    device_extension* Vcb = ctx->Vcb;\n\n    TRACE(\"(%p, %p)\\n\", Context, Irp);\n\n    InitializeListHead(&rollback);\n\n    skip_lock = ExIsResourceAcquiredExclusiveLite(&Vcb->tree_lock);\n\n    if (!skip_lock)\n        ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    ExAcquireResourceSharedLite(&Vcb->fileref_lock, true);\n\n    // FIXME - trans\n    Status = open_file3(Vcb, Irp, ctx->granted_access, ctx->fileref, &rollback);\n\n    if (!NT_SUCCESS(Status)) {\n        free_fileref(ctx->fileref);\n        do_rollback(ctx->Vcb, &rollback);\n    } else\n        clear_rollback(&rollback);\n\n    ExReleaseResourceLite(&Vcb->fileref_lock);\n\n    if (Status == STATUS_SUCCESS) {\n        fcb* fcb2;\n        PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n        PFILE_OBJECT FileObject = IrpSp->FileObject;\n        bool skip_fcb_lock;\n\n        IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess |= ctx->granted_access;\n        IrpSp->Parameters.Create.SecurityContext->AccessState->RemainingDesiredAccess &= ~(ctx->granted_access | MAXIMUM_ALLOWED);\n\n        if (!FileObject->Vpb)\n            FileObject->Vpb = Vcb->devobj->Vpb;\n\n        fcb2 = FileObject->FsContext;\n\n        if (fcb2->ads) {\n            struct _ccb* ccb2 = FileObject->FsContext2;\n\n            fcb2 = ccb2->fileref->parent->fcb;\n        }\n\n        skip_fcb_lock = ExIsResourceAcquiredExclusiveLite(fcb2->Header.Resource);\n\n        if (!skip_fcb_lock)\n            ExAcquireResourceExclusiveLite(fcb2->Header.Resource, true);\n\n        fcb_load_csums(Vcb, fcb2, Irp);\n\n        if (!skip_fcb_lock)\n            ExReleaseResourceLite(fcb2->Header.Resource);\n    }\n\n    if (!skip_lock)\n        ExReleaseResourceLite(&Vcb->tree_lock);\n\n    // FIXME - call free_trans if failed and within transaction\n\n    Irp->IoStatus.Status = Status;\n    IoCompleteRequest(Irp, NT_SUCCESS(Status) ? IO_DISK_INCREMENT : IO_NO_INCREMENT);\n\n    ctx->Status = Status;\n\n    KeSetEvent(&ctx->event, 0, false);\n}\n\nstatic NTSTATUS open_file2(device_extension* Vcb, ULONG RequestedDisposition, file_ref* fileref, ACCESS_MASK* granted_access,\n                           PFILE_OBJECT FileObject, UNICODE_STRING* fn, ULONG options, PIRP Irp, LIST_ENTRY* rollback,\n                           oplock_context** opctx) {\n    NTSTATUS Status;\n    file_ref* sf;\n    bool readonly;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n    if (RequestedDisposition == FILE_SUPERSEDE || RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF) {\n        LARGE_INTEGER zero;\n\n        if (fileref->fcb->type == BTRFS_TYPE_DIRECTORY || is_subvol_readonly(fileref->fcb->subvol, Irp)) {\n            Status = STATUS_ACCESS_DENIED;\n            goto end;\n        }\n\n        if (Vcb->readonly) {\n            Status = STATUS_MEDIA_WRITE_PROTECTED;\n            goto end;\n        }\n\n        zero.QuadPart = 0;\n        if (!MmCanFileBeTruncated(&fileref->fcb->nonpaged->segment_object, &zero)) {\n            Status = STATUS_USER_MAPPED_FILE;\n            goto end;\n        }\n    }\n\n    if (IrpSp->Parameters.Create.SecurityContext->DesiredAccess != 0) {\n        SeLockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext);\n\n        if (!SeAccessCheck((fileref->fcb->ads || fileref->fcb == Vcb->dummy_fcb) ? fileref->parent->fcb->sd : fileref->fcb->sd,\n                            &IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext,\n                            true, IrpSp->Parameters.Create.SecurityContext->DesiredAccess, 0, NULL,\n                            IoGetFileObjectGenericMapping(), IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode,\n                            granted_access, &Status)) {\n            SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext);\n            TRACE(\"SeAccessCheck failed, returning %08lx\\n\", Status);\n            goto end;\n        }\n\n        SeUnlockSubjectContext(&IrpSp->Parameters.Create.SecurityContext->AccessState->SubjectSecurityContext);\n    } else\n        *granted_access = 0;\n\n    TRACE(\"deleted = %s\\n\", fileref->deleted ? \"true\" : \"false\");\n\n    sf = fileref;\n    while (sf) {\n        if (sf->delete_on_close) {\n            TRACE(\"could not open as deletion pending\\n\");\n            Status = STATUS_DELETE_PENDING;\n            goto end;\n        }\n        sf = sf->parent;\n    }\n\n    readonly = (!fileref->fcb->ads && fileref->fcb->atts & FILE_ATTRIBUTE_READONLY && !(IrpSp->Flags & SL_IGNORE_READONLY_ATTRIBUTE)) ||\n               (fileref->fcb->ads && fileref->parent->fcb->atts & FILE_ATTRIBUTE_READONLY && !(IrpSp->Flags & SL_IGNORE_READONLY_ATTRIBUTE)) ||\n               is_subvol_readonly(fileref->fcb->subvol, Irp) || fileref->fcb == Vcb->dummy_fcb || Vcb->readonly;\n\n    if (options & FILE_DELETE_ON_CLOSE && (fileref == Vcb->root_fileref || readonly)) {\n        Status = STATUS_CANNOT_DELETE;\n        goto end;\n    }\n\n    readonly |= fileref->fcb->inode_item.flags_ro & BTRFS_INODE_RO_VERITY;\n\n    if (readonly) {\n        ACCESS_MASK allowed;\n\n        allowed = READ_CONTROL | SYNCHRONIZE | ACCESS_SYSTEM_SECURITY | FILE_READ_DATA |\n                    FILE_READ_EA | FILE_READ_ATTRIBUTES | FILE_EXECUTE | FILE_LIST_DIRECTORY |\n                    FILE_TRAVERSE;\n\n        if (!Vcb->readonly && (fileref->fcb == Vcb->dummy_fcb || fileref->fcb->inode == SUBVOL_ROOT_INODE))\n            allowed |= DELETE;\n\n        if (fileref->fcb != Vcb->dummy_fcb && !is_subvol_readonly(fileref->fcb->subvol, Irp) && !Vcb->readonly) {\n            allowed |= DELETE | WRITE_OWNER | WRITE_DAC | FILE_WRITE_EA | FILE_WRITE_ATTRIBUTES;\n\n            if (!fileref->fcb->ads && fileref->fcb->type == BTRFS_TYPE_DIRECTORY)\n                allowed |= FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE | FILE_DELETE_CHILD;\n        } else if (fileref->fcb->inode == SUBVOL_ROOT_INODE && is_subvol_readonly(fileref->fcb->subvol, Irp) && !Vcb->readonly) {\n            // We allow a subvolume root to be opened read-write even if its readonly flag is set, so it can be cleared\n\n            allowed |= FILE_WRITE_ATTRIBUTES;\n        }\n\n        if (IrpSp->Parameters.Create.SecurityContext->DesiredAccess & MAXIMUM_ALLOWED) {\n            *granted_access &= allowed;\n            IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess &= allowed;\n        } else if (*granted_access & ~allowed) {\n            Status = Vcb->readonly ? STATUS_MEDIA_WRITE_PROTECTED : STATUS_ACCESS_DENIED;\n            goto end;\n        }\n\n        if (RequestedDisposition == FILE_OVERWRITE || RequestedDisposition == FILE_OVERWRITE_IF) {\n            WARN(\"cannot overwrite readonly file\\n\");\n            Status = STATUS_ACCESS_DENIED;\n            goto end;\n        }\n    }\n\n    if ((fileref->fcb->type == BTRFS_TYPE_SYMLINK || fileref->fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) && !(options & FILE_OPEN_REPARSE_POINT))  {\n        REPARSE_DATA_BUFFER* data;\n\n        /* How reparse points work from the point of view of the filesystem appears to\n            * undocumented. When returning STATUS_REPARSE, MSDN encourages us to return\n            * IO_REPARSE in Irp->IoStatus.Information, but that means we have to do our own\n            * translation. If we instead return the reparse tag in Information, and store\n            * a pointer to the reparse data buffer in Irp->Tail.Overlay.AuxiliaryBuffer,\n            * IopSymlinkProcessReparse will do the translation for us. */\n\n        Status = get_reparse_block(fileref->fcb, (uint8_t**)&data);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"get_reparse_block returned %08lx\\n\", Status);\n            Status = STATUS_SUCCESS;\n        } else {\n            Irp->IoStatus.Information = data->ReparseTag;\n\n            if (fn->Buffer[(fn->Length / sizeof(WCHAR)) - 1] == '\\\\')\n                data->Reserved = sizeof(WCHAR);\n\n            Irp->Tail.Overlay.AuxiliaryBuffer = (void*)data;\n\n            Status = STATUS_REPARSE;\n            goto end;\n        }\n    }\n\n    if (fileref->fcb->type == BTRFS_TYPE_DIRECTORY && !fileref->fcb->ads) {\n        if (options & FILE_NON_DIRECTORY_FILE && !(fileref->fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT)) {\n            Status = STATUS_FILE_IS_A_DIRECTORY;\n            goto end;\n        }\n    } else if (options & FILE_DIRECTORY_FILE) {\n        TRACE(\"returning STATUS_NOT_A_DIRECTORY (type = %u)\\n\", fileref->fcb->type);\n        Status = STATUS_NOT_A_DIRECTORY;\n        goto end;\n    }\n\n    if (fileref->open_count > 0) {\n        oplock_context* ctx;\n\n        Status = IoCheckShareAccess(*granted_access, IrpSp->Parameters.Create.ShareAccess, FileObject, &fileref->fcb->share_access, false);\n\n        if (!NT_SUCCESS(Status)) {\n            if (Status == STATUS_SHARING_VIOLATION)\n                TRACE(\"IoCheckShareAccess failed, returning %08lx\\n\", Status);\n            else\n                WARN(\"IoCheckShareAccess failed, returning %08lx\\n\", Status);\n\n            goto end;\n        }\n\n        ctx = ExAllocatePoolWithTag(NonPagedPool, sizeof(oplock_context), ALLOC_TAG);\n        if (!ctx) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        ctx->Vcb = Vcb;\n        ctx->granted_access = *granted_access;\n        ctx->fileref = fileref;\n        KeInitializeEvent(&ctx->event, NotificationEvent, false);\n\n        Status = FsRtlCheckOplock(fcb_oplock(fileref->fcb), Irp, ctx, oplock_complete, NULL);\n        if (Status == STATUS_PENDING) {\n            *opctx = ctx;\n            return Status;\n        }\n\n        ExFreePool(ctx);\n\n        if (!NT_SUCCESS(Status)) {\n            WARN(\"FsRtlCheckOplock returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        IoUpdateShareAccess(FileObject, &fileref->fcb->share_access);\n    } else\n        IoSetShareAccess(*granted_access, IrpSp->Parameters.Create.ShareAccess, FileObject, &fileref->fcb->share_access);\n\n    Status = open_file3(Vcb, Irp, *granted_access, fileref, rollback);\n\n    if (!NT_SUCCESS(Status))\n        IoRemoveShareAccess(FileObject, &fileref->fcb->share_access);\n\nend:\n    if (!NT_SUCCESS(Status))\n        free_fileref(fileref);\n\n    return Status;\n}\n\nNTSTATUS open_fileref_by_inode(_Requires_exclusive_lock_held_(_Curr_->fcb_lock) device_extension* Vcb,\n                               root* subvol, uint64_t inode, file_ref** pfr, PIRP Irp) {\n    NTSTATUS Status;\n    fcb* fcb;\n    uint64_t parent = 0;\n    UNICODE_STRING name;\n    bool hl_alloc = false;\n    file_ref *parfr, *fr;\n\n    Status = open_fcb(Vcb, subvol, inode, 0, NULL, true, NULL, &fcb, PagedPool, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"open_fcb returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    if (fcb->inode_item.st_nlink == 0 || fcb->deleted) {\n        ExReleaseResourceLite(fcb->Header.Resource);\n        free_fcb(fcb);\n        return STATUS_OBJECT_NAME_NOT_FOUND;\n    }\n\n    if (fcb->fileref) {\n        *pfr = fcb->fileref;\n        increase_fileref_refcount(fcb->fileref);\n        free_fcb(fcb);\n        ExReleaseResourceLite(fcb->Header.Resource);\n        return STATUS_SUCCESS;\n    }\n\n    if (IsListEmpty(&fcb->hardlinks)) {\n        ExReleaseResourceLite(fcb->Header.Resource);\n\n        ExAcquireResourceSharedLite(&Vcb->dirty_filerefs_lock, true);\n\n        if (!IsListEmpty(&Vcb->dirty_filerefs)) {\n            LIST_ENTRY* le = Vcb->dirty_filerefs.Flink;\n            while (le != &Vcb->dirty_filerefs) {\n                fr = CONTAINING_RECORD(le, file_ref, list_entry_dirty);\n\n                if (fr->fcb == fcb) {\n                    ExReleaseResourceLite(&Vcb->dirty_filerefs_lock);\n                    increase_fileref_refcount(fr);\n                    free_fcb(fcb);\n                    *pfr = fr;\n                    return STATUS_SUCCESS;\n                }\n\n                le = le->Flink;\n            }\n        }\n\n        ExReleaseResourceLite(&Vcb->dirty_filerefs_lock);\n\n        {\n            KEY searchkey;\n            traverse_ptr tp;\n\n            searchkey.obj_id = fcb->inode;\n            searchkey.obj_type = TYPE_INODE_REF;\n            searchkey.offset = 0;\n\n            Status = find_item(Vcb, subvol, &tp, &searchkey, false, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"find_item returned %08lx\\n\", Status);\n                free_fcb(fcb);\n                return Status;\n            }\n\n            do {\n                traverse_ptr next_tp;\n\n                if (tp.item->key.obj_id > fcb->inode || (tp.item->key.obj_id == fcb->inode && tp.item->key.obj_type > TYPE_INODE_EXTREF))\n                    break;\n\n                if (tp.item->key.obj_id == fcb->inode) {\n                    if (tp.item->key.obj_type == TYPE_INODE_REF) {\n                        INODE_REF* ir = (INODE_REF*)tp.item->data;\n\n                        if (tp.item->size < offsetof(INODE_REF, name[0]) || tp.item->size < offsetof(INODE_REF, name[0]) + ir->n) {\n                            ERR(\"INODE_REF was too short\\n\");\n                            free_fcb(fcb);\n                            return STATUS_INTERNAL_ERROR;\n                        }\n\n                        ULONG stringlen;\n\n                        Status = utf8_to_utf16(NULL, 0, &stringlen, ir->name, ir->n);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"utf8_to_utf16 1 returned %08lx\\n\", Status);\n                            free_fcb(fcb);\n                            return Status;\n                        }\n\n                        name.Length = name.MaximumLength = (uint16_t)stringlen;\n\n                        if (stringlen == 0)\n                            name.Buffer = NULL;\n                        else {\n                            name.Buffer = ExAllocatePoolWithTag(PagedPool, name.MaximumLength, ALLOC_TAG);\n\n                            if (!name.Buffer) {\n                                ERR(\"out of memory\\n\");\n                                free_fcb(fcb);\n                                return STATUS_INSUFFICIENT_RESOURCES;\n                            }\n\n                            Status = utf8_to_utf16(name.Buffer, stringlen, &stringlen, ir->name, ir->n);\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"utf8_to_utf16 2 returned %08lx\\n\", Status);\n                                ExFreePool(name.Buffer);\n                                free_fcb(fcb);\n                                return Status;\n                            }\n\n                            hl_alloc = true;\n                        }\n\n                        parent = tp.item->key.offset;\n\n                        break;\n                    } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) {\n                        INODE_EXTREF* ier = (INODE_EXTREF*)tp.item->data;\n\n                        if (tp.item->size < offsetof(INODE_EXTREF, name[0]) || tp.item->size < offsetof(INODE_EXTREF, name[0]) + ier->n) {\n                            ERR(\"INODE_EXTREF was too short\\n\");\n                            free_fcb(fcb);\n                            return STATUS_INTERNAL_ERROR;\n                        }\n\n                        ULONG stringlen;\n\n                        Status = utf8_to_utf16(NULL, 0, &stringlen, ier->name, ier->n);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"utf8_to_utf16 1 returned %08lx\\n\", Status);\n                            free_fcb(fcb);\n                            return Status;\n                        }\n\n                        name.Length = name.MaximumLength = (uint16_t)stringlen;\n\n                        if (stringlen == 0)\n                            name.Buffer = NULL;\n                        else {\n                            name.Buffer = ExAllocatePoolWithTag(PagedPool, name.MaximumLength, ALLOC_TAG);\n\n                            if (!name.Buffer) {\n                                ERR(\"out of memory\\n\");\n                                free_fcb(fcb);\n                                return STATUS_INSUFFICIENT_RESOURCES;\n                            }\n\n                            Status = utf8_to_utf16(name.Buffer, stringlen, &stringlen, ier->name, ier->n);\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"utf8_to_utf16 2 returned %08lx\\n\", Status);\n                                ExFreePool(name.Buffer);\n                                free_fcb(fcb);\n                                return Status;\n                            }\n\n                            hl_alloc = true;\n                        }\n\n                        parent = ier->dir;\n\n                        break;\n                    }\n                }\n\n                if (find_next_item(Vcb, &tp, &next_tp, false, Irp))\n                    tp = next_tp;\n                else\n                    break;\n            } while (true);\n        }\n\n        if (parent == 0) {\n            WARN(\"trying to open inode with no references\\n\");\n            free_fcb(fcb);\n            return STATUS_INVALID_PARAMETER;\n        }\n    } else {\n        hardlink* hl = CONTAINING_RECORD(fcb->hardlinks.Flink, hardlink, list_entry);\n\n        name = hl->name;\n        parent = hl->parent;\n\n        ExReleaseResourceLite(fcb->Header.Resource);\n    }\n\n    if (parent == inode) { // subvolume root\n        KEY searchkey;\n        traverse_ptr tp;\n\n        searchkey.obj_id = subvol->id;\n        searchkey.obj_type = TYPE_ROOT_BACKREF;\n        searchkey.offset = 0xffffffffffffffff;\n\n        Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            free_fcb(fcb);\n            return Status;\n        }\n\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n            ROOT_REF* rr = (ROOT_REF*)tp.item->data;\n            LIST_ENTRY* le;\n            root* r = NULL;\n            ULONG stringlen;\n\n            if (tp.item->size < sizeof(ROOT_REF)) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(ROOT_REF));\n                free_fcb(fcb);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            if (tp.item->size < offsetof(ROOT_REF, name[0]) + rr->n) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, offsetof(ROOT_REF, name[0]) + rr->n);\n                free_fcb(fcb);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            le = Vcb->roots.Flink;\n            while (le != &Vcb->roots) {\n                root* r2 = CONTAINING_RECORD(le, root, list_entry);\n\n                if (r2->id == tp.item->key.offset) {\n                    r = r2;\n                    break;\n                }\n\n                le = le->Flink;\n            }\n\n            if (!r) {\n                ERR(\"couldn't find subvol %I64x\\n\", tp.item->key.offset);\n                free_fcb(fcb);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            Status = open_fileref_by_inode(Vcb, r, rr->dir, &parfr, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"open_fileref_by_inode returned %08lx\\n\", Status);\n                free_fcb(fcb);\n                return Status;\n            }\n\n            Status = utf8_to_utf16(NULL, 0, &stringlen, rr->name, rr->n);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"utf8_to_utf16 1 returned %08lx\\n\", Status);\n                free_fcb(fcb);\n                return Status;\n            }\n\n            name.Length = name.MaximumLength = (uint16_t)stringlen;\n\n            if (stringlen == 0)\n                name.Buffer = NULL;\n            else {\n                if (hl_alloc)\n                    ExFreePool(name.Buffer);\n\n                name.Buffer = ExAllocatePoolWithTag(PagedPool, name.MaximumLength, ALLOC_TAG);\n\n                if (!name.Buffer) {\n                    ERR(\"out of memory\\n\");\n                    free_fcb(fcb);\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                Status = utf8_to_utf16(name.Buffer, stringlen, &stringlen, rr->name, rr->n);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"utf8_to_utf16 2 returned %08lx\\n\", Status);\n                    ExFreePool(name.Buffer);\n                    free_fcb(fcb);\n                    return Status;\n                }\n\n                hl_alloc = true;\n            }\n        } else {\n            if (!Vcb->options.no_root_dir && subvol->id == BTRFS_ROOT_FSTREE && Vcb->root_fileref->fcb->subvol != subvol) {\n                Status = open_fileref_by_inode(Vcb, Vcb->root_fileref->fcb->subvol, SUBVOL_ROOT_INODE, &parfr, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"open_fileref_by_inode returned %08lx\\n\", Status);\n                    free_fcb(fcb);\n                    return Status;\n                }\n\n                name.Length = name.MaximumLength = sizeof(root_dir_utf16) - sizeof(WCHAR);\n                name.Buffer = (WCHAR*)root_dir_utf16;\n            } else {\n                ERR(\"couldn't find parent for subvol %I64x\\n\", subvol->id);\n                free_fcb(fcb);\n                return STATUS_INTERNAL_ERROR;\n            }\n        }\n    } else {\n        Status = open_fileref_by_inode(Vcb, subvol, parent, &parfr, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"open_fileref_by_inode returned %08lx\\n\", Status);\n            free_fcb(fcb);\n            return Status;\n        }\n    }\n\n    Status = open_fileref_child(Vcb, parfr, &name, true, true, false, PagedPool, &fr, Irp);\n\n    if (hl_alloc)\n        ExFreePool(name.Buffer);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"open_fileref_child returned %08lx\\n\", Status);\n\n        free_fcb(fcb);\n        free_fileref(parfr);\n\n        return Status;\n    }\n\n    *pfr = fr;\n\n    free_fcb(fcb);\n    free_fileref(parfr);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS open_file(PDEVICE_OBJECT DeviceObject, _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, PIRP Irp,\n                          LIST_ENTRY* rollback, oplock_context** opctx) {\n    PFILE_OBJECT FileObject = NULL;\n    ULONG RequestedDisposition;\n    ULONG options;\n    NTSTATUS Status;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    USHORT parsed;\n    ULONG fn_offset = 0;\n    file_ref *related, *fileref = NULL;\n    POOL_TYPE pool_type = IrpSp->Flags & SL_OPEN_PAGING_FILE ? NonPagedPool : PagedPool;\n    ACCESS_MASK granted_access;\n    bool loaded_related = false;\n    UNICODE_STRING fn;\n\n    Irp->IoStatus.Information = 0;\n\n    RequestedDisposition = ((IrpSp->Parameters.Create.Options >> 24) & 0xff);\n    options = IrpSp->Parameters.Create.Options & FILE_VALID_OPTION_FLAGS;\n\n    if (options & FILE_DIRECTORY_FILE && RequestedDisposition == FILE_SUPERSEDE) {\n        WARN(\"error - supersede requested with FILE_DIRECTORY_FILE\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    FileObject = IrpSp->FileObject;\n\n    if (!FileObject) {\n        ERR(\"FileObject was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (FileObject->RelatedFileObject && FileObject->RelatedFileObject->FsContext2) {\n        struct _ccb* relatedccb = FileObject->RelatedFileObject->FsContext2;\n\n        related = relatedccb->fileref;\n    } else\n        related = NULL;\n\n    debug_create_options(options);\n\n    switch (RequestedDisposition) {\n        case FILE_SUPERSEDE:\n            TRACE(\"requested disposition: FILE_SUPERSEDE\\n\");\n            break;\n\n        case FILE_CREATE:\n            TRACE(\"requested disposition: FILE_CREATE\\n\");\n            break;\n\n        case FILE_OPEN:\n            TRACE(\"requested disposition: FILE_OPEN\\n\");\n            break;\n\n        case FILE_OPEN_IF:\n            TRACE(\"requested disposition: FILE_OPEN_IF\\n\");\n            break;\n\n        case FILE_OVERWRITE:\n            TRACE(\"requested disposition: FILE_OVERWRITE\\n\");\n            break;\n\n        case FILE_OVERWRITE_IF:\n            TRACE(\"requested disposition: FILE_OVERWRITE_IF\\n\");\n            break;\n\n        default:\n            ERR(\"unknown disposition: %lx\\n\", RequestedDisposition);\n            Status = STATUS_NOT_IMPLEMENTED;\n            goto exit;\n    }\n\n    fn = FileObject->FileName;\n\n    TRACE(\"(%.*S)\\n\", (int)(fn.Length / sizeof(WCHAR)), fn.Buffer);\n    TRACE(\"FileObject = %p\\n\", FileObject);\n\n    if (Vcb->readonly && (RequestedDisposition == FILE_SUPERSEDE || RequestedDisposition == FILE_CREATE || RequestedDisposition == FILE_OVERWRITE)) {\n        Status = STATUS_MEDIA_WRITE_PROTECTED;\n        goto exit;\n    }\n\n    if (options & FILE_OPEN_BY_FILE_ID) {\n        if (RequestedDisposition != FILE_OPEN) {\n            WARN(\"FILE_OPEN_BY_FILE_ID not supported for anything other than FILE_OPEN\\n\");\n            Status = STATUS_INVALID_PARAMETER;\n            goto exit;\n        }\n\n        if (fn.Length == sizeof(uint64_t)) {\n            uint64_t inode;\n\n            if (!related) {\n                WARN(\"cannot open by short file ID unless related fileref also provided\\n\");\n                Status = STATUS_INVALID_PARAMETER;\n                goto exit;\n            }\n\n            inode = (*(uint64_t*)fn.Buffer) & 0xffffffffff;\n\n            if (related->fcb == Vcb->root_fileref->fcb && inode == 0)\n                inode = Vcb->root_fileref->fcb->inode;\n\n            if (inode == 0) { // we use 0 to mean the parent of a subvolume\n                fileref = related->parent;\n                increase_fileref_refcount(fileref);\n                Status = STATUS_SUCCESS;\n            } else\n                Status = open_fileref_by_inode(Vcb, related->fcb->subvol, inode, &fileref, Irp);\n\n            goto loaded;\n        } else if (fn.Length == sizeof(FILE_ID_128)) {\n            uint64_t inode, subvol_id;\n            root* subvol = NULL;\n\n            RtlCopyMemory(&inode, fn.Buffer, sizeof(uint64_t));\n            RtlCopyMemory(&subvol_id, (uint8_t*)fn.Buffer + sizeof(uint64_t), sizeof(uint64_t));\n\n            if (subvol_id == BTRFS_ROOT_FSTREE || (subvol_id >= 0x100 && subvol_id < 0x8000000000000000)) {\n                LIST_ENTRY* le = Vcb->roots.Flink;\n                while (le != &Vcb->roots) {\n                    root* r = CONTAINING_RECORD(le, root, list_entry);\n\n                    if (r->id == subvol_id) {\n                        subvol = r;\n                        break;\n                    }\n\n                    le = le->Flink;\n                }\n            }\n\n            if (!subvol) {\n                WARN(\"subvol %I64x not found\\n\", subvol_id);\n                Status = STATUS_OBJECT_NAME_NOT_FOUND;\n            } else\n                Status = open_fileref_by_inode(Vcb, subvol, inode, &fileref, Irp);\n\n            goto loaded;\n        } else {\n            WARN(\"invalid ID size for FILE_OPEN_BY_FILE_ID\\n\");\n            Status = STATUS_INVALID_PARAMETER;\n            goto exit;\n        }\n    }\n\n    if (related && fn.Length != 0 && fn.Buffer[0] == '\\\\') {\n        Status = STATUS_INVALID_PARAMETER;\n        goto exit;\n    }\n\n    if (!related && RequestedDisposition != FILE_OPEN && !(IrpSp->Flags & SL_OPEN_TARGET_DIRECTORY)) {\n        ULONG fnoff;\n\n        Status = open_fileref(Vcb, &related, &fn, NULL, true, &parsed, &fnoff,\n                              pool_type, IrpSp->Flags & SL_CASE_SENSITIVE, Irp);\n\n        if (Status == STATUS_OBJECT_NAME_NOT_FOUND)\n            Status = STATUS_OBJECT_PATH_NOT_FOUND;\n        else if (Status == STATUS_REPARSE)\n            fileref = related;\n        else if (NT_SUCCESS(Status)) {\n            fnoff *= sizeof(WCHAR);\n            fnoff += (related->dc ? related->dc->name.Length : 0) + sizeof(WCHAR);\n\n            if (related->fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) {\n                Status = STATUS_REPARSE;\n                fileref = related;\n                parsed = (USHORT)fnoff - sizeof(WCHAR);\n            } else {\n                fn.Buffer = &fn.Buffer[fnoff / sizeof(WCHAR)];\n                fn.Length -= (USHORT)fnoff;\n\n                Status = open_fileref(Vcb, &fileref, &fn, related, IrpSp->Flags & SL_OPEN_TARGET_DIRECTORY, &parsed, &fn_offset,\n                                      pool_type, IrpSp->Flags & SL_CASE_SENSITIVE, Irp);\n\n                loaded_related = true;\n            }\n        }\n    } else {\n        Status = open_fileref(Vcb, &fileref, &fn, related, IrpSp->Flags & SL_OPEN_TARGET_DIRECTORY, &parsed, &fn_offset,\n                              pool_type, IrpSp->Flags & SL_CASE_SENSITIVE, Irp);\n    }\n\nloaded:\n    if (Status == STATUS_REPARSE) {\n        REPARSE_DATA_BUFFER* data;\n\n        ExAcquireResourceSharedLite(fileref->fcb->Header.Resource, true);\n        Status = get_reparse_block(fileref->fcb, (uint8_t**)&data);\n        ExReleaseResourceLite(fileref->fcb->Header.Resource);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"get_reparse_block returned %08lx\\n\", Status);\n\n            Status = STATUS_SUCCESS;\n        } else {\n            Status = STATUS_REPARSE;\n            RtlCopyMemory(&Irp->IoStatus.Information, data, sizeof(ULONG));\n\n            data->Reserved = FileObject->FileName.Length - parsed;\n\n            Irp->Tail.Overlay.AuxiliaryBuffer = (void*)data;\n\n            free_fileref(fileref);\n\n            goto exit;\n        }\n    }\n\n    if (NT_SUCCESS(Status) && fileref->deleted)\n        Status = STATUS_OBJECT_NAME_NOT_FOUND;\n\n    if (NT_SUCCESS(Status)) {\n        if (RequestedDisposition == FILE_CREATE) {\n            TRACE(\"file already exists, returning STATUS_OBJECT_NAME_COLLISION\\n\");\n            Status = STATUS_OBJECT_NAME_COLLISION;\n\n            free_fileref(fileref);\n\n            goto exit;\n        }\n    } else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {\n        if (RequestedDisposition == FILE_OPEN || RequestedDisposition == FILE_OVERWRITE) {\n            TRACE(\"file doesn't exist, returning STATUS_OBJECT_NAME_NOT_FOUND\\n\");\n            goto exit;\n        }\n    } else if (Status == STATUS_OBJECT_PATH_NOT_FOUND || Status == STATUS_OBJECT_NAME_INVALID) {\n        TRACE(\"open_fileref returned %08lx\\n\", Status);\n        goto exit;\n    } else {\n        ERR(\"open_fileref returned %08lx\\n\", Status);\n        goto exit;\n    }\n\n    if (NT_SUCCESS(Status)) { // file already exists\n        Status = open_file2(Vcb, RequestedDisposition, fileref, &granted_access, FileObject, &fn,\n                            options, Irp, rollback, opctx);\n    } else {\n        file_ref* existing_file = NULL;\n\n        Status = file_create(Irp, Vcb, FileObject, related, loaded_related, &fn, RequestedDisposition, options, &existing_file, rollback);\n\n        if (Status == STATUS_OBJECT_NAME_COLLISION) { // already exists\n            fileref = existing_file;\n\n            Status = open_file2(Vcb, RequestedDisposition, fileref, &granted_access, FileObject, &fn,\n                                options, Irp, rollback, opctx);\n        } else {\n            Irp->IoStatus.Information = NT_SUCCESS(Status) ? FILE_CREATED : 0;\n            granted_access = IrpSp->Parameters.Create.SecurityContext->DesiredAccess;\n        }\n    }\n\n    if (NT_SUCCESS(Status) && !(options & FILE_NO_INTERMEDIATE_BUFFERING))\n        FileObject->Flags |= FO_CACHE_SUPPORTED;\n\nexit:\n    if (loaded_related)\n        free_fileref(related);\n\n    if (Status == STATUS_SUCCESS) {\n        fcb* fcb2;\n\n        IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess |= granted_access;\n        IrpSp->Parameters.Create.SecurityContext->AccessState->RemainingDesiredAccess &= ~(granted_access | MAXIMUM_ALLOWED);\n\n        if (!FileObject->Vpb)\n            FileObject->Vpb = DeviceObject->Vpb;\n\n        fcb2 = FileObject->FsContext;\n\n        if (fcb2->ads) {\n            struct _ccb* ccb2 = FileObject->FsContext2;\n\n            fcb2 = ccb2->fileref->parent->fcb;\n        }\n\n        ExAcquireResourceExclusiveLite(fcb2->Header.Resource, true);\n        fcb_load_csums(Vcb, fcb2, Irp);\n        ExReleaseResourceLite(fcb2->Header.Resource);\n    } else if (Status != STATUS_REPARSE && Status != STATUS_OBJECT_NAME_NOT_FOUND && Status != STATUS_OBJECT_PATH_NOT_FOUND)\n        TRACE(\"returning %08lx\\n\", Status);\n\n    return Status;\n}\n\nstatic NTSTATUS verify_vcb(device_extension* Vcb, PIRP Irp) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    bool need_verify = false;\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    le = Vcb->devices.Flink;\n    while (le != &Vcb->devices) {\n        device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n        if (dev->devobj && dev->removable) {\n            ULONG cc;\n            IO_STATUS_BLOCK iosb;\n\n            Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, &cc, sizeof(ULONG), true, &iosb);\n\n            if (IoIsErrorUserInduced(Status)) {\n                ERR(\"IOCTL_STORAGE_CHECK_VERIFY returned %08lx (user-induced)\\n\", Status);\n                need_verify = true;\n            } else if (!NT_SUCCESS(Status)) {\n                ERR(\"IOCTL_STORAGE_CHECK_VERIFY returned %08lx\\n\", Status);\n                goto end;\n            } else if (iosb.Information < sizeof(ULONG)) {\n                ERR(\"iosb.Information was too short\\n\");\n                Status = STATUS_INTERNAL_ERROR;\n            } else if (cc != dev->change_count) {\n                dev->devobj->Flags |= DO_VERIFY_VOLUME;\n                need_verify = true;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    if (need_verify) {\n        PDEVICE_OBJECT devobj;\n\n        devobj = IoGetDeviceToVerify(Irp->Tail.Overlay.Thread);\n        IoSetDeviceToVerify(Irp->Tail.Overlay.Thread, NULL);\n\n        if (!devobj) {\n            devobj = IoGetDeviceToVerify(PsGetCurrentThread());\n            IoSetDeviceToVerify(PsGetCurrentThread(), NULL);\n        }\n\n        devobj = Vcb->Vpb ? Vcb->Vpb->RealDevice : NULL;\n\n        if (devobj)\n            Status = IoVerifyVolume(devobj, false);\n        else\n            Status = STATUS_VERIFY_REQUIRED;\n    }\n\n    return Status;\n}\n\nstatic bool has_manage_volume_privilege(ACCESS_STATE* access_state, KPROCESSOR_MODE processor_mode) {\n    PRIVILEGE_SET privset;\n\n    privset.PrivilegeCount = 1;\n    privset.Control = PRIVILEGE_SET_ALL_NECESSARY;\n    privset.Privilege[0].Luid = RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE);\n    privset.Privilege[0].Attributes = 0;\n\n    return SePrivilegeCheck(&privset, &access_state->SubjectSecurityContext, processor_mode) ? true : false;\n}\n\n_Dispatch_type_(IRP_MJ_CREATE)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    NTSTATUS Status;\n    PIO_STACK_LOCATION IrpSp;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    bool top_level, locked = false;\n    oplock_context* opctx = NULL;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"create (flags = %lx)\\n\", Irp->Flags);\n\n    top_level = is_top_level(Irp);\n\n    /* return success if just called for FS device object */\n    if (DeviceObject == master_devobj)  {\n        TRACE(\"create called for FS device object\\n\");\n\n        Irp->IoStatus.Information = FILE_OPENED;\n        Status = STATUS_SUCCESS;\n\n        goto exit;\n    } else if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = vol_create(DeviceObject, Irp);\n        goto exit;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto exit;\n    }\n\n    if (!(Vcb->Vpb->Flags & VPB_MOUNTED)) {\n        Status = STATUS_DEVICE_NOT_READY;\n        goto exit;\n    }\n\n    if (Vcb->removing) {\n        Status = STATUS_ACCESS_DENIED;\n        goto exit;\n    }\n\n    Status = verify_vcb(Vcb, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"verify_vcb returned %08lx\\n\", Status);\n        goto exit;\n    }\n\n    ExAcquireResourceSharedLite(&Vcb->load_lock, true);\n    locked = true;\n\n    IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n    if (IrpSp->Flags != 0) {\n        uint32_t flags = IrpSp->Flags;\n\n        TRACE(\"flags:\\n\");\n\n        if (flags & SL_CASE_SENSITIVE) {\n            TRACE(\"SL_CASE_SENSITIVE\\n\");\n            flags &= ~SL_CASE_SENSITIVE;\n        }\n\n        if (flags & SL_FORCE_ACCESS_CHECK) {\n            TRACE(\"SL_FORCE_ACCESS_CHECK\\n\");\n            flags &= ~SL_FORCE_ACCESS_CHECK;\n        }\n\n        if (flags & SL_OPEN_PAGING_FILE) {\n            TRACE(\"SL_OPEN_PAGING_FILE\\n\");\n            flags &= ~SL_OPEN_PAGING_FILE;\n        }\n\n        if (flags & SL_OPEN_TARGET_DIRECTORY) {\n            TRACE(\"SL_OPEN_TARGET_DIRECTORY\\n\");\n            flags &= ~SL_OPEN_TARGET_DIRECTORY;\n        }\n\n        if (flags & SL_STOP_ON_SYMLINK) {\n            TRACE(\"SL_STOP_ON_SYMLINK\\n\");\n            flags &= ~SL_STOP_ON_SYMLINK;\n        }\n\n        if (flags & SL_IGNORE_READONLY_ATTRIBUTE) {\n            TRACE(\"SL_IGNORE_READONLY_ATTRIBUTE\\n\");\n            flags &= ~SL_IGNORE_READONLY_ATTRIBUTE;\n        }\n\n        if (flags)\n            WARN(\"unknown flags: %x\\n\", flags);\n    } else {\n        TRACE(\"flags: (none)\\n\");\n    }\n\n    if (!IrpSp->FileObject) {\n        ERR(\"FileObject was NULL\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto exit;\n    }\n\n    if (IrpSp->FileObject->RelatedFileObject) {\n        fcb* relatedfcb = IrpSp->FileObject->RelatedFileObject->FsContext;\n\n        if (relatedfcb && relatedfcb->Vcb != Vcb) {\n            WARN(\"RelatedFileObject was for different device\\n\");\n            Status = STATUS_INVALID_PARAMETER;\n            goto exit;\n        }\n    }\n\n    // opening volume\n    if (IrpSp->FileObject->FileName.Length == 0 && !IrpSp->FileObject->RelatedFileObject) {\n        ULONG RequestedDisposition = ((IrpSp->Parameters.Create.Options >> 24) & 0xff);\n        ULONG RequestedOptions = IrpSp->Parameters.Create.Options & FILE_VALID_OPTION_FLAGS;\n#ifdef DEBUG_FCB_REFCOUNTS\n        LONG rc;\n#endif\n        ccb* ccb;\n\n        TRACE(\"open operation for volume\\n\");\n\n        if (RequestedDisposition != FILE_OPEN && RequestedDisposition != FILE_OPEN_IF) {\n            Status = STATUS_ACCESS_DENIED;\n            goto exit;\n        }\n\n        if (RequestedOptions & FILE_DIRECTORY_FILE) {\n            Status = STATUS_NOT_A_DIRECTORY;\n            goto exit;\n        }\n\n        ccb = ExAllocatePoolWithTag(NonPagedPool, sizeof(*ccb), ALLOC_TAG);\n        if (!ccb) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        RtlZeroMemory(ccb, sizeof(*ccb));\n\n        ccb->NodeType = BTRFS_NODE_TYPE_CCB;\n        ccb->NodeSize = sizeof(*ccb);\n        ccb->disposition = RequestedDisposition;\n        ccb->options = RequestedOptions;\n        ccb->access = IrpSp->Parameters.Create.SecurityContext->AccessState->PreviouslyGrantedAccess;\n        ccb->manage_volume_privilege = has_manage_volume_privilege(IrpSp->Parameters.Create.SecurityContext->AccessState,\n                                                                   IrpSp->Flags & SL_FORCE_ACCESS_CHECK ? UserMode : Irp->RequestorMode);\n        ccb->reserving = false;\n        ccb->lxss = called_from_lxss();\n\n#ifdef DEBUG_FCB_REFCOUNTS\n        rc = InterlockedIncrement(&Vcb->volume_fcb->refcount);\n        WARN(\"fcb %p: refcount now %i (volume)\\n\", Vcb->volume_fcb, rc);\n#else\n        InterlockedIncrement(&Vcb->volume_fcb->refcount);\n#endif\n        IrpSp->FileObject->FsContext = Vcb->volume_fcb;\n        IrpSp->FileObject->FsContext2 = ccb;\n\n        IrpSp->FileObject->SectionObjectPointer = &Vcb->volume_fcb->nonpaged->segment_object;\n\n        if (!IrpSp->FileObject->Vpb)\n            IrpSp->FileObject->Vpb = DeviceObject->Vpb;\n\n        InterlockedIncrement(&Vcb->open_files);\n\n        Irp->IoStatus.Information = FILE_OPENED;\n        Status = STATUS_SUCCESS;\n    } else {\n        LIST_ENTRY rollback;\n        bool skip_lock;\n\n        InitializeListHead(&rollback);\n\n        TRACE(\"file name: %.*S\\n\", (int)(IrpSp->FileObject->FileName.Length / sizeof(WCHAR)), IrpSp->FileObject->FileName.Buffer);\n\n        if (IrpSp->FileObject->RelatedFileObject)\n            TRACE(\"related file = %p\\n\", IrpSp->FileObject->RelatedFileObject);\n\n        // Don't lock again if we're being called from within CcCopyRead etc.\n        skip_lock = ExIsResourceAcquiredExclusiveLite(&Vcb->tree_lock);\n\n        if (!skip_lock)\n            ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n        ExAcquireResourceSharedLite(&Vcb->fileref_lock, true);\n\n        Status = open_file(DeviceObject, Vcb, Irp, &rollback, &opctx);\n\n        if (!NT_SUCCESS(Status))\n            do_rollback(Vcb, &rollback);\n        else\n            clear_rollback(&rollback);\n\n        ExReleaseResourceLite(&Vcb->fileref_lock);\n\n        if (!skip_lock)\n            ExReleaseResourceLite(&Vcb->tree_lock);\n    }\n\nexit:\n    if (Status != STATUS_PENDING) {\n        Irp->IoStatus.Status = Status;\n        IoCompleteRequest(Irp, NT_SUCCESS(Status) ? IO_DISK_INCREMENT : IO_NO_INCREMENT);\n    }\n\n    if (locked)\n        ExReleaseResourceLite(&Vcb->load_lock);\n\n    if (Status == STATUS_PENDING) {\n        KeWaitForSingleObject(&opctx->event, Executive, KernelMode, false, NULL);\n        Status = opctx->Status;\n        ExFreePool(opctx);\n    }\n\n    TRACE(\"create returning %08lx\\n\", Status);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n"
  },
  {
    "path": "src/devctrl.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include <ntdddisk.h>\n#include <mountdev.h>\n#include <diskguid.h>\n\nextern PDRIVER_OBJECT drvobj;\nextern LIST_ENTRY VcbList;\nextern ERESOURCE global_loading_lock;\n\nstatic NTSTATUS mountdev_query_stable_guid(device_extension* Vcb, PIRP Irp) {\n    MOUNTDEV_STABLE_GUID* msg = Irp->UserBuffer;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n    TRACE(\"IOCTL_MOUNTDEV_QUERY_STABLE_GUID\\n\");\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUNTDEV_STABLE_GUID))\n        return STATUS_INVALID_PARAMETER;\n\n    RtlCopyMemory(&msg->StableGuid, &Vcb->superblock.uuid, sizeof(GUID));\n\n    Irp->IoStatus.Information = sizeof(MOUNTDEV_STABLE_GUID);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS is_writable(device_extension* Vcb) {\n    TRACE(\"IOCTL_DISK_IS_WRITABLE\\n\");\n\n    return Vcb->readonly ? STATUS_MEDIA_WRITE_PROTECTED : STATUS_SUCCESS;\n}\n\nstatic NTSTATUS query_filesystems(void* data, ULONG length) {\n    NTSTATUS Status;\n    LIST_ENTRY *le, *le2;\n    btrfs_filesystem* bfs = NULL;\n    ULONG itemsize;\n\n    ExAcquireResourceSharedLite(&global_loading_lock, true);\n\n    if (IsListEmpty(&VcbList)) {\n        if (length < sizeof(btrfs_filesystem)) {\n            Status = STATUS_BUFFER_OVERFLOW;\n            goto end;\n        } else {\n            RtlZeroMemory(data, sizeof(btrfs_filesystem));\n            Status = STATUS_SUCCESS;\n            goto end;\n        }\n    }\n\n    le = VcbList.Flink;\n\n    while (le != &VcbList) {\n        device_extension* Vcb = CONTAINING_RECORD(le, device_extension, list_entry);\n        btrfs_filesystem_device* bfd;\n\n        if (bfs) {\n            bfs->next_entry = itemsize;\n            bfs = (btrfs_filesystem*)((uint8_t*)bfs + itemsize);\n        } else\n            bfs = data;\n\n        if (length < offsetof(btrfs_filesystem, device)) {\n            Status = STATUS_BUFFER_OVERFLOW;\n            goto end;\n        }\n\n        itemsize = offsetof(btrfs_filesystem, device);\n        length -= offsetof(btrfs_filesystem, device);\n\n        bfs->next_entry = 0;\n        RtlCopyMemory(&bfs->uuid, &Vcb->superblock.uuid, sizeof(BTRFS_UUID));\n\n        ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n        bfs->num_devices = (uint32_t)Vcb->superblock.num_devices;\n\n        bfd = NULL;\n\n        le2 = Vcb->devices.Flink;\n        while (le2 != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le2, device, list_entry);\n            MOUNTDEV_NAME mdn;\n\n            if (bfd)\n                bfd = (btrfs_filesystem_device*)((uint8_t*)bfd + offsetof(btrfs_filesystem_device, name[0]) + bfd->name_length);\n            else\n                bfd = &bfs->device;\n\n            if (length < offsetof(btrfs_filesystem_device, name[0])) {\n                ExReleaseResourceLite(&Vcb->tree_lock);\n                Status = STATUS_BUFFER_OVERFLOW;\n                goto end;\n            }\n\n            itemsize += (ULONG)offsetof(btrfs_filesystem_device, name[0]);\n            length -= (ULONG)offsetof(btrfs_filesystem_device, name[0]);\n\n            RtlCopyMemory(&bfd->uuid, &dev->devitem.device_uuid, sizeof(BTRFS_UUID));\n\n            if (dev->devobj) {\n                Status = dev_ioctl(dev->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL);\n                if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {\n                    ExReleaseResourceLite(&Vcb->tree_lock);\n                    ERR(\"IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\\n\", Status);\n                    goto end;\n                }\n\n                if (mdn.NameLength > length) {\n                    ExReleaseResourceLite(&Vcb->tree_lock);\n                    Status = STATUS_BUFFER_OVERFLOW;\n                    goto end;\n                }\n\n                Status = dev_ioctl(dev->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &bfd->name_length, (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength, true, NULL);\n                if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {\n                    ExReleaseResourceLite(&Vcb->tree_lock);\n                    ERR(\"IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\\n\", Status);\n                    goto end;\n                }\n\n                itemsize += bfd->name_length;\n                length -= bfd->name_length;\n            } else {\n                bfd->missing = true;\n                bfd->name_length = 0;\n            }\n\n            le2 = le2->Flink;\n        }\n\n        ExReleaseResourceLite(&Vcb->tree_lock);\n\n        le = le->Flink;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&global_loading_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS probe_volume(void* data, ULONG length, KPROCESSOR_MODE processor_mode) {\n    MOUNTDEV_NAME* mdn = (MOUNTDEV_NAME*)data;\n    UNICODE_STRING path, pnp_name;\n    NTSTATUS Status;\n    PDEVICE_OBJECT DeviceObject;\n    PFILE_OBJECT FileObject;\n    const GUID* guid;\n\n    if (length < sizeof(MOUNTDEV_NAME))\n        return STATUS_INVALID_PARAMETER;\n\n    if (length < offsetof(MOUNTDEV_NAME, Name[0]) + mdn->NameLength)\n        return STATUS_INVALID_PARAMETER;\n\n    TRACE(\"%.*S\\n\", (int)(mdn->NameLength / sizeof(WCHAR)), mdn->Name);\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    path.Buffer = mdn->Name;\n    path.Length = path.MaximumLength = mdn->NameLength;\n\n    Status = IoGetDeviceObjectPointer(&path, FILE_READ_ATTRIBUTES, &FileObject, &DeviceObject);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    Status = get_device_pnp_name(DeviceObject, &pnp_name, &guid);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"get_device_pnp_name returned %08lx\\n\", Status);\n        ObDereferenceObject(FileObject);\n        return Status;\n    }\n\n    if (RtlCompareMemory(guid, &GUID_DEVINTERFACE_DISK, sizeof(GUID)) == sizeof(GUID)) {\n        Status = dev_ioctl(DeviceObject, IOCTL_DISK_UPDATE_PROPERTIES, NULL, 0, NULL, 0, true, NULL);\n        if (!NT_SUCCESS(Status))\n            WARN(\"IOCTL_DISK_UPDATE_PROPERTIES returned %08lx\\n\", Status);\n    }\n\n    ObDereferenceObject(FileObject);\n\n    volume_removal(&pnp_name);\n\n    if (RtlCompareMemory(guid, &GUID_DEVINTERFACE_DISK, sizeof(GUID)) == sizeof(GUID))\n        disk_arrival(&pnp_name);\n    else\n        volume_arrival(&pnp_name, false);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS ioctl_unload(PIRP Irp) {\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_LOAD_DRIVER_PRIVILEGE), Irp->RequestorMode)) {\n        ERR(\"insufficient privileges\\n\");\n        return STATUS_PRIVILEGE_NOT_HELD;\n    }\n\n    do_shutdown(Irp);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS control_ioctl(PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    NTSTATUS Status;\n\n    switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) {\n        case IOCTL_BTRFS_QUERY_FILESYSTEMS:\n            Status = query_filesystems(map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength);\n            break;\n\n        case IOCTL_BTRFS_PROBE_VOLUME:\n            Status = probe_volume(Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->RequestorMode);\n            break;\n\n        case IOCTL_BTRFS_UNLOAD:\n            Status = ioctl_unload(Irp);\n            break;\n\n        default:\n            TRACE(\"unhandled ioctl %lx\\n\", IrpSp->Parameters.DeviceIoControl.IoControlCode);\n            Status = STATUS_NOT_IMPLEMENTED;\n            break;\n    }\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_DEVICE_CONTROL)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_device_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    NTSTATUS Status;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    top_level = is_top_level(Irp);\n\n    Irp->IoStatus.Information = 0;\n\n    if (Vcb) {\n        if (Vcb->type == VCB_TYPE_CONTROL) {\n            Status = control_ioctl(Irp);\n            goto end;\n        } else if (Vcb->type == VCB_TYPE_VOLUME) {\n            Status = vol_device_control(DeviceObject, Irp);\n            goto end;\n        } else if (Vcb->type != VCB_TYPE_FS) {\n            Status = STATUS_INVALID_PARAMETER;\n            goto end;\n        }\n    } else {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) {\n        case IOCTL_MOUNTDEV_QUERY_STABLE_GUID:\n            Status = mountdev_query_stable_guid(Vcb, Irp);\n            goto end;\n\n        case IOCTL_DISK_IS_WRITABLE:\n            Status = is_writable(Vcb);\n            goto end;\n\n        default:\n            TRACE(\"unhandled control code %lx\\n\", IrpSp->Parameters.DeviceIoControl.IoControlCode);\n            break;\n    }\n\n    IoSkipCurrentIrpStackLocation(Irp);\n\n    Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp);\n\n    goto end2;\n\nend:\n    Irp->IoStatus.Status = Status;\n\n    if (Status != STATUS_PENDING)\n        IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\nend2:\n    TRACE(\"returning %08lx\\n\", Status);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n"
  },
  {
    "path": "src/dirctrl.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include \"crc32c.h\"\n\n// not currently in mingw\n#ifndef _MSC_VER\n#define FileIdExtdDirectoryInformation (enum _FILE_INFORMATION_CLASS)60\n#define FileIdExtdBothDirectoryInformation (enum _FILE_INFORMATION_CLASS)63\n\ntypedef struct _FILE_ID_EXTD_DIR_INFORMATION {\n    ULONG NextEntryOffset;\n    ULONG FileIndex;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER EndOfFile;\n    LARGE_INTEGER AllocationSize;\n    ULONG FileAttributes;\n    ULONG FileNameLength;\n    ULONG EaSize;\n    ULONG ReparsePointTag;\n    FILE_ID_128 FileId;\n    WCHAR FileName[1];\n} FILE_ID_EXTD_DIR_INFORMATION, *PFILE_ID_EXTD_DIR_INFORMATION;\n\ntypedef struct _FILE_ID_EXTD_BOTH_DIR_INFORMATION {\n    ULONG NextEntryOffset;\n    ULONG FileIndex;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER EndOfFile;\n    LARGE_INTEGER AllocationSize;\n    ULONG FileAttributes;\n    ULONG FileNameLength;\n    ULONG EaSize;\n    ULONG ReparsePointTag;\n    FILE_ID_128 FileId;\n    CCHAR ShortNameLength;\n    WCHAR ShortName[12];\n    WCHAR FileName[1];\n} FILE_ID_EXTD_BOTH_DIR_INFORMATION, *PFILE_ID_EXTD_BOTH_DIR_INFORMATION;\n\n#endif\n\nenum DirEntryType {\n    DirEntryType_File,\n    DirEntryType_Self,\n    DirEntryType_Parent\n};\n\ntypedef struct {\n    KEY key;\n    UNICODE_STRING name;\n    uint8_t type;\n    enum DirEntryType dir_entry_type;\n    dir_child* dc;\n} dir_entry;\n\nULONG get_reparse_tag_fcb(fcb* fcb) {\n    ULONG tag;\n\n    if (fcb->type == BTRFS_TYPE_SYMLINK)\n        return IO_REPARSE_TAG_SYMLINK;\n    else if (fcb->type == BTRFS_TYPE_DIRECTORY) {\n        if (!fcb->reparse_xattr.Buffer || fcb->reparse_xattr.Length < sizeof(ULONG))\n            return 0;\n\n        RtlCopyMemory(&tag, fcb->reparse_xattr.Buffer, sizeof(ULONG));\n    } else {\n        NTSTATUS Status;\n        ULONG br;\n\n        Status = read_file(fcb, (uint8_t*)&tag, 0, sizeof(ULONG), &br, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"read_file returned %08lx\\n\", Status);\n            return 0;\n        }\n    }\n\n    return tag;\n}\n\nULONG get_reparse_tag(device_extension* Vcb, root* subvol, uint64_t inode, uint8_t type, ULONG atts, bool lxss, PIRP Irp) {\n    fcb* fcb;\n    ULONG tag = 0;\n    NTSTATUS Status;\n\n    if (type == BTRFS_TYPE_SYMLINK)\n        return IO_REPARSE_TAG_SYMLINK;\n    else if (lxss) {\n        if (type == BTRFS_TYPE_SOCKET)\n            return IO_REPARSE_TAG_AF_UNIX;\n        else if (type == BTRFS_TYPE_FIFO)\n            return IO_REPARSE_TAG_LX_FIFO;\n        else if (type == BTRFS_TYPE_CHARDEV)\n            return IO_REPARSE_TAG_LX_CHR;\n        else if (type == BTRFS_TYPE_BLOCKDEV)\n            return IO_REPARSE_TAG_LX_BLK;\n    }\n\n    if (type != BTRFS_TYPE_FILE && type != BTRFS_TYPE_DIRECTORY)\n        return 0;\n\n    if (!(atts & FILE_ATTRIBUTE_REPARSE_POINT))\n        return 0;\n\n    Status = open_fcb(Vcb, subvol, inode, type, NULL, false, NULL, &fcb, PagedPool, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"open_fcb returned %08lx\\n\", Status);\n        return 0;\n    }\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    tag = get_reparse_tag_fcb(fcb);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    free_fcb(fcb);\n\n    return tag;\n}\n\nstatic ULONG get_ea_len(device_extension* Vcb, root* subvol, uint64_t inode, PIRP Irp) {\n    uint8_t* eadata;\n    uint16_t len;\n\n    if (get_xattr(Vcb, subvol, inode, EA_EA, EA_EA_HASH, &eadata, &len, Irp)) {\n        ULONG offset;\n        NTSTATUS Status;\n\n        Status = IoCheckEaBufferValidity((FILE_FULL_EA_INFORMATION*)eadata, len, &offset);\n\n        if (!NT_SUCCESS(Status)) {\n            WARN(\"IoCheckEaBufferValidity returned %08lx (error at offset %lu)\\n\", Status, offset);\n            ExFreePool(eadata);\n            return 0;\n        } else {\n            FILE_FULL_EA_INFORMATION* eainfo;\n            ULONG ealen;\n\n            ealen = 4;\n            eainfo = (FILE_FULL_EA_INFORMATION*)eadata;\n            do {\n                ealen += 5 + eainfo->EaNameLength + eainfo->EaValueLength;\n\n                if (eainfo->NextEntryOffset == 0)\n                    break;\n\n                eainfo = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)eainfo) + eainfo->NextEntryOffset);\n            } while (true);\n\n            ExFreePool(eadata);\n\n            return ealen;\n        }\n    } else\n        return 0;\n}\n\nstatic NTSTATUS query_dir_item(fcb* fcb, ccb* ccb, void* buf, LONG* len, PIRP Irp, dir_entry* de, root* r) {\n    PIO_STACK_LOCATION IrpSp;\n    LONG needed;\n    uint64_t inode;\n    INODE_ITEM ii;\n    NTSTATUS Status;\n    ULONG atts = 0, ealen = 0;\n    file_ref* fileref = ccb->fileref;\n\n    IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n    if (de->key.obj_type == TYPE_ROOT_ITEM) { // subvol\n        LIST_ENTRY* le;\n\n        r = NULL;\n\n        le = fcb->Vcb->roots.Flink;\n        while (le != &fcb->Vcb->roots) {\n            root* subvol = CONTAINING_RECORD(le, root, list_entry);\n\n            if (subvol->id == de->key.obj_id) {\n                r = subvol;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        if (r && r->parent != fcb->subvol->id && (!de->dc || !de->dc->root_dir))\n            r = NULL;\n\n        inode = SUBVOL_ROOT_INODE;\n    } else {\n        inode = de->key.obj_id;\n    }\n\n    if (IrpSp->Parameters.QueryDirectory.FileInformationClass != FileNamesInformation) { // FIXME - object ID and reparse point classes too?\n        switch (de->dir_entry_type) {\n            case DirEntryType_File:\n            {\n                if (!r) {\n                    LARGE_INTEGER time;\n\n                    ii = fcb->Vcb->dummy_fcb->inode_item;\n                    atts = fcb->Vcb->dummy_fcb->atts;\n                    ealen = fcb->Vcb->dummy_fcb->ealen;\n\n                    KeQuerySystemTime(&time);\n                    win_time_to_unix(time, &ii.otime);\n                    ii.st_atime = ii.st_mtime = ii.st_ctime = ii.otime;\n                } else {\n                    bool found = false;\n\n                    if (de->dc && de->dc->fileref && de->dc->fileref->fcb) {\n                        ii = de->dc->fileref->fcb->inode_item;\n                        atts = de->dc->fileref->fcb->atts;\n                        ealen = de->dc->fileref->fcb->ealen;\n                        found = true;\n                    }\n\n                    if (!found) {\n                        KEY searchkey;\n                        traverse_ptr tp;\n\n                        searchkey.obj_id = inode;\n                        searchkey.obj_type = TYPE_INODE_ITEM;\n                        searchkey.offset = 0xffffffffffffffff;\n\n                        Status = find_item(fcb->Vcb, r, &tp, &searchkey, false, Irp);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"error - find_item returned %08lx\\n\", Status);\n                            return Status;\n                        }\n\n                        if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n                            ERR(\"could not find inode item for inode %I64x in root %I64x\\n\", inode, r->id);\n                            return STATUS_INTERNAL_ERROR;\n                        }\n\n                        RtlZeroMemory(&ii, sizeof(INODE_ITEM));\n\n                        if (tp.item->size > 0)\n                            RtlCopyMemory(&ii, tp.item->data, min(sizeof(INODE_ITEM), tp.item->size));\n\n                        if (IrpSp->Parameters.QueryDirectory.FileInformationClass == FileBothDirectoryInformation ||\n                            IrpSp->Parameters.QueryDirectory.FileInformationClass == FileDirectoryInformation ||\n                            IrpSp->Parameters.QueryDirectory.FileInformationClass == FileFullDirectoryInformation ||\n                            IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdBothDirectoryInformation ||\n                            IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdFullDirectoryInformation ||\n                            IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdExtdDirectoryInformation ||\n                            IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdExtdBothDirectoryInformation) {\n\n                            bool dotfile = de->name.Length > sizeof(WCHAR) && de->name.Buffer[0] == '.';\n\n                            atts = get_file_attributes(fcb->Vcb, r, inode, de->type, dotfile, false, Irp);\n                        }\n\n                        if (IrpSp->Parameters.QueryDirectory.FileInformationClass == FileBothDirectoryInformation ||\n                            IrpSp->Parameters.QueryDirectory.FileInformationClass == FileFullDirectoryInformation ||\n                            IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdBothDirectoryInformation ||\n                            IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdFullDirectoryInformation ||\n                            IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdExtdDirectoryInformation ||\n                            IrpSp->Parameters.QueryDirectory.FileInformationClass == FileIdExtdBothDirectoryInformation) {\n                            ealen = get_ea_len(fcb->Vcb, r, inode, Irp);\n                        }\n                    }\n                }\n\n                break;\n            }\n\n            case DirEntryType_Self:\n                ii = fcb->inode_item;\n                r = fcb->subvol;\n                inode = fcb->inode;\n                atts = fcb->atts;\n                ealen = fcb->ealen;\n                break;\n\n            case DirEntryType_Parent:\n                if (fileref && fileref->parent) {\n                    ii = fileref->parent->fcb->inode_item;\n                    r = fileref->parent->fcb->subvol;\n                    inode = fileref->parent->fcb->inode;\n                    atts = fileref->parent->fcb->atts;\n                    ealen = fileref->parent->fcb->ealen;\n                } else {\n                    ERR(\"no fileref\\n\");\n                    return STATUS_INTERNAL_ERROR;\n                }\n                break;\n        }\n\n        if (atts == 0)\n            atts = FILE_ATTRIBUTE_NORMAL;\n    }\n\n    switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) {\n        case FileBothDirectoryInformation:\n        {\n            FILE_BOTH_DIR_INFORMATION* fbdi = buf;\n\n            TRACE(\"FileBothDirectoryInformation\\n\");\n\n            needed = offsetof(FILE_BOTH_DIR_INFORMATION, FileName) + de->name.Length;\n\n            if (needed > *len) {\n                TRACE(\"buffer overflow - %li > %lu\\n\", needed, *len);\n                return STATUS_BUFFER_OVERFLOW;\n            }\n\n            fbdi->NextEntryOffset = 0;\n            fbdi->FileIndex = 0;\n            fbdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime);\n            fbdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime);\n            fbdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime);\n            fbdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime);\n            fbdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size;\n\n            if (de->type == BTRFS_TYPE_SYMLINK)\n                fbdi->AllocationSize.QuadPart = 0;\n            else if (atts & FILE_ATTRIBUTE_SPARSE_FILE)\n                fbdi->AllocationSize.QuadPart = ii.st_blocks;\n            else\n                fbdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size);\n\n            fbdi->FileAttributes = atts;\n            fbdi->FileNameLength = de->name.Length;\n            fbdi->EaSize = (r && atts & FILE_ATTRIBUTE_REPARSE_POINT) ? get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp) : ealen;\n            fbdi->ShortNameLength = 0;\n\n            RtlCopyMemory(fbdi->FileName, de->name.Buffer, de->name.Length);\n\n            *len -= needed;\n\n            return STATUS_SUCCESS;\n        }\n\n        case FileDirectoryInformation:\n        {\n            FILE_DIRECTORY_INFORMATION* fdi = buf;\n\n            TRACE(\"FileDirectoryInformation\\n\");\n\n            needed = offsetof(FILE_DIRECTORY_INFORMATION, FileName) + de->name.Length;\n\n            if (needed > *len) {\n                TRACE(\"buffer overflow - %li > %lu\\n\", needed, *len);\n                return STATUS_BUFFER_OVERFLOW;\n            }\n\n            fdi->NextEntryOffset = 0;\n            fdi->FileIndex = 0;\n            fdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime);\n            fdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime);\n            fdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime);\n            fdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime);\n            fdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size;\n\n            if (de->type == BTRFS_TYPE_SYMLINK)\n                fdi->AllocationSize.QuadPart = 0;\n            else if (atts & FILE_ATTRIBUTE_SPARSE_FILE)\n                fdi->AllocationSize.QuadPart = ii.st_blocks;\n            else\n                fdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size);\n\n            fdi->FileAttributes = atts;\n            fdi->FileNameLength = de->name.Length;\n\n            RtlCopyMemory(fdi->FileName, de->name.Buffer, de->name.Length);\n\n            *len -= needed;\n\n            return STATUS_SUCCESS;\n        }\n\n        case FileFullDirectoryInformation:\n        {\n            FILE_FULL_DIR_INFORMATION* ffdi = buf;\n\n            TRACE(\"FileFullDirectoryInformation\\n\");\n\n            needed = offsetof(FILE_FULL_DIR_INFORMATION, FileName) + de->name.Length;\n\n            if (needed > *len) {\n                TRACE(\"buffer overflow - %li > %lu\\n\", needed, *len);\n                return STATUS_BUFFER_OVERFLOW;\n            }\n\n            ffdi->NextEntryOffset = 0;\n            ffdi->FileIndex = 0;\n            ffdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime);\n            ffdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime);\n            ffdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime);\n            ffdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime);\n            ffdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size;\n\n            if (de->type == BTRFS_TYPE_SYMLINK)\n                ffdi->AllocationSize.QuadPart = 0;\n            else if (atts & FILE_ATTRIBUTE_SPARSE_FILE)\n                ffdi->AllocationSize.QuadPart = ii.st_blocks;\n            else\n                ffdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size);\n\n            ffdi->FileAttributes = atts;\n            ffdi->FileNameLength = de->name.Length;\n            ffdi->EaSize = (r && atts & FILE_ATTRIBUTE_REPARSE_POINT) ? get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp) : ealen;\n\n            RtlCopyMemory(ffdi->FileName, de->name.Buffer, de->name.Length);\n\n            *len -= needed;\n\n            return STATUS_SUCCESS;\n        }\n\n        case FileIdBothDirectoryInformation:\n        {\n            FILE_ID_BOTH_DIR_INFORMATION* fibdi = buf;\n\n            TRACE(\"FileIdBothDirectoryInformation\\n\");\n\n            needed = offsetof(FILE_ID_BOTH_DIR_INFORMATION, FileName) + de->name.Length;\n\n            if (needed > *len) {\n                TRACE(\"buffer overflow - %li > %lu\\n\", needed, *len);\n                return STATUS_BUFFER_OVERFLOW;\n            }\n\n            fibdi->NextEntryOffset = 0;\n            fibdi->FileIndex = 0;\n            fibdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime);\n            fibdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime);\n            fibdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime);\n            fibdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime);\n            fibdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size;\n\n            if (de->type == BTRFS_TYPE_SYMLINK)\n                fibdi->AllocationSize.QuadPart = 0;\n            else if (atts & FILE_ATTRIBUTE_SPARSE_FILE)\n                fibdi->AllocationSize.QuadPart = ii.st_blocks;\n            else\n                fibdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size);\n\n            fibdi->FileAttributes = atts;\n            fibdi->FileNameLength = de->name.Length;\n            fibdi->EaSize = (r && atts & FILE_ATTRIBUTE_REPARSE_POINT) ? get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp) : ealen;\n            fibdi->ShortNameLength = 0;\n            fibdi->FileId.QuadPart = r ? make_file_id(r, inode) : make_file_id(fcb->Vcb->dummy_fcb->subvol, fcb->Vcb->dummy_fcb->inode);\n\n            RtlCopyMemory(fibdi->FileName, de->name.Buffer, de->name.Length);\n\n            *len -= needed;\n\n            return STATUS_SUCCESS;\n        }\n\n        case FileIdFullDirectoryInformation:\n        {\n            FILE_ID_FULL_DIR_INFORMATION* fifdi = buf;\n\n            TRACE(\"FileIdFullDirectoryInformation\\n\");\n\n            needed = offsetof(FILE_ID_FULL_DIR_INFORMATION, FileName) + de->name.Length;\n\n            if (needed > *len) {\n                TRACE(\"buffer overflow - %li > %lu\\n\", needed, *len);\n                return STATUS_BUFFER_OVERFLOW;\n            }\n\n            fifdi->NextEntryOffset = 0;\n            fifdi->FileIndex = 0;\n            fifdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime);\n            fifdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime);\n            fifdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime);\n            fifdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime);\n            fifdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size;\n\n            if (de->type == BTRFS_TYPE_SYMLINK)\n                fifdi->AllocationSize.QuadPart = 0;\n            else if (atts & FILE_ATTRIBUTE_SPARSE_FILE)\n                fifdi->AllocationSize.QuadPart = ii.st_blocks;\n            else\n                fifdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size);\n\n            fifdi->FileAttributes = atts;\n            fifdi->FileNameLength = de->name.Length;\n            fifdi->EaSize = (r && atts & FILE_ATTRIBUTE_REPARSE_POINT) ? get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp) : ealen;\n            fifdi->FileId.QuadPart = r ? make_file_id(r, inode) : make_file_id(fcb->Vcb->dummy_fcb->subvol, fcb->Vcb->dummy_fcb->inode);\n\n            RtlCopyMemory(fifdi->FileName, de->name.Buffer, de->name.Length);\n\n            *len -= needed;\n\n            return STATUS_SUCCESS;\n        }\n\n#ifndef _MSC_VER\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wswitch\"\n#endif\n        case FileIdExtdDirectoryInformation:\n        {\n            FILE_ID_EXTD_DIR_INFORMATION* fiedi = buf;\n\n            TRACE(\"FileIdExtdDirectoryInformation\\n\");\n\n            needed = offsetof(FILE_ID_EXTD_DIR_INFORMATION, FileName) + de->name.Length;\n\n            if (needed > *len) {\n                TRACE(\"buffer overflow - %li > %lu\\n\", needed, *len);\n                return STATUS_BUFFER_OVERFLOW;\n            }\n\n            fiedi->NextEntryOffset = 0;\n            fiedi->FileIndex = 0;\n            fiedi->CreationTime.QuadPart = unix_time_to_win(&ii.otime);\n            fiedi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime);\n            fiedi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime);\n            fiedi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime);\n            fiedi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size;\n\n            if (de->type == BTRFS_TYPE_SYMLINK)\n                fiedi->AllocationSize.QuadPart = 0;\n            else if (atts & FILE_ATTRIBUTE_SPARSE_FILE)\n                fiedi->AllocationSize.QuadPart = ii.st_blocks;\n            else\n                fiedi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size);\n\n            fiedi->FileAttributes = atts;\n            fiedi->FileNameLength = de->name.Length;\n            fiedi->EaSize = ealen;\n            fiedi->ReparsePointTag = get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp);\n\n            RtlCopyMemory(&fiedi->FileId.Identifier[0], &fcb->inode, sizeof(uint64_t));\n            RtlCopyMemory(&fiedi->FileId.Identifier[sizeof(uint64_t)], &fcb->subvol->id, sizeof(uint64_t));\n\n            RtlCopyMemory(fiedi->FileName, de->name.Buffer, de->name.Length);\n\n            *len -= needed;\n\n            return STATUS_SUCCESS;\n        }\n\n        case FileIdExtdBothDirectoryInformation:\n        {\n            FILE_ID_EXTD_BOTH_DIR_INFORMATION* fiebdi = buf;\n\n            TRACE(\"FileIdExtdBothDirectoryInformation\\n\");\n\n            needed = offsetof(FILE_ID_EXTD_BOTH_DIR_INFORMATION, FileName) + de->name.Length;\n\n            if (needed > *len) {\n                TRACE(\"buffer overflow - %li > %lu\\n\", needed, *len);\n                return STATUS_BUFFER_OVERFLOW;\n            }\n\n            fiebdi->NextEntryOffset = 0;\n            fiebdi->FileIndex = 0;\n            fiebdi->CreationTime.QuadPart = unix_time_to_win(&ii.otime);\n            fiebdi->LastAccessTime.QuadPart = unix_time_to_win(&ii.st_atime);\n            fiebdi->LastWriteTime.QuadPart = unix_time_to_win(&ii.st_mtime);\n            fiebdi->ChangeTime.QuadPart = unix_time_to_win(&ii.st_ctime);\n            fiebdi->EndOfFile.QuadPart = de->type == BTRFS_TYPE_SYMLINK ? 0 : ii.st_size;\n\n            if (de->type == BTRFS_TYPE_SYMLINK)\n                fiebdi->AllocationSize.QuadPart = 0;\n            else if (atts & FILE_ATTRIBUTE_SPARSE_FILE)\n                fiebdi->AllocationSize.QuadPart = ii.st_blocks;\n            else\n                fiebdi->AllocationSize.QuadPart = sector_align(ii.st_size, fcb->Vcb->superblock.sector_size);\n\n            fiebdi->FileAttributes = atts;\n            fiebdi->FileNameLength = de->name.Length;\n            fiebdi->EaSize = ealen;\n            fiebdi->ReparsePointTag = get_reparse_tag(fcb->Vcb, r, inode, de->type, atts, ccb->lxss, Irp);\n\n            RtlCopyMemory(&fiebdi->FileId.Identifier[0], &fcb->inode, sizeof(uint64_t));\n            RtlCopyMemory(&fiebdi->FileId.Identifier[sizeof(uint64_t)], &fcb->subvol->id, sizeof(uint64_t));\n\n            fiebdi->ShortNameLength = 0;\n\n            RtlCopyMemory(fiebdi->FileName, de->name.Buffer, de->name.Length);\n\n            *len -= needed;\n\n            return STATUS_SUCCESS;\n        }\n\n#ifndef _MSC_VER\n#pragma GCC diagnostic pop\n#endif\n\n        case FileNamesInformation:\n        {\n            FILE_NAMES_INFORMATION* fni = buf;\n\n            TRACE(\"FileNamesInformation\\n\");\n\n            needed = offsetof(FILE_NAMES_INFORMATION, FileName) + de->name.Length;\n\n            if (needed > *len) {\n                TRACE(\"buffer overflow - %li > %lu\\n\", needed, *len);\n                return STATUS_BUFFER_OVERFLOW;\n            }\n\n            fni->NextEntryOffset = 0;\n            fni->FileIndex = 0;\n            fni->FileNameLength = de->name.Length;\n\n            RtlCopyMemory(fni->FileName, de->name.Buffer, de->name.Length);\n\n            *len -= needed;\n\n            return STATUS_SUCCESS;\n        }\n\n        default:\n            WARN(\"Unknown FileInformationClass %u\\n\", IrpSp->Parameters.QueryDirectory.FileInformationClass);\n            return STATUS_NOT_IMPLEMENTED;\n    }\n\n    return STATUS_NO_MORE_FILES;\n}\n\nstatic NTSTATUS next_dir_entry(file_ref* fileref, uint64_t* offset, dir_entry* de, dir_child** pdc) {\n    LIST_ENTRY* le;\n    dir_child* dc;\n\n    if (*pdc) {\n        dir_child* dc2 = *pdc;\n\n        if (dc2->list_entry_index.Flink != &fileref->fcb->dir_children_index)\n            dc = CONTAINING_RECORD(dc2->list_entry_index.Flink, dir_child, list_entry_index);\n        else\n            dc = NULL;\n\n        goto next;\n    }\n\n    if (fileref->parent) { // don't return . and .. if root directory\n        if (*offset == 0) {\n            de->key.obj_id = fileref->fcb->inode;\n            de->key.obj_type = TYPE_INODE_ITEM;\n            de->key.offset = 0;\n            de->dir_entry_type = DirEntryType_Self;\n            de->name.Buffer = L\".\";\n            de->name.Length = de->name.MaximumLength = sizeof(WCHAR);\n            de->type = BTRFS_TYPE_DIRECTORY;\n\n            *offset = 1;\n            *pdc = NULL;\n\n            return STATUS_SUCCESS;\n        } else if (*offset == 1) {\n            de->key.obj_id = fileref->parent->fcb->inode;\n            de->key.obj_type = TYPE_INODE_ITEM;\n            de->key.offset = 0;\n            de->dir_entry_type = DirEntryType_Parent;\n            de->name.Buffer = L\"..\";\n            de->name.Length = de->name.MaximumLength = sizeof(WCHAR) * 2;\n            de->type = BTRFS_TYPE_DIRECTORY;\n\n            *offset = 2;\n            *pdc = NULL;\n\n            return STATUS_SUCCESS;\n        }\n    }\n\n    if (*offset < 2)\n        *offset = 2;\n\n    dc = NULL;\n    le = fileref->fcb->dir_children_index.Flink;\n\n    // skip entries before offset\n    while (le != &fileref->fcb->dir_children_index) {\n        dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_index);\n\n        if (dc2->index >= *offset) {\n            dc = dc2;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\nnext:\n    if (!dc)\n        return STATUS_NO_MORE_FILES;\n\n    if (dc->root_dir && fileref->parent) { // hide $Root dir unless in apparent root, to avoid recursion\n        if (dc->list_entry_index.Flink == &fileref->fcb->dir_children_index)\n            return STATUS_NO_MORE_FILES;\n\n        dc = CONTAINING_RECORD(dc->list_entry_index.Flink, dir_child, list_entry_index);\n    }\n\n    de->key = dc->key;\n    de->name = dc->name;\n    de->type = dc->type;\n    de->dir_entry_type = DirEntryType_File;\n    de->dc = dc;\n\n    *offset = dc->index + 1;\n    *pdc = dc;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS query_directory(PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp;\n    NTSTATUS Status, status2;\n    fcb* fcb;\n    ccb* ccb;\n    file_ref* fileref;\n    device_extension* Vcb;\n    void* buf;\n    uint8_t *curitem, *lastitem;\n    LONG length;\n    ULONG count;\n    bool has_wildcard = false, specific_file = false, initial;\n    dir_entry de;\n    uint64_t newoffset;\n    dir_child* dc = NULL;\n\n    TRACE(\"query directory\\n\");\n\n    IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    fcb = IrpSp->FileObject->FsContext;\n    ccb = IrpSp->FileObject->FsContext2;\n    fileref = ccb ? ccb->fileref : NULL;\n\n    if (!fileref)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!ccb) {\n        ERR(\"ccb was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (!fcb) {\n        ERR(\"fcb was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_LIST_DIRECTORY)) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    Vcb = fcb->Vcb;\n\n    if (!Vcb) {\n        ERR(\"Vcb was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (fileref->fcb == Vcb->dummy_fcb)\n        return STATUS_NO_MORE_FILES;\n\n    if (IrpSp->Flags == 0) {\n        TRACE(\"QD flags: (none)\\n\");\n    } else {\n        ULONG flags = IrpSp->Flags;\n\n        TRACE(\"QD flags:\\n\");\n\n        if (flags & SL_INDEX_SPECIFIED) {\n            TRACE(\"    SL_INDEX_SPECIFIED\\n\");\n            flags &= ~SL_INDEX_SPECIFIED;\n        }\n\n        if (flags & SL_RESTART_SCAN) {\n            TRACE(\"    SL_RESTART_SCAN\\n\");\n            flags &= ~SL_RESTART_SCAN;\n        }\n\n        if (flags & SL_RETURN_SINGLE_ENTRY) {\n            TRACE(\"    SL_RETURN_SINGLE_ENTRY\\n\");\n            flags &= ~SL_RETURN_SINGLE_ENTRY;\n        }\n\n        if (flags != 0)\n            TRACE(\"    unknown flags: %lu\\n\", flags);\n    }\n\n    if (IrpSp->Flags & SL_RESTART_SCAN) {\n        ccb->query_dir_offset = 0;\n\n        if (ccb->query_string.Buffer) {\n            RtlFreeUnicodeString(&ccb->query_string);\n            ccb->query_string.Buffer = NULL;\n        }\n\n        ccb->has_wildcard = false;\n        ccb->specific_file = false;\n    }\n\n    initial = !ccb->query_string.Buffer;\n\n    if (IrpSp->Parameters.QueryDirectory.FileName && IrpSp->Parameters.QueryDirectory.FileName->Length > 1) {\n        TRACE(\"QD filename: %.*S\\n\", (int)(IrpSp->Parameters.QueryDirectory.FileName->Length / sizeof(WCHAR)), IrpSp->Parameters.QueryDirectory.FileName->Buffer);\n\n        if (IrpSp->Parameters.QueryDirectory.FileName->Length > sizeof(WCHAR) || IrpSp->Parameters.QueryDirectory.FileName->Buffer[0] != L'*') {\n            specific_file = true;\n\n            if (FsRtlDoesNameContainWildCards(IrpSp->Parameters.QueryDirectory.FileName)) {\n                has_wildcard = true;\n                specific_file = false;\n            } else if (!initial)\n                return STATUS_NO_MORE_FILES;\n        }\n\n        if (ccb->query_string.Buffer)\n            RtlFreeUnicodeString(&ccb->query_string);\n\n        if (has_wildcard)\n            RtlUpcaseUnicodeString(&ccb->query_string, IrpSp->Parameters.QueryDirectory.FileName, true);\n        else {\n            ccb->query_string.Buffer = ExAllocatePoolWithTag(PagedPool, IrpSp->Parameters.QueryDirectory.FileName->Length, ALLOC_TAG);\n            if (!ccb->query_string.Buffer) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            ccb->query_string.Length = ccb->query_string.MaximumLength = IrpSp->Parameters.QueryDirectory.FileName->Length;\n            RtlCopyMemory(ccb->query_string.Buffer, IrpSp->Parameters.QueryDirectory.FileName->Buffer, IrpSp->Parameters.QueryDirectory.FileName->Length);\n        }\n\n        ccb->has_wildcard = has_wildcard;\n        ccb->specific_file = specific_file;\n    } else {\n        has_wildcard = ccb->has_wildcard;\n        specific_file = ccb->specific_file;\n\n        if (!(IrpSp->Flags & SL_RESTART_SCAN)) {\n            initial = false;\n\n            if (specific_file)\n                return STATUS_NO_MORE_FILES;\n        }\n    }\n\n    if (ccb->query_string.Buffer) {\n        TRACE(\"query string = %.*S\\n\", (int)(ccb->query_string.Length / sizeof(WCHAR)), ccb->query_string.Buffer);\n    }\n\n    newoffset = ccb->query_dir_offset;\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    ExAcquireResourceSharedLite(&fileref->fcb->nonpaged->dir_children_lock, true);\n\n    Status = next_dir_entry(fileref, &newoffset, &de, &dc);\n\n    if (!NT_SUCCESS(Status)) {\n        if (Status == STATUS_NO_MORE_FILES && initial)\n            Status = STATUS_NO_SUCH_FILE;\n        goto end;\n    }\n\n    ccb->query_dir_offset = newoffset;\n\n    buf = map_user_buffer(Irp, NormalPagePriority);\n\n    if (Irp->MdlAddress && !buf) {\n        ERR(\"MmGetSystemAddressForMdlSafe returned NULL\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    length = IrpSp->Parameters.QueryDirectory.Length;\n\n    if (specific_file) {\n        bool found = false;\n        UNICODE_STRING us;\n        LIST_ENTRY* le;\n        uint32_t hash;\n        uint8_t c;\n\n        us.Buffer = NULL;\n\n        if (!ccb->case_sensitive) {\n            Status = RtlUpcaseUnicodeString(&us, &ccb->query_string, true);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n                goto end;\n            }\n\n            hash = calc_crc32c(0xffffffff, (uint8_t*)us.Buffer, us.Length);\n        } else\n            hash = calc_crc32c(0xffffffff, (uint8_t*)ccb->query_string.Buffer, ccb->query_string.Length);\n\n        c = hash >> 24;\n\n        if (ccb->case_sensitive) {\n            if (fileref->fcb->hash_ptrs[c]) {\n                le = fileref->fcb->hash_ptrs[c];\n                while (le != &fileref->fcb->dir_children_hash) {\n                    dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_hash);\n\n                    if (dc2->hash == hash) {\n                        if (dc2->name.Length == ccb->query_string.Length && RtlCompareMemory(dc2->name.Buffer, ccb->query_string.Buffer, ccb->query_string.Length) == ccb->query_string.Length) {\n                            found = true;\n\n                            de.key = dc2->key;\n                            de.name = dc2->name;\n                            de.type = dc2->type;\n                            de.dir_entry_type = DirEntryType_File;\n                            de.dc = dc2;\n\n                            break;\n                        }\n                    } else if (dc2->hash > hash)\n                        break;\n\n                    le = le->Flink;\n                }\n            }\n        } else {\n            if (fileref->fcb->hash_ptrs_uc[c]) {\n                le = fileref->fcb->hash_ptrs_uc[c];\n                while (le != &fileref->fcb->dir_children_hash_uc) {\n                    dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_hash_uc);\n\n                    if (dc2->hash_uc == hash) {\n                        if (dc2->name_uc.Length == us.Length && RtlCompareMemory(dc2->name_uc.Buffer, us.Buffer, us.Length) == us.Length) {\n                            found = true;\n\n                            de.key = dc2->key;\n                            de.name = dc2->name;\n                            de.type = dc2->type;\n                            de.dir_entry_type = DirEntryType_File;\n                            de.dc = dc2;\n\n                            break;\n                        }\n                    } else if (dc2->hash_uc > hash)\n                        break;\n\n                    le = le->Flink;\n                }\n            }\n        }\n\n        if (us.Buffer)\n            ExFreePool(us.Buffer);\n\n        if (!found) {\n            Status = STATUS_NO_SUCH_FILE;\n            goto end;\n        }\n    } else if (has_wildcard) {\n        while (!FsRtlIsNameInExpression(&ccb->query_string, &de.name, !ccb->case_sensitive, NULL)) {\n            newoffset = ccb->query_dir_offset;\n            Status = next_dir_entry(fileref, &newoffset, &de, &dc);\n\n            if (NT_SUCCESS(Status))\n                ccb->query_dir_offset = newoffset;\n            else {\n                if (Status == STATUS_NO_MORE_FILES && initial)\n                    Status = STATUS_NO_SUCH_FILE;\n\n                goto end;\n            }\n        }\n    }\n\n    TRACE(\"file(0) = %.*S\\n\", (int)(de.name.Length / sizeof(WCHAR)), de.name.Buffer);\n    TRACE(\"offset = %I64u\\n\", ccb->query_dir_offset - 1);\n\n    Status = query_dir_item(fcb, ccb, buf, &length, Irp, &de, fcb->subvol);\n\n    count = 0;\n    if (NT_SUCCESS(Status) && !(IrpSp->Flags & SL_RETURN_SINGLE_ENTRY) && !specific_file) {\n        lastitem = (uint8_t*)buf;\n\n        while (length > 0) {\n            switch (IrpSp->Parameters.QueryDirectory.FileInformationClass) {\n#ifndef _MSC_VER\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wswitch\"\n#endif\n                case FileBothDirectoryInformation:\n                case FileDirectoryInformation:\n                case FileIdBothDirectoryInformation:\n                case FileFullDirectoryInformation:\n                case FileIdFullDirectoryInformation:\n                case FileIdExtdDirectoryInformation:\n                case FileIdExtdBothDirectoryInformation:\n                    length -= length % 8;\n                    break;\n#ifndef _MSC_VER\n#pragma GCC diagnostic pop\n#endif\n\n                case FileNamesInformation:\n                    length -= length % 4;\n                    break;\n\n                default:\n                    WARN(\"unhandled file information class %u\\n\", IrpSp->Parameters.QueryDirectory.FileInformationClass);\n                    break;\n            }\n\n            if (length > 0) {\n                newoffset = ccb->query_dir_offset;\n                Status = next_dir_entry(fileref, &newoffset, &de, &dc);\n                if (NT_SUCCESS(Status)) {\n                    if (!has_wildcard || FsRtlIsNameInExpression(&ccb->query_string, &de.name, !ccb->case_sensitive, NULL)) {\n                        curitem = (uint8_t*)buf + IrpSp->Parameters.QueryDirectory.Length - length;\n                        count++;\n\n                        TRACE(\"file(%lu) %Iu = %.*S\\n\", count, curitem - (uint8_t*)buf, (int)(de.name.Length / sizeof(WCHAR)), de.name.Buffer);\n                        TRACE(\"offset = %I64u\\n\", ccb->query_dir_offset - 1);\n\n                        status2 = query_dir_item(fcb, ccb, curitem, &length, Irp, &de, fcb->subvol);\n\n                        if (NT_SUCCESS(status2)) {\n                            ULONG* lastoffset = (ULONG*)lastitem;\n\n                            *lastoffset = (ULONG)(curitem - lastitem);\n                            ccb->query_dir_offset = newoffset;\n\n                            lastitem = curitem;\n                        } else\n                            break;\n                    } else\n                        ccb->query_dir_offset = newoffset;\n                } else {\n                    if (Status == STATUS_NO_MORE_FILES)\n                        Status = STATUS_SUCCESS;\n\n                    break;\n                }\n            } else\n                break;\n        }\n    }\n\n    Irp->IoStatus.Information = IrpSp->Parameters.QueryDirectory.Length - length;\n\nend:\n    ExReleaseResourceLite(&fileref->fcb->nonpaged->dir_children_lock);\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    TRACE(\"returning %08lx\\n\", Status);\n\n    return Status;\n}\n\nstatic NTSTATUS notify_change_directory(device_extension* Vcb, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    fcb* fcb = FileObject->FsContext;\n    ccb* ccb = FileObject->FsContext2;\n    file_ref* fileref = ccb ? ccb->fileref : NULL;\n    NTSTATUS Status;\n\n    TRACE(\"IRP_MN_NOTIFY_CHANGE_DIRECTORY\\n\");\n\n    if (!ccb) {\n        ERR(\"ccb was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (!fileref) {\n        ERR(\"no fileref\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_LIST_DIRECTORY)) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (fcb->type != BTRFS_TYPE_DIRECTORY) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    // FIXME - raise exception if FCB marked for deletion?\n\n    TRACE(\"FileObject %p\\n\", FileObject);\n\n    if (ccb->filename.Length == 0) {\n        ULONG reqlen;\n\n        ccb->filename.MaximumLength = ccb->filename.Length = 0;\n\n        Status = fileref_get_filename(fileref, &ccb->filename, NULL, &reqlen);\n        if (Status == STATUS_BUFFER_OVERFLOW) {\n            ccb->filename.Buffer = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG);\n            if (!ccb->filename.Buffer) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            ccb->filename.MaximumLength = (uint16_t)reqlen;\n\n            Status = fileref_get_filename(fileref, &ccb->filename, NULL, &reqlen);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"fileref_get_filename returned %08lx\\n\", Status);\n                goto end;\n            }\n        } else {\n            ERR(\"fileref_get_filename returned %08lx\\n\", Status);\n            goto end;\n        }\n    }\n\n    FsRtlNotifyFilterChangeDirectory(Vcb->NotifySync, &Vcb->DirNotifyList, FileObject->FsContext2, (PSTRING)&ccb->filename,\n                                     IrpSp->Flags & SL_WATCH_TREE, false, IrpSp->Parameters.NotifyDirectory.CompletionFilter, Irp,\n                                     NULL, NULL, NULL);\n\n    Status = STATUS_PENDING;\n\nend:\n    ExReleaseResourceLite(fcb->Header.Resource);\n    ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_DIRECTORY_CONTROL)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_directory_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp;\n    NTSTATUS Status;\n    ULONG func;\n    bool top_level;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"directory control\\n\");\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n    Irp->IoStatus.Information = 0;\n\n    func = IrpSp->MinorFunction;\n\n    switch (func) {\n        case IRP_MN_NOTIFY_CHANGE_DIRECTORY:\n            Status = notify_change_directory(Vcb, Irp);\n            break;\n\n        case IRP_MN_QUERY_DIRECTORY:\n            Status = query_directory(Irp);\n            break;\n\n        default:\n            WARN(\"unknown minor %lu\\n\", func);\n            Status = STATUS_NOT_IMPLEMENTED;\n            Irp->IoStatus.Status = Status;\n            break;\n    }\n\n    if (Status == STATUS_PENDING)\n        goto exit;\n\nend:\n    Irp->IoStatus.Status = Status;\n\n    IoCompleteRequest(Irp, IO_DISK_INCREMENT);\n\nexit:\n    TRACE(\"returning %08lx\\n\", Status);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n"
  },
  {
    "path": "src/extent-tree.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include \"crc32c.h\"\n\ntypedef struct {\n    uint8_t type;\n\n    union {\n        EXTENT_DATA_REF edr;\n        SHARED_DATA_REF sdr;\n        TREE_BLOCK_REF tbr;\n        SHARED_BLOCK_REF sbr;\n    };\n\n    uint64_t hash;\n    LIST_ENTRY list_entry;\n} extent_ref;\n\nuint64_t get_extent_data_ref_hash2(uint64_t root, uint64_t objid, uint64_t offset) {\n    uint32_t high_crc = 0xffffffff, low_crc = 0xffffffff;\n\n    high_crc = calc_crc32c(high_crc, (uint8_t*)&root, sizeof(uint64_t));\n    low_crc = calc_crc32c(low_crc, (uint8_t*)&objid, sizeof(uint64_t));\n    low_crc = calc_crc32c(low_crc, (uint8_t*)&offset, sizeof(uint64_t));\n\n    return ((uint64_t)high_crc << 31) ^ (uint64_t)low_crc;\n}\n\nstatic __inline uint64_t get_extent_data_ref_hash(EXTENT_DATA_REF* edr) {\n    return get_extent_data_ref_hash2(edr->root, edr->objid, edr->offset);\n}\n\nstatic uint64_t get_extent_hash(uint8_t type, void* data) {\n    if (type == TYPE_EXTENT_DATA_REF) {\n        return get_extent_data_ref_hash((EXTENT_DATA_REF*)data);\n    } else if (type == TYPE_SHARED_BLOCK_REF) {\n        SHARED_BLOCK_REF* sbr = (SHARED_BLOCK_REF*)data;\n        return sbr->offset;\n    } else if (type == TYPE_SHARED_DATA_REF) {\n        SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data;\n        return sdr->offset;\n    } else if (type == TYPE_TREE_BLOCK_REF) {\n        TREE_BLOCK_REF* tbr = (TREE_BLOCK_REF*)data;\n        return tbr->offset;\n    } else {\n        ERR(\"unhandled extent type %x\\n\", type);\n        return 0;\n    }\n}\n\nstatic void free_extent_refs(LIST_ENTRY* extent_refs) {\n    while (!IsListEmpty(extent_refs)) {\n        LIST_ENTRY* le = RemoveHeadList(extent_refs);\n        extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);\n\n        ExFreePool(er);\n    }\n}\n\nstatic NTSTATUS add_shared_data_extent_ref(LIST_ENTRY* extent_refs, uint64_t parent, uint32_t count) {\n    extent_ref* er2;\n    LIST_ENTRY* le;\n\n    if (!IsListEmpty(extent_refs)) {\n        le = extent_refs->Flink;\n\n        while (le != extent_refs) {\n            extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);\n\n            if (er->type == TYPE_SHARED_DATA_REF && er->sdr.offset == parent) {\n                er->sdr.count += count;\n                return STATUS_SUCCESS;\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    er2 = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG);\n    if (!er2) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    er2->type = TYPE_SHARED_DATA_REF;\n    er2->sdr.offset = parent;\n    er2->sdr.count = count;\n\n    InsertTailList(extent_refs, &er2->list_entry);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS add_shared_block_extent_ref(LIST_ENTRY* extent_refs, uint64_t parent) {\n    extent_ref* er2;\n    LIST_ENTRY* le;\n\n    if (!IsListEmpty(extent_refs)) {\n        le = extent_refs->Flink;\n\n        while (le != extent_refs) {\n            extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);\n\n            if (er->type == TYPE_SHARED_BLOCK_REF && er->sbr.offset == parent)\n                return STATUS_SUCCESS;\n\n            le = le->Flink;\n        }\n    }\n\n    er2 = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG);\n    if (!er2) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    er2->type = TYPE_SHARED_BLOCK_REF;\n    er2->sbr.offset = parent;\n\n    InsertTailList(extent_refs, &er2->list_entry);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS add_tree_block_extent_ref(LIST_ENTRY* extent_refs, uint64_t root) {\n    extent_ref* er2;\n    LIST_ENTRY* le;\n\n    if (!IsListEmpty(extent_refs)) {\n        le = extent_refs->Flink;\n\n        while (le != extent_refs) {\n            extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);\n\n            if (er->type == TYPE_TREE_BLOCK_REF && er->tbr.offset == root)\n                return STATUS_SUCCESS;\n\n            le = le->Flink;\n        }\n    }\n\n    er2 = ExAllocatePoolWithTag(PagedPool, sizeof(extent_ref), ALLOC_TAG);\n    if (!er2) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    er2->type = TYPE_TREE_BLOCK_REF;\n    er2->tbr.offset = root;\n\n    InsertTailList(extent_refs, &er2->list_entry);\n\n    return STATUS_SUCCESS;\n}\n\nstatic void sort_extent_refs(LIST_ENTRY* extent_refs) {\n    LIST_ENTRY newlist;\n\n    if (IsListEmpty(extent_refs))\n        return;\n\n    // insertion sort\n\n    InitializeListHead(&newlist);\n\n    while (!IsListEmpty(extent_refs)) {\n        extent_ref* er = CONTAINING_RECORD(RemoveHeadList(extent_refs), extent_ref, list_entry);\n        LIST_ENTRY* le;\n        bool inserted = false;\n\n        le = newlist.Flink;\n        while (le != &newlist) {\n            extent_ref* er2 = CONTAINING_RECORD(le, extent_ref, list_entry);\n\n            if (er->type < er2->type || (er->type == er2->type && er->hash > er2->hash)) {\n                InsertHeadList(le->Blink, &er->list_entry);\n                inserted = true;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        if (!inserted)\n            InsertTailList(&newlist, &er->list_entry);\n    }\n\n    newlist.Flink->Blink = extent_refs;\n    newlist.Blink->Flink = extent_refs;\n    extent_refs->Flink = newlist.Flink;\n    extent_refs->Blink = newlist.Blink;\n}\n\nstatic NTSTATUS construct_extent_item(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t flags, LIST_ENTRY* extent_refs,\n                                      KEY* firstitem, uint8_t level, PIRP Irp) {\n    NTSTATUS Status;\n    LIST_ENTRY *le, *next_le;\n    uint64_t refcount;\n    uint16_t inline_len;\n    bool all_inline = true;\n    extent_ref* first_noninline = NULL;\n    EXTENT_ITEM* ei;\n    uint8_t* siptr;\n\n    // FIXME - write skinny extents if is tree and incompat flag set\n\n    if (IsListEmpty(extent_refs)) {\n        WARN(\"no extent refs found\\n\");\n        return STATUS_SUCCESS;\n    }\n\n    refcount = 0;\n    inline_len = sizeof(EXTENT_ITEM);\n\n    if (flags & EXTENT_ITEM_TREE_BLOCK)\n        inline_len += sizeof(EXTENT_ITEM2);\n\n    le = extent_refs->Flink;\n    while (le != extent_refs) {\n        extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);\n        uint64_t rc;\n\n        next_le = le->Flink;\n\n        rc = get_extent_data_refcount(er->type, &er->edr);\n\n        if (rc == 0) {\n            RemoveEntryList(&er->list_entry);\n\n            ExFreePool(er);\n        } else {\n            uint16_t extlen = get_extent_data_len(er->type);\n\n            refcount += rc;\n\n            er->hash = get_extent_hash(er->type, &er->edr);\n\n            if (all_inline) {\n                if ((uint16_t)(inline_len + 1 + extlen) > Vcb->superblock.node_size >> 2) {\n                    all_inline = false;\n                    first_noninline = er;\n                } else\n                    inline_len += extlen + 1;\n            }\n        }\n\n        le = next_le;\n    }\n\n    ei = ExAllocatePoolWithTag(PagedPool, inline_len, ALLOC_TAG);\n    if (!ei) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    ei->refcount = refcount;\n    ei->generation = Vcb->superblock.generation;\n    ei->flags = flags;\n\n    if (flags & EXTENT_ITEM_TREE_BLOCK) {\n        EXTENT_ITEM2* ei2 = (EXTENT_ITEM2*)&ei[1];\n\n        if (firstitem) {\n            ei2->firstitem.obj_id = firstitem->obj_id;\n            ei2->firstitem.obj_type = firstitem->obj_type;\n            ei2->firstitem.offset = firstitem->offset;\n        } else {\n            ei2->firstitem.obj_id = 0;\n            ei2->firstitem.obj_type = 0;\n            ei2->firstitem.offset = 0;\n        }\n\n        ei2->level = level;\n\n        siptr = (uint8_t*)&ei2[1];\n    } else\n        siptr = (uint8_t*)&ei[1];\n\n    sort_extent_refs(extent_refs);\n\n    le = extent_refs->Flink;\n    while (le != extent_refs) {\n        extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);\n        ULONG extlen = get_extent_data_len(er->type);\n\n        if (!all_inline && er == first_noninline)\n            break;\n\n        *siptr = er->type;\n        siptr++;\n\n        if (extlen > 0) {\n            RtlCopyMemory(siptr, &er->edr, extlen);\n            siptr += extlen;\n        }\n\n        le = le->Flink;\n    }\n\n    Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, size, ei, inline_len, NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(ei);\n        return Status;\n    }\n\n    if (!all_inline) {\n        le = &first_noninline->list_entry;\n\n        while (le != extent_refs) {\n            extent_ref* er = CONTAINING_RECORD(le, extent_ref, list_entry);\n            uint16_t len;\n            uint8_t* data;\n\n            if (er->type == TYPE_EXTENT_DATA_REF) {\n                len = sizeof(EXTENT_DATA_REF);\n\n                data = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);\n\n                if (!data) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(data, &er->edr, len);\n            } else if (er->type == TYPE_SHARED_DATA_REF) {\n                len = sizeof(uint32_t);\n\n                data = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);\n\n                if (!data) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                *((uint32_t*)data) = er->sdr.count;\n            } else {\n                len = 0;\n                data = NULL;\n            }\n\n            Status = insert_tree_item(Vcb, Vcb->extent_root, address, er->type, er->hash, data, len, NULL, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                if (data) ExFreePool(data);\n                return Status;\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS convert_old_extent(device_extension* Vcb, uint64_t address, bool tree, KEY* firstitem, uint8_t level, PIRP Irp) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp, next_tp;\n    LIST_ENTRY extent_refs;\n    uint64_t size;\n\n    InitializeListHead(&extent_refs);\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = TYPE_EXTENT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n        ERR(\"old-style extent %I64x not found\\n\", address);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    size = tp.item->key.offset;\n\n    Status = delete_tree_item(Vcb, &tp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    while (find_next_item(Vcb, &tp, &next_tp, false, Irp)) {\n        tp = next_tp;\n\n        if (tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_EXTENT_REF_V0 && tp.item->size >= sizeof(EXTENT_REF_V0)) {\n            EXTENT_REF_V0* erv0 = (EXTENT_REF_V0*)tp.item->data;\n\n            if (tree) {\n                if (tp.item->key.offset == tp.item->key.obj_id) { // top of the tree\n                    Status = add_tree_block_extent_ref(&extent_refs, erv0->root);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"add_tree_block_extent_ref returned %08lx\\n\", Status);\n                        goto end;\n                    }\n                } else {\n                    Status = add_shared_block_extent_ref(&extent_refs, tp.item->key.offset);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"add_shared_block_extent_ref returned %08lx\\n\", Status);\n                        goto end;\n                    }\n                }\n            } else {\n                Status = add_shared_data_extent_ref(&extent_refs, tp.item->key.offset, erv0->count);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"add_shared_data_extent_ref returned %08lx\\n\", Status);\n                    goto end;\n                }\n            }\n\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        if (tp.item->key.obj_id > address || tp.item->key.obj_type > TYPE_EXTENT_REF_V0)\n            break;\n    }\n\n    Status = construct_extent_item(Vcb, address, size, tree ? (EXTENT_ITEM_TREE_BLOCK | EXTENT_ITEM_SHARED_BACKREFS) : EXTENT_ITEM_DATA,\n                                   &extent_refs, firstitem, level, Irp);\n    if (!NT_SUCCESS(Status))\n        ERR(\"construct_extent_item returned %08lx\\n\", Status);\n\nend:\n    free_extent_refs(&extent_refs);\n\n    return Status;\n}\n\nNTSTATUS increase_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, uint8_t type, void* data, KEY* firstitem, uint8_t level, PIRP Irp) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    ULONG len, max_extent_item_size;\n    uint16_t datalen = get_extent_data_len(type);\n    EXTENT_ITEM* ei;\n    uint8_t* ptr;\n    uint64_t inline_rc, offset;\n    uint8_t* data2;\n    EXTENT_ITEM* newei;\n    bool skinny;\n    bool is_tree = type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF;\n\n    if (datalen == 0) {\n        ERR(\"unrecognized extent type %x\\n\", type);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    // If entry doesn't exist yet, create new inline extent item\n\n    if (tp.item->key.obj_id != searchkey.obj_id || (tp.item->key.obj_type != TYPE_EXTENT_ITEM && tp.item->key.obj_type != TYPE_METADATA_ITEM)) {\n        uint16_t eisize;\n\n        eisize = sizeof(EXTENT_ITEM);\n        if (is_tree && !(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) eisize += sizeof(EXTENT_ITEM2);\n        eisize += sizeof(uint8_t);\n        eisize += datalen;\n\n        ei = ExAllocatePoolWithTag(PagedPool, eisize, ALLOC_TAG);\n        if (!ei) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        ei->refcount = get_extent_data_refcount(type, data);\n        ei->generation = Vcb->superblock.generation;\n        ei->flags = is_tree ? EXTENT_ITEM_TREE_BLOCK : EXTENT_ITEM_DATA;\n        ptr = (uint8_t*)&ei[1];\n\n        if (is_tree && !(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) {\n            EXTENT_ITEM2* ei2 = (EXTENT_ITEM2*)ptr;\n            ei2->firstitem = *firstitem;\n            ei2->level = level;\n            ptr = (uint8_t*)&ei2[1];\n        }\n\n        *ptr = type;\n        RtlCopyMemory(ptr + 1, data, datalen);\n\n        if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA && is_tree)\n            Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, level, ei, eisize, NULL, Irp);\n        else\n            Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, size, ei, eisize, NULL, Irp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        return STATUS_SUCCESS;\n    } else if (tp.item->key.obj_id == address && tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->key.offset != size) {\n        ERR(\"extent %I64x exists, but with size %I64x rather than %I64x expected\\n\", tp.item->key.obj_id, tp.item->key.offset, size);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    skinny = tp.item->key.obj_type == TYPE_METADATA_ITEM;\n\n    if (tp.item->size == sizeof(EXTENT_ITEM_V0) && !skinny) {\n        Status = convert_old_extent(Vcb, address, is_tree, firstitem, level, Irp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"convert_old_extent returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        return increase_extent_refcount(Vcb, address, size, type, data, firstitem, level, Irp);\n    }\n\n    if (tp.item->size < sizeof(EXTENT_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    ei = (EXTENT_ITEM*)tp.item->data;\n\n    len = tp.item->size - sizeof(EXTENT_ITEM);\n    ptr = (uint8_t*)&ei[1];\n\n    if (ei->flags & EXTENT_ITEM_TREE_BLOCK && !skinny) {\n        if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        len -= sizeof(EXTENT_ITEM2);\n        ptr += sizeof(EXTENT_ITEM2);\n    }\n\n    inline_rc = 0;\n\n    // Loop through existing inline extent entries\n\n    while (len > 0) {\n        uint8_t secttype = *ptr;\n        ULONG sectlen = get_extent_data_len(secttype);\n        uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));\n\n        len--;\n\n        if (sectlen > len) {\n            ERR(\"(%I64x,%x,%I64x): %lx bytes left, expecting at least %lx\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (sectlen == 0) {\n            ERR(\"(%I64x,%x,%I64x): unrecognized extent type %x\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        // If inline extent already present, increase refcount and return\n\n        if (secttype == type) {\n            if (type == TYPE_EXTENT_DATA_REF) {\n                EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t));\n                EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data;\n\n                if (sectedr->root == edr->root && sectedr->objid == edr->objid && sectedr->offset == edr->offset) {\n                    uint32_t rc = get_extent_data_refcount(type, data);\n                    EXTENT_DATA_REF* sectedr2;\n\n                    newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);\n                    if (!newei) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    RtlCopyMemory(newei, tp.item->data, tp.item->size);\n\n                    newei->refcount += rc;\n\n                    sectedr2 = (EXTENT_DATA_REF*)((uint8_t*)newei + ((uint8_t*)sectedr - tp.item->data));\n                    sectedr2->count += rc;\n\n                    Status = delete_tree_item(Vcb, &tp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    return STATUS_SUCCESS;\n                }\n            } else if (type == TYPE_TREE_BLOCK_REF) {\n                TREE_BLOCK_REF* secttbr = (TREE_BLOCK_REF*)(ptr + sizeof(uint8_t));\n                TREE_BLOCK_REF* tbr = (TREE_BLOCK_REF*)data;\n\n                if (secttbr->offset == tbr->offset) {\n                    TRACE(\"trying to increase refcount of non-shared tree extent\\n\");\n                    return STATUS_SUCCESS;\n                }\n            } else if (type == TYPE_SHARED_BLOCK_REF) {\n                SHARED_BLOCK_REF* sectsbr = (SHARED_BLOCK_REF*)(ptr + sizeof(uint8_t));\n                SHARED_BLOCK_REF* sbr = (SHARED_BLOCK_REF*)data;\n\n                if (sectsbr->offset == sbr->offset)\n                    return STATUS_SUCCESS;\n            } else if (type == TYPE_SHARED_DATA_REF) {\n                SHARED_DATA_REF* sectsdr = (SHARED_DATA_REF*)(ptr + sizeof(uint8_t));\n                SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data;\n\n                if (sectsdr->offset == sdr->offset) {\n                    uint32_t rc = get_extent_data_refcount(type, data);\n                    SHARED_DATA_REF* sectsdr2;\n\n                    newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);\n                    if (!newei) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    RtlCopyMemory(newei, tp.item->data, tp.item->size);\n\n                    newei->refcount += rc;\n\n                    sectsdr2 = (SHARED_DATA_REF*)((uint8_t*)newei + ((uint8_t*)sectsdr - tp.item->data));\n                    sectsdr2->count += rc;\n\n                    Status = delete_tree_item(Vcb, &tp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    return STATUS_SUCCESS;\n                }\n            } else {\n                ERR(\"unhandled extent type %x\\n\", type);\n                return STATUS_INTERNAL_ERROR;\n            }\n        }\n\n        len -= sectlen;\n        ptr += sizeof(uint8_t) + sectlen;\n        inline_rc += sectcount;\n    }\n\n    offset = get_extent_hash(type, data);\n\n    max_extent_item_size = (Vcb->superblock.node_size >> 4) - sizeof(leaf_node);\n\n    // If we can, add entry as inline extent item\n\n    if (inline_rc == ei->refcount && tp.item->size + sizeof(uint8_t) + datalen < max_extent_item_size) {\n        len = tp.item->size - sizeof(EXTENT_ITEM);\n        ptr = (uint8_t*)&ei[1];\n\n        if (ei->flags & EXTENT_ITEM_TREE_BLOCK && !skinny) {\n            len -= sizeof(EXTENT_ITEM2);\n            ptr += sizeof(EXTENT_ITEM2);\n        }\n\n        // Confusingly, it appears that references are sorted forward by type (i.e. EXTENT_DATA_REFs before\n        // SHARED_DATA_REFs), but then backwards by hash...\n\n        while (len > 0) {\n            uint8_t secttype = *ptr;\n            ULONG sectlen = get_extent_data_len(secttype);\n\n            if (secttype > type)\n                break;\n\n            if (secttype == type) {\n                uint64_t sectoff = get_extent_hash(secttype, ptr + 1);\n\n                if (sectoff < offset)\n                    break;\n            }\n\n            len -= sectlen + sizeof(uint8_t);\n            ptr += sizeof(uint8_t) + sectlen;\n        }\n\n        newei = ExAllocatePoolWithTag(PagedPool, tp.item->size + sizeof(uint8_t) + datalen, ALLOC_TAG);\n        RtlCopyMemory(newei, tp.item->data, ptr - tp.item->data);\n\n        newei->refcount += get_extent_data_refcount(type, data);\n\n        if (len > 0)\n            RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data) + sizeof(uint8_t) + datalen, ptr, len);\n\n        ptr = (ptr - tp.item->data) + (uint8_t*)newei;\n\n        *ptr = type;\n        RtlCopyMemory(ptr + 1, data, datalen);\n\n        Status = delete_tree_item(Vcb, &tp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size + sizeof(uint8_t) + datalen, NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        return STATUS_SUCCESS;\n    }\n\n    // Look for existing non-inline entry, and increase refcount if found\n\n    if (inline_rc != ei->refcount) {\n        traverse_ptr tp2;\n\n        searchkey.obj_id = address;\n        searchkey.obj_type = type;\n        searchkey.offset = offset;\n\n        Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (!keycmp(tp2.item->key, searchkey)) {\n            if (type == TYPE_SHARED_DATA_REF && tp2.item->size < sizeof(uint32_t)) {\n                ERR(\"(%I64x,%x,%I64x) was %x bytes, expecting %Ix\\n\", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, tp2.item->size, sizeof(uint32_t));\n                return STATUS_INTERNAL_ERROR;\n            } else if (type != TYPE_SHARED_DATA_REF && tp2.item->size < datalen) {\n                ERR(\"(%I64x,%x,%I64x) was %x bytes, expecting %x\\n\", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, tp2.item->size, datalen);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            data2 = ExAllocatePoolWithTag(PagedPool, tp2.item->size, ALLOC_TAG);\n            if (!data2) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(data2, tp2.item->data, tp2.item->size);\n\n            if (type == TYPE_EXTENT_DATA_REF) {\n                EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data2;\n\n                edr->count += get_extent_data_refcount(type, data);\n            } else if (type == TYPE_TREE_BLOCK_REF) {\n                TRACE(\"trying to increase refcount of non-shared tree extent\\n\");\n                return STATUS_SUCCESS;\n            } else if (type == TYPE_SHARED_BLOCK_REF)\n                return STATUS_SUCCESS;\n            else if (type == TYPE_SHARED_DATA_REF) {\n                uint32_t* sdr = (uint32_t*)data2;\n\n                *sdr += get_extent_data_refcount(type, data);\n            } else {\n                ERR(\"unhandled extent type %x\\n\", type);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            Status = delete_tree_item(Vcb, &tp2);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            Status = insert_tree_item(Vcb, Vcb->extent_root, tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, data2, tp2.item->size, NULL, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);\n            if (!newei) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(newei, tp.item->data, tp.item->size);\n\n            newei->refcount += get_extent_data_refcount(type, data);\n\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            return STATUS_SUCCESS;\n        }\n    }\n\n    // Otherwise, add new non-inline entry\n\n    if (type == TYPE_SHARED_DATA_REF) {\n        SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data;\n\n        data2 = ExAllocatePoolWithTag(PagedPool, sizeof(uint32_t), ALLOC_TAG);\n        if (!data2) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        datalen = sizeof(uint32_t);\n\n        *((uint32_t*)data2) = sdr->count;\n    } else if (type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF) {\n        data2 = NULL;\n        datalen = 0;\n    } else {\n        data2 = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG);\n        if (!data2) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(data2, data, datalen);\n    }\n\n    Status = insert_tree_item(Vcb, Vcb->extent_root, address, type, offset, data2, datalen, NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);\n    if (!newei) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(newei, tp.item->data, tp.item->size);\n\n    newei->refcount += get_extent_data_refcount(type, data);\n\n    Status = delete_tree_item(Vcb, &tp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS increase_extent_refcount_data(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint64_t inode, uint64_t offset, uint32_t refcount, PIRP Irp) {\n    EXTENT_DATA_REF edr;\n\n    edr.root = root;\n    edr.objid = inode;\n    edr.offset = offset;\n    edr.count = refcount;\n\n    return increase_extent_refcount(Vcb, address, size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, Irp);\n}\n\nNTSTATUS decrease_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, uint8_t type, void* data, KEY* firstitem,\n                                  uint8_t level, uint64_t parent, bool superseded, PIRP Irp) {\n    KEY searchkey;\n    NTSTATUS Status;\n    traverse_ptr tp, tp2;\n    EXTENT_ITEM* ei;\n    ULONG len;\n    uint64_t inline_rc;\n    uint8_t* ptr;\n    uint32_t rc = data ? get_extent_data_refcount(type, data) : 1;\n    ULONG datalen = get_extent_data_len(type);\n    bool is_tree = (type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF), skinny = false;\n\n    if (is_tree && Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) {\n        searchkey.obj_id = address;\n        searchkey.obj_type = TYPE_METADATA_ITEM;\n        searchkey.offset = 0xffffffffffffffff;\n\n        Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type)\n            skinny = true;\n    }\n\n    if (!skinny) {\n        searchkey.obj_id = address;\n        searchkey.obj_type = TYPE_EXTENT_ITEM;\n        searchkey.offset = 0xffffffffffffffff;\n\n        Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n            ERR(\"could not find EXTENT_ITEM for address %I64x\\n\", address);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (tp.item->key.offset != size) {\n            ERR(\"extent %I64x had length %I64x, not %I64x as expected\\n\", address, tp.item->key.offset, size);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (tp.item->size == sizeof(EXTENT_ITEM_V0)) {\n            Status = convert_old_extent(Vcb, address, is_tree, firstitem, level, Irp);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"convert_old_extent returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            return decrease_extent_refcount(Vcb, address, size, type, data, firstitem, level, parent, superseded, Irp);\n        }\n    }\n\n    if (tp.item->size < sizeof(EXTENT_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    ei = (EXTENT_ITEM*)tp.item->data;\n\n    len = tp.item->size - sizeof(EXTENT_ITEM);\n    ptr = (uint8_t*)&ei[1];\n\n    if (ei->flags & EXTENT_ITEM_TREE_BLOCK && !skinny) {\n        if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        len -= sizeof(EXTENT_ITEM2);\n        ptr += sizeof(EXTENT_ITEM2);\n    }\n\n    if (ei->refcount < rc) {\n        ERR(\"error - extent has refcount %I64x, trying to reduce by %x\\n\", ei->refcount, rc);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    inline_rc = 0;\n\n    // Loop through inline extent entries\n\n    while (len > 0) {\n        uint8_t secttype = *ptr;\n        uint16_t sectlen = get_extent_data_len(secttype);\n        uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));\n\n        len--;\n\n        if (sectlen > len) {\n            ERR(\"(%I64x,%x,%I64x): %lx bytes left, expecting at least %x\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (sectlen == 0) {\n            ERR(\"(%I64x,%x,%I64x): unrecognized extent type %x\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (secttype == type) {\n            if (type == TYPE_EXTENT_DATA_REF) {\n                EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t));\n                EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data;\n\n                if (sectedr->root == edr->root && sectedr->objid == edr->objid && sectedr->offset == edr->offset) {\n                    uint16_t neweilen;\n                    EXTENT_ITEM* newei;\n\n                    if (ei->refcount == edr->count) {\n                        Status = delete_tree_item(Vcb, &tp);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                            return Status;\n                        }\n\n                        if (!superseded)\n                            add_checksum_entry(Vcb, address, (ULONG)(size >> Vcb->sector_shift), NULL, Irp);\n\n                        return STATUS_SUCCESS;\n                    }\n\n                    if (sectedr->count < edr->count) {\n                        ERR(\"error - extent section has refcount %x, trying to reduce by %x\\n\", sectedr->count, edr->count);\n                        return STATUS_INTERNAL_ERROR;\n                    }\n\n                    if (sectedr->count > edr->count)    // reduce section refcount\n                        neweilen = tp.item->size;\n                    else                                // remove section entirely\n                        neweilen = tp.item->size - sizeof(uint8_t) - sectlen;\n\n                    newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG);\n                    if (!newei) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    if (sectedr->count > edr->count) {\n                        EXTENT_DATA_REF* newedr = (EXTENT_DATA_REF*)((uint8_t*)newei + ((uint8_t*)sectedr - tp.item->data));\n\n                        RtlCopyMemory(newei, ei, neweilen);\n\n                        newedr->count -= rc;\n                    } else {\n                        RtlCopyMemory(newei, ei, ptr - tp.item->data);\n\n                        if (len > sectlen)\n                            RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(uint8_t), len - sectlen);\n                    }\n\n                    newei->refcount -= rc;\n\n                    Status = delete_tree_item(Vcb, &tp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, neweilen, NULL, Irp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    return STATUS_SUCCESS;\n                }\n            } else if (type == TYPE_SHARED_DATA_REF) {\n                SHARED_DATA_REF* sectsdr = (SHARED_DATA_REF*)(ptr + sizeof(uint8_t));\n                SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data;\n\n                if (sectsdr->offset == sdr->offset) {\n                    EXTENT_ITEM* newei;\n                    uint16_t neweilen;\n\n                    if (ei->refcount == sectsdr->count) {\n                        Status = delete_tree_item(Vcb, &tp);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                            return Status;\n                        }\n\n                        if (!superseded)\n                            add_checksum_entry(Vcb, address, (ULONG)(size >> Vcb->sector_shift), NULL, Irp);\n\n                        return STATUS_SUCCESS;\n                    }\n\n                    if (sectsdr->count < sdr->count) {\n                        ERR(\"error - SHARED_DATA_REF has refcount %x, trying to reduce by %x\\n\", sectsdr->count, sdr->count);\n                        return STATUS_INTERNAL_ERROR;\n                    }\n\n                    if (sectsdr->count > sdr->count)    // reduce section refcount\n                        neweilen = tp.item->size;\n                    else                                // remove section entirely\n                        neweilen = tp.item->size - sizeof(uint8_t) - sectlen;\n\n                    newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG);\n                    if (!newei) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    if (sectsdr->count > sdr->count) {\n                        SHARED_DATA_REF* newsdr = (SHARED_DATA_REF*)((uint8_t*)newei + ((uint8_t*)sectsdr - tp.item->data));\n\n                        RtlCopyMemory(newei, ei, neweilen);\n\n                        newsdr->count -= rc;\n                    } else {\n                        RtlCopyMemory(newei, ei, ptr - tp.item->data);\n\n                        if (len > sectlen)\n                            RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(uint8_t), len - sectlen);\n                    }\n\n                    newei->refcount -= rc;\n\n                    Status = delete_tree_item(Vcb, &tp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, neweilen, NULL, Irp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    return STATUS_SUCCESS;\n                }\n            } else if (type == TYPE_TREE_BLOCK_REF) {\n                TREE_BLOCK_REF* secttbr = (TREE_BLOCK_REF*)(ptr + sizeof(uint8_t));\n                TREE_BLOCK_REF* tbr = (TREE_BLOCK_REF*)data;\n\n                if (secttbr->offset == tbr->offset) {\n                    EXTENT_ITEM* newei;\n                    uint16_t neweilen;\n\n                    if (ei->refcount == 1) {\n                        Status = delete_tree_item(Vcb, &tp);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                            return Status;\n                        }\n\n                        return STATUS_SUCCESS;\n                    }\n\n                    neweilen = tp.item->size - sizeof(uint8_t) - sectlen;\n\n                    newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG);\n                    if (!newei) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    RtlCopyMemory(newei, ei, ptr - tp.item->data);\n\n                    if (len > sectlen)\n                        RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(uint8_t), len - sectlen);\n\n                    newei->refcount--;\n\n                    Status = delete_tree_item(Vcb, &tp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, neweilen, NULL, Irp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    return STATUS_SUCCESS;\n                }\n            } else if (type == TYPE_SHARED_BLOCK_REF) {\n                SHARED_BLOCK_REF* sectsbr = (SHARED_BLOCK_REF*)(ptr + sizeof(uint8_t));\n                SHARED_BLOCK_REF* sbr = (SHARED_BLOCK_REF*)data;\n\n                if (sectsbr->offset == sbr->offset) {\n                    EXTENT_ITEM* newei;\n                    uint16_t neweilen;\n\n                    if (ei->refcount == 1) {\n                        Status = delete_tree_item(Vcb, &tp);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                            return Status;\n                        }\n\n                        return STATUS_SUCCESS;\n                    }\n\n                    neweilen = tp.item->size - sizeof(uint8_t) - sectlen;\n\n                    newei = ExAllocatePoolWithTag(PagedPool, neweilen, ALLOC_TAG);\n                    if (!newei) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    RtlCopyMemory(newei, ei, ptr - tp.item->data);\n\n                    if (len > sectlen)\n                        RtlCopyMemory((uint8_t*)newei + (ptr - tp.item->data), ptr + sectlen + sizeof(uint8_t), len - sectlen);\n\n                    newei->refcount--;\n\n                    Status = delete_tree_item(Vcb, &tp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, neweilen, NULL, Irp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    return STATUS_SUCCESS;\n                }\n            } else {\n                ERR(\"unhandled extent type %x\\n\", type);\n                return STATUS_INTERNAL_ERROR;\n            }\n        }\n\n        len -= sectlen;\n        ptr += sizeof(uint8_t) + sectlen;\n        inline_rc += sectcount;\n    }\n\n    if (inline_rc == ei->refcount) {\n        ERR(\"entry not found in inline extent item for address %I64x\\n\", address);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (type == TYPE_SHARED_DATA_REF)\n        datalen = sizeof(uint32_t);\n    else if (type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF)\n        datalen = 0;\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = type;\n    searchkey.offset = (type == TYPE_SHARED_DATA_REF || type == TYPE_EXTENT_REF_V0) ? parent : get_extent_hash(type, data);\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp2, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (keycmp(tp2.item->key, searchkey)) {\n        ERR(\"(%I64x,%x,%I64x) not found\\n\", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (tp2.item->size < datalen) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %lu\\n\", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, tp2.item->size, datalen);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (type == TYPE_EXTENT_DATA_REF) {\n        EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)tp2.item->data;\n        EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)data;\n\n        if (sectedr->root == edr->root && sectedr->objid == edr->objid && sectedr->offset == edr->offset) {\n            EXTENT_ITEM* newei;\n\n            if (ei->refcount == edr->count) {\n                Status = delete_tree_item(Vcb, &tp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                Status = delete_tree_item(Vcb, &tp2);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                if (!superseded)\n                    add_checksum_entry(Vcb, address, (ULONG)(size >> Vcb->sector_shift), NULL, Irp);\n\n                return STATUS_SUCCESS;\n            }\n\n            if (sectedr->count < edr->count) {\n                ERR(\"error - extent section has refcount %x, trying to reduce by %x\\n\", sectedr->count, edr->count);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            Status = delete_tree_item(Vcb, &tp2);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (sectedr->count > edr->count) {\n                EXTENT_DATA_REF* newedr = ExAllocatePoolWithTag(PagedPool, tp2.item->size, ALLOC_TAG);\n\n                if (!newedr) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(newedr, sectedr, tp2.item->size);\n\n                newedr->count -= edr->count;\n\n                Status = insert_tree_item(Vcb, Vcb->extent_root, tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, newedr, tp2.item->size, NULL, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n\n            newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);\n            if (!newei) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(newei, tp.item->data, tp.item->size);\n\n            newei->refcount -= rc;\n\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            return STATUS_SUCCESS;\n        } else {\n            ERR(\"error - hash collision?\\n\");\n            return STATUS_INTERNAL_ERROR;\n        }\n    } else if (type == TYPE_SHARED_DATA_REF) {\n        SHARED_DATA_REF* sdr = (SHARED_DATA_REF*)data;\n\n        if (tp2.item->key.offset == sdr->offset) {\n            uint32_t* sectsdrcount = (uint32_t*)tp2.item->data;\n            EXTENT_ITEM* newei;\n\n            if (ei->refcount == sdr->count) {\n                Status = delete_tree_item(Vcb, &tp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                Status = delete_tree_item(Vcb, &tp2);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                if (!superseded)\n                    add_checksum_entry(Vcb, address, (ULONG)(size >> Vcb->sector_shift), NULL, Irp);\n\n                return STATUS_SUCCESS;\n            }\n\n            if (*sectsdrcount < sdr->count) {\n                ERR(\"error - extent section has refcount %x, trying to reduce by %x\\n\", *sectsdrcount, sdr->count);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            Status = delete_tree_item(Vcb, &tp2);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (*sectsdrcount > sdr->count) {\n                uint32_t* newsdr = ExAllocatePoolWithTag(PagedPool, tp2.item->size, ALLOC_TAG);\n\n                if (!newsdr) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                *newsdr = *sectsdrcount - sdr->count;\n\n                Status = insert_tree_item(Vcb, Vcb->extent_root, tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset, newsdr, tp2.item->size, NULL, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n\n            newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);\n            if (!newei) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(newei, tp.item->data, tp.item->size);\n\n            newei->refcount -= rc;\n\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            return STATUS_SUCCESS;\n        } else {\n            ERR(\"error - collision?\\n\");\n            return STATUS_INTERNAL_ERROR;\n        }\n    } else if (type == TYPE_TREE_BLOCK_REF || type == TYPE_SHARED_BLOCK_REF) {\n        EXTENT_ITEM* newei;\n\n        if (ei->refcount == 1) {\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            Status = delete_tree_item(Vcb, &tp2);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            return STATUS_SUCCESS;\n        }\n\n        Status = delete_tree_item(Vcb, &tp2);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);\n        if (!newei) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(newei, tp.item->data, tp.item->size);\n\n        newei->refcount -= rc;\n\n        Status = delete_tree_item(Vcb, &tp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        return STATUS_SUCCESS;\n    } else if (type == TYPE_EXTENT_REF_V0) {\n        EXTENT_REF_V0* erv0 = (EXTENT_REF_V0*)tp2.item->data;\n        EXTENT_ITEM* newei;\n\n        if (ei->refcount == erv0->count) {\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            Status = delete_tree_item(Vcb, &tp2);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (!superseded)\n                add_checksum_entry(Vcb, address, (ULONG)(size >> Vcb->sector_shift), NULL, Irp);\n\n            return STATUS_SUCCESS;\n        }\n\n        Status = delete_tree_item(Vcb, &tp2);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        newei = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);\n        if (!newei) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(newei, tp.item->data, tp.item->size);\n\n        newei->refcount -= rc;\n\n        Status = delete_tree_item(Vcb, &tp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newei, tp.item->size, NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        return STATUS_SUCCESS;\n    } else {\n        ERR(\"unhandled extent type %x\\n\", type);\n        return STATUS_INTERNAL_ERROR;\n    }\n}\n\nNTSTATUS decrease_extent_refcount_data(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint64_t inode,\n                                       uint64_t offset, uint32_t refcount, bool superseded, PIRP Irp) {\n    EXTENT_DATA_REF edr;\n\n    edr.root = root;\n    edr.objid = inode;\n    edr.offset = offset;\n    edr.count = refcount;\n\n    return decrease_extent_refcount(Vcb, address, size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, 0, superseded, Irp);\n}\n\nNTSTATUS decrease_extent_refcount_tree(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root,\n                                       uint8_t level, PIRP Irp) {\n    TREE_BLOCK_REF tbr;\n\n    tbr.offset = root;\n\n    return decrease_extent_refcount(Vcb, address, size, TYPE_TREE_BLOCK_REF, &tbr, NULL/*FIXME*/, level, 0, false, Irp);\n}\n\nstatic uint32_t find_extent_data_refcount(device_extension* Vcb, uint64_t address, uint64_t size, uint64_t root, uint64_t objid, uint64_t offset, PIRP Irp) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = TYPE_EXTENT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return 0;\n    }\n\n    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n        TRACE(\"could not find address %I64x in extent tree\\n\", address);\n        return 0;\n    }\n\n    if (tp.item->key.offset != size) {\n        ERR(\"extent %I64x had size %I64x, not %I64x as expected\\n\", address, tp.item->key.offset, size);\n        return 0;\n    }\n\n    if (tp.item->size >= sizeof(EXTENT_ITEM)) {\n        EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data;\n        uint32_t len = tp.item->size - sizeof(EXTENT_ITEM);\n        uint8_t* ptr = (uint8_t*)&ei[1];\n\n        while (len > 0) {\n            uint8_t secttype = *ptr;\n            ULONG sectlen = get_extent_data_len(secttype);\n            uint32_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));\n\n            len--;\n\n            if (sectlen > len) {\n                ERR(\"(%I64x,%x,%I64x): %x bytes left, expecting at least %lx\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);\n                return 0;\n            }\n\n            if (sectlen == 0) {\n                ERR(\"(%I64x,%x,%I64x): unrecognized extent type %x\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);\n                return 0;\n            }\n\n            if (secttype == TYPE_EXTENT_DATA_REF) {\n                EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t));\n\n                if (sectedr->root == root && sectedr->objid == objid && sectedr->offset == offset)\n                    return sectcount;\n            }\n\n            len -= sectlen;\n            ptr += sizeof(uint8_t) + sectlen;\n        }\n    }\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = TYPE_EXTENT_DATA_REF;\n    searchkey.offset = get_extent_data_ref_hash2(root, objid, offset);\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return 0;\n    }\n\n    if (!keycmp(searchkey, tp.item->key)) {\n        if (tp.item->size < sizeof(EXTENT_DATA_REF))\n            ERR(\"(%I64x,%x,%I64x) has size %u, not %Iu as expected\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA_REF));\n        else {\n            EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)tp.item->data;\n\n            return edr->count;\n        }\n    }\n\n    return 0;\n}\n\nuint64_t get_extent_refcount(device_extension* Vcb, uint64_t address, uint64_t size, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n    EXTENT_ITEM* ei;\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return 0;\n    }\n\n    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA && tp.item->key.obj_id == address &&\n        tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) {\n        ei = (EXTENT_ITEM*)tp.item->data;\n\n        return ei->refcount;\n    }\n\n    if (tp.item->key.obj_id != address || tp.item->key.obj_type != TYPE_EXTENT_ITEM) {\n        ERR(\"couldn't find (%I64x,%x,%I64x) in extent tree\\n\", address, TYPE_EXTENT_ITEM, size);\n        return 0;\n    } else if (tp.item->key.offset != size) {\n        ERR(\"extent %I64x had size %I64x, not %I64x as expected\\n\", address, tp.item->key.offset, size);\n        return 0;\n    }\n\n    if (tp.item->size == sizeof(EXTENT_ITEM_V0)) {\n        EXTENT_ITEM_V0* eiv0 = (EXTENT_ITEM_V0*)tp.item->data;\n\n        return eiv0->refcount;\n    } else if (tp.item->size < sizeof(EXTENT_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x) was %x bytes, expected at least %Ix\\n\", tp.item->key.obj_id, tp.item->key.obj_type,\n                                                                      tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n        return 0;\n    }\n\n    ei = (EXTENT_ITEM*)tp.item->data;\n\n    return ei->refcount;\n}\n\nbool is_extent_unique(device_extension* Vcb, uint64_t address, uint64_t size, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp, next_tp;\n    NTSTATUS Status;\n    uint64_t rc, rcrun, root = 0, inode = 0, offset = 0;\n    uint32_t len;\n    EXTENT_ITEM* ei;\n    uint8_t* ptr;\n    bool b;\n\n    rc = get_extent_refcount(Vcb, address, size, Irp);\n\n    if (rc == 1)\n        return true;\n\n    if (rc == 0)\n        return false;\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = TYPE_EXTENT_ITEM;\n    searchkey.offset = size;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        WARN(\"error - find_item returned %08lx\\n\", Status);\n        return false;\n    }\n\n    if (keycmp(tp.item->key, searchkey)) {\n        WARN(\"could not find (%I64x,%x,%I64x)\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n        return false;\n    }\n\n    if (tp.item->size == sizeof(EXTENT_ITEM_V0))\n        return false;\n\n    if (tp.item->size < sizeof(EXTENT_ITEM)) {\n        WARN(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n        return false;\n    }\n\n    ei = (EXTENT_ITEM*)tp.item->data;\n\n    len = tp.item->size - sizeof(EXTENT_ITEM);\n    ptr = (uint8_t*)&ei[1];\n\n    if (ei->flags & EXTENT_ITEM_TREE_BLOCK) {\n        if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) {\n            WARN(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));\n            return false;\n        }\n\n        len -= sizeof(EXTENT_ITEM2);\n        ptr += sizeof(EXTENT_ITEM2);\n    }\n\n    rcrun = 0;\n\n    // Loop through inline extent entries\n\n    while (len > 0) {\n        uint8_t secttype = *ptr;\n        ULONG sectlen = get_extent_data_len(secttype);\n        uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));\n\n        len--;\n\n        if (sectlen > len) {\n            WARN(\"(%I64x,%x,%I64x): %x bytes left, expecting at least %lx\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);\n            return false;\n        }\n\n        if (sectlen == 0) {\n            WARN(\"(%I64x,%x,%I64x): unrecognized extent type %x\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);\n            return false;\n        }\n\n        if (secttype == TYPE_EXTENT_DATA_REF) {\n            EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t));\n\n            if (root == 0 && inode == 0) {\n                root = sectedr->root;\n                inode = sectedr->objid;\n                offset = sectedr->offset;\n            } else if (root != sectedr->root || inode != sectedr->objid || offset != sectedr->offset)\n                return false;\n        } else\n            return false;\n\n        len -= sectlen;\n        ptr += sizeof(uint8_t) + sectlen;\n        rcrun += sectcount;\n    }\n\n    if (rcrun == rc)\n        return true;\n\n    // Loop through non-inlines if some refs still unaccounted for\n\n    do {\n        b = find_next_item(Vcb, &tp, &next_tp, false, Irp);\n\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == TYPE_EXTENT_DATA_REF) {\n            EXTENT_DATA_REF* edr = (EXTENT_DATA_REF*)tp.item->data;\n\n            if (tp.item->size < sizeof(EXTENT_DATA_REF)) {\n                WARN(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n                     tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));\n                return false;\n            }\n\n            if (root == 0 && inode == 0) {\n                root = edr->root;\n                inode = edr->objid;\n                offset = edr->offset;\n            } else if (root != edr->root || inode != edr->objid || offset != edr->offset)\n                return false;\n\n            rcrun += edr->count;\n        }\n\n        if (rcrun == rc)\n            return true;\n\n        if (b) {\n            tp = next_tp;\n\n            if (tp.item->key.obj_id > searchkey.obj_id)\n                break;\n        }\n    } while (b);\n\n    // If we reach this point, there's still some refs unaccounted for somewhere.\n    // Return false in case we mess things up elsewhere.\n\n    return false;\n}\n\nuint64_t get_extent_flags(device_extension* Vcb, uint64_t address, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n    EXTENT_ITEM* ei;\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return 0;\n    }\n\n    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA && tp.item->key.obj_id == address &&\n        tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) {\n        ei = (EXTENT_ITEM*)tp.item->data;\n\n        return ei->flags;\n    }\n\n    if (tp.item->key.obj_id != address || tp.item->key.obj_type != TYPE_EXTENT_ITEM) {\n        ERR(\"couldn't find %I64x in extent tree\\n\", address);\n        return 0;\n    }\n\n    if (tp.item->size == sizeof(EXTENT_ITEM_V0))\n        return 0;\n    else if (tp.item->size < sizeof(EXTENT_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x) was %x bytes, expected at least %Ix\\n\", tp.item->key.obj_id, tp.item->key.obj_type,\n                                                                      tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n        return 0;\n    }\n\n    ei = (EXTENT_ITEM*)tp.item->data;\n\n    return ei->flags;\n}\n\nvoid update_extent_flags(device_extension* Vcb, uint64_t address, uint64_t flags, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n    EXTENT_ITEM* ei;\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return;\n    }\n\n    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA && tp.item->key.obj_id == address &&\n        tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->size >= sizeof(EXTENT_ITEM)) {\n        ei = (EXTENT_ITEM*)tp.item->data;\n        ei->flags = flags;\n        return;\n    }\n\n    if (tp.item->key.obj_id != address || tp.item->key.obj_type != TYPE_EXTENT_ITEM) {\n        ERR(\"couldn't find %I64x in extent tree\\n\", address);\n        return;\n    }\n\n    if (tp.item->size == sizeof(EXTENT_ITEM_V0))\n        return;\n    else if (tp.item->size < sizeof(EXTENT_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x) was %x bytes, expected at least %Ix\\n\", tp.item->key.obj_id, tp.item->key.obj_type,\n                                                                      tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n        return;\n    }\n\n    ei = (EXTENT_ITEM*)tp.item->data;\n    ei->flags = flags;\n}\n\nstatic changed_extent* get_changed_extent_item(chunk* c, uint64_t address, uint64_t size, bool no_csum) {\n    LIST_ENTRY* le;\n    changed_extent* ce;\n\n    le = c->changed_extents.Flink;\n    while (le != &c->changed_extents) {\n        ce = CONTAINING_RECORD(le, changed_extent, list_entry);\n\n        if (ce->address == address && ce->size == size)\n            return ce;\n\n        le = le->Flink;\n    }\n\n    ce = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent), ALLOC_TAG);\n    if (!ce) {\n        ERR(\"out of memory\\n\");\n        return NULL;\n    }\n\n    ce->address = address;\n    ce->size = size;\n    ce->old_size = size;\n    ce->count = 0;\n    ce->old_count = 0;\n    ce->no_csum = no_csum;\n    ce->superseded = false;\n    InitializeListHead(&ce->refs);\n    InitializeListHead(&ce->old_refs);\n\n    InsertTailList(&c->changed_extents, &ce->list_entry);\n\n    return ce;\n}\n\nNTSTATUS update_changed_extent_ref(device_extension* Vcb, chunk* c, uint64_t address, uint64_t size, uint64_t root, uint64_t objid, uint64_t offset, int32_t count,\n                                   bool no_csum, bool superseded, PIRP Irp) {\n    LIST_ENTRY* le;\n    changed_extent* ce;\n    changed_extent_ref* cer;\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    uint32_t old_count;\n\n    ExAcquireResourceExclusiveLite(&c->changed_extents_lock, true);\n\n    ce = get_changed_extent_item(c, address, size, no_csum);\n\n    if (!ce) {\n        ERR(\"get_changed_extent_item failed\\n\");\n        Status = STATUS_INTERNAL_ERROR;\n        goto end;\n    }\n\n    if (IsListEmpty(&ce->refs) && IsListEmpty(&ce->old_refs)) { // new entry\n        searchkey.obj_id = address;\n        searchkey.obj_type = TYPE_EXTENT_ITEM;\n        searchkey.offset = 0xffffffffffffffff;\n\n        Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n            ERR(\"could not find address %I64x in extent tree\\n\", address);\n            Status = STATUS_INTERNAL_ERROR;\n            goto end;\n        }\n\n        if (tp.item->key.offset != size) {\n            ERR(\"extent %I64x had size %I64x, not %I64x as expected\\n\", address, tp.item->key.offset, size);\n            Status = STATUS_INTERNAL_ERROR;\n            goto end;\n        }\n\n        if (tp.item->size == sizeof(EXTENT_ITEM_V0)) {\n            EXTENT_ITEM_V0* eiv0 = (EXTENT_ITEM_V0*)tp.item->data;\n\n            ce->count = ce->old_count = eiv0->refcount;\n        } else if (tp.item->size >= sizeof(EXTENT_ITEM)) {\n            EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data;\n\n            ce->count = ce->old_count = ei->refcount;\n        } else {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n            Status = STATUS_INTERNAL_ERROR;\n            goto end;\n        }\n    }\n\n    le = ce->refs.Flink;\n    while (le != &ce->refs) {\n        cer = CONTAINING_RECORD(le, changed_extent_ref, list_entry);\n\n        if (cer->type == TYPE_EXTENT_DATA_REF && cer->edr.root == root && cer->edr.objid == objid && cer->edr.offset == offset) {\n            ce->count += count;\n            cer->edr.count += count;\n            Status = STATUS_SUCCESS;\n\n            if (superseded)\n                ce->superseded = true;\n\n            goto end;\n        }\n\n        le = le->Flink;\n    }\n\n    old_count = find_extent_data_refcount(Vcb, address, size, root, objid, offset, Irp);\n\n    if (old_count > 0) {\n        cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG);\n\n        if (!cer) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        cer->type = TYPE_EXTENT_DATA_REF;\n        cer->edr.root = root;\n        cer->edr.objid = objid;\n        cer->edr.offset = offset;\n        cer->edr.count = old_count;\n\n        InsertTailList(&ce->old_refs, &cer->list_entry);\n    }\n\n    cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG);\n\n    if (!cer) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    cer->type = TYPE_EXTENT_DATA_REF;\n    cer->edr.root = root;\n    cer->edr.objid = objid;\n    cer->edr.offset = offset;\n    cer->edr.count = old_count + count;\n\n    InsertTailList(&ce->refs, &cer->list_entry);\n\n    ce->count += count;\n\n    if (superseded)\n        ce->superseded = true;\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&c->changed_extents_lock);\n\n    return Status;\n}\n\nvoid add_changed_extent_ref(chunk* c, uint64_t address, uint64_t size, uint64_t root, uint64_t objid, uint64_t offset, uint32_t count, bool no_csum) {\n    changed_extent* ce;\n    changed_extent_ref* cer;\n    LIST_ENTRY* le;\n\n    ce = get_changed_extent_item(c, address, size, no_csum);\n\n    if (!ce) {\n        ERR(\"get_changed_extent_item failed\\n\");\n        return;\n    }\n\n    le = ce->refs.Flink;\n    while (le != &ce->refs) {\n        cer = CONTAINING_RECORD(le, changed_extent_ref, list_entry);\n\n        if (cer->type == TYPE_EXTENT_DATA_REF && cer->edr.root == root && cer->edr.objid == objid && cer->edr.offset == offset) {\n            ce->count += count;\n            cer->edr.count += count;\n            return;\n        }\n\n        le = le->Flink;\n    }\n\n    cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG);\n\n    if (!cer) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    cer->type = TYPE_EXTENT_DATA_REF;\n    cer->edr.root = root;\n    cer->edr.objid = objid;\n    cer->edr.offset = offset;\n    cer->edr.count = count;\n\n    InsertTailList(&ce->refs, &cer->list_entry);\n\n    ce->count += count;\n}\n\nuint64_t find_extent_shared_tree_refcount(device_extension* Vcb, uint64_t address, uint64_t parent, PIRP Irp) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    uint64_t inline_rc;\n    EXTENT_ITEM* ei;\n    uint32_t len;\n    uint8_t* ptr;\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return 0;\n    }\n\n    if (tp.item->key.obj_id != searchkey.obj_id || (tp.item->key.obj_type != TYPE_EXTENT_ITEM && tp.item->key.obj_type != TYPE_METADATA_ITEM)) {\n        TRACE(\"could not find address %I64x in extent tree\\n\", address);\n        return 0;\n    }\n\n    if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->key.offset != Vcb->superblock.node_size) {\n        ERR(\"extent %I64x had size %I64x, not %x as expected\\n\", address, tp.item->key.offset, Vcb->superblock.node_size);\n        return 0;\n    }\n\n    if (tp.item->size < sizeof(EXTENT_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x): size was %u, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n        return 0;\n    }\n\n    ei = (EXTENT_ITEM*)tp.item->data;\n    inline_rc = 0;\n\n    len = tp.item->size - sizeof(EXTENT_ITEM);\n    ptr = (uint8_t*)&ei[1];\n\n    if (searchkey.obj_type == TYPE_EXTENT_ITEM && ei->flags & EXTENT_ITEM_TREE_BLOCK) {\n        if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) {\n            ERR(\"(%I64x,%x,%I64x): size was %u, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n                                                                          tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));\n            return 0;\n        }\n\n        len -= sizeof(EXTENT_ITEM2);\n        ptr += sizeof(EXTENT_ITEM2);\n    }\n\n    while (len > 0) {\n        uint8_t secttype = *ptr;\n        ULONG sectlen = get_extent_data_len(secttype);\n        uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));\n\n        len--;\n\n        if (sectlen > len) {\n            ERR(\"(%I64x,%x,%I64x): %x bytes left, expecting at least %lx\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);\n            return 0;\n        }\n\n        if (sectlen == 0) {\n            ERR(\"(%I64x,%x,%I64x): unrecognized extent type %x\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);\n            return 0;\n        }\n\n        if (secttype == TYPE_SHARED_BLOCK_REF) {\n            SHARED_BLOCK_REF* sectsbr = (SHARED_BLOCK_REF*)(ptr + sizeof(uint8_t));\n\n            if (sectsbr->offset == parent)\n                return 1;\n        }\n\n        len -= sectlen;\n        ptr += sizeof(uint8_t) + sectlen;\n        inline_rc += sectcount;\n    }\n\n    // FIXME - what if old?\n\n    if (inline_rc == ei->refcount)\n        return 0;\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = TYPE_SHARED_BLOCK_REF;\n    searchkey.offset = parent;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return 0;\n    }\n\n    if (!keycmp(searchkey, tp.item->key))\n        return 1;\n\n    return 0;\n}\n\nuint32_t find_extent_shared_data_refcount(device_extension* Vcb, uint64_t address, uint64_t parent, PIRP Irp) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    uint64_t inline_rc;\n    EXTENT_ITEM* ei;\n    uint32_t len;\n    uint8_t* ptr;\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return 0;\n    }\n\n    if (tp.item->key.obj_id != searchkey.obj_id || (tp.item->key.obj_type != TYPE_EXTENT_ITEM && tp.item->key.obj_type != TYPE_METADATA_ITEM)) {\n        TRACE(\"could not find address %I64x in extent tree\\n\", address);\n        return 0;\n    }\n\n    if (tp.item->size < sizeof(EXTENT_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x): size was %u, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n        return 0;\n    }\n\n    ei = (EXTENT_ITEM*)tp.item->data;\n    inline_rc = 0;\n\n    len = tp.item->size - sizeof(EXTENT_ITEM);\n    ptr = (uint8_t*)&ei[1];\n\n    while (len > 0) {\n        uint8_t secttype = *ptr;\n        ULONG sectlen = get_extent_data_len(secttype);\n        uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));\n\n        len--;\n\n        if (sectlen > len) {\n            ERR(\"(%I64x,%x,%I64x): %x bytes left, expecting at least %lx\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);\n            return 0;\n        }\n\n        if (sectlen == 0) {\n            ERR(\"(%I64x,%x,%I64x): unrecognized extent type %x\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);\n            return 0;\n        }\n\n        if (secttype == TYPE_SHARED_DATA_REF) {\n            SHARED_DATA_REF* sectsdr = (SHARED_DATA_REF*)(ptr + sizeof(uint8_t));\n\n            if (sectsdr->offset == parent)\n                return sectsdr->count;\n        }\n\n        len -= sectlen;\n        ptr += sizeof(uint8_t) + sectlen;\n        inline_rc += sectcount;\n    }\n\n    // FIXME - what if old?\n\n    if (inline_rc == ei->refcount)\n        return 0;\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = TYPE_SHARED_DATA_REF;\n    searchkey.offset = parent;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return 0;\n    }\n\n    if (!keycmp(searchkey, tp.item->key)) {\n        if (tp.item->size < sizeof(uint32_t))\n            ERR(\"(%I64x,%x,%I64x) has size %u, not %Iu as expected\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(uint32_t));\n        else {\n            uint32_t* count = (uint32_t*)tp.item->data;\n            return *count;\n        }\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/fastio.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n\nFAST_IO_DISPATCH FastIoDispatch;\n\n_Function_class_(FAST_IO_QUERY_BASIC_INFO)\nstatic BOOLEAN __stdcall fast_query_basic_info(PFILE_OBJECT FileObject, BOOLEAN wait, PFILE_BASIC_INFORMATION fbi,\n                                               PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) {\n    fcb* fcb;\n    ccb* ccb;\n\n    UNUSED(DeviceObject);\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"(%p, %u, %p, %p, %p)\\n\", FileObject, wait, fbi, IoStatus, DeviceObject);\n\n    if (!FileObject) {\n        FsRtlExitFileSystem();\n        return false;\n    }\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb) {\n        FsRtlExitFileSystem();\n        return false;\n    }\n\n    ccb = FileObject->FsContext2;\n\n    if (!ccb) {\n        FsRtlExitFileSystem();\n        return false;\n    }\n\n    if (!(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) {\n        FsRtlExitFileSystem();\n        return false;\n    }\n\n    if (fcb->ads) {\n        if (!ccb->fileref || !ccb->fileref->parent || !ccb->fileref->parent->fcb) {\n            FsRtlExitFileSystem();\n            return false;\n        }\n\n        fcb = ccb->fileref->parent->fcb;\n    }\n\n    if (!ExAcquireResourceSharedLite(fcb->Header.Resource, wait)) {\n        FsRtlExitFileSystem();\n        return false;\n    }\n\n    if (fcb == fcb->Vcb->dummy_fcb) {\n        LARGE_INTEGER time;\n\n        KeQuerySystemTime(&time);\n        fbi->CreationTime = fbi->LastAccessTime = fbi->LastWriteTime = fbi->ChangeTime = time;\n    } else {\n        fbi->CreationTime.QuadPart = unix_time_to_win(&fcb->inode_item.otime);\n        fbi->LastAccessTime.QuadPart = unix_time_to_win(&fcb->inode_item.st_atime);\n        fbi->LastWriteTime.QuadPart = unix_time_to_win(&fcb->inode_item.st_mtime);\n        fbi->ChangeTime.QuadPart = unix_time_to_win(&fcb->inode_item.st_ctime);\n    }\n\n    fbi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts;\n\n    IoStatus->Status = STATUS_SUCCESS;\n    IoStatus->Information = sizeof(FILE_BASIC_INFORMATION);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    FsRtlExitFileSystem();\n\n    return true;\n}\n\n_Function_class_(FAST_IO_QUERY_STANDARD_INFO)\nstatic BOOLEAN __stdcall fast_query_standard_info(PFILE_OBJECT FileObject, BOOLEAN wait, PFILE_STANDARD_INFORMATION fsi,\n                                                  PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) {\n    fcb* fcb;\n    ccb* ccb;\n    bool ads;\n    ULONG adssize;\n\n    UNUSED(DeviceObject);\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"(%p, %u, %p, %p, %p)\\n\", FileObject, wait, fsi, IoStatus, DeviceObject);\n\n    if (!FileObject) {\n        FsRtlExitFileSystem();\n        return false;\n    }\n\n    fcb = FileObject->FsContext;\n    ccb = FileObject->FsContext2;\n\n    if (!fcb) {\n        FsRtlExitFileSystem();\n        return false;\n    }\n\n    if (!ExAcquireResourceSharedLite(fcb->Header.Resource, wait)) {\n        FsRtlExitFileSystem();\n        return false;\n    }\n\n    ads = fcb->ads;\n\n    if (ads) {\n        struct _fcb* fcb2;\n\n        if (!ccb || !ccb->fileref || !ccb->fileref->parent || !ccb->fileref->parent->fcb) {\n            ExReleaseResourceLite(fcb->Header.Resource);\n            FsRtlExitFileSystem();\n            return false;\n        }\n\n        adssize = fcb->adsdata.Length;\n\n        fcb2 = ccb->fileref->parent->fcb;\n\n        ExReleaseResourceLite(fcb->Header.Resource);\n\n        fcb = fcb2;\n\n        if (!ExAcquireResourceSharedLite(fcb->Header.Resource, wait)) {\n            FsRtlExitFileSystem();\n            return false;\n        }\n\n        fsi->AllocationSize.QuadPart = fsi->EndOfFile.QuadPart = adssize;\n        fsi->NumberOfLinks = fcb->inode_item.st_nlink;\n        fsi->Directory = false;\n    } else {\n        fsi->AllocationSize.QuadPart = fcb_alloc_size(fcb);\n        fsi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;\n        fsi->NumberOfLinks = fcb->inode_item.st_nlink;\n        fsi->Directory = S_ISDIR(fcb->inode_item.st_mode);\n    }\n\n    fsi->DeletePending = ccb->fileref ? ccb->fileref->delete_on_close : false;\n\n    IoStatus->Status = STATUS_SUCCESS;\n    IoStatus->Information = sizeof(FILE_STANDARD_INFORMATION);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    FsRtlExitFileSystem();\n\n    return true;\n}\n\n_Function_class_(FAST_IO_CHECK_IF_POSSIBLE)\nstatic BOOLEAN __stdcall fast_io_check_if_possible(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, ULONG Length, BOOLEAN Wait,\n                                                   ULONG LockKey, BOOLEAN CheckForReadOperation, PIO_STATUS_BLOCK IoStatus,\n                                                   PDEVICE_OBJECT DeviceObject) {\n    fcb* fcb = FileObject->FsContext;\n    LARGE_INTEGER len2;\n\n    UNUSED(Wait);\n    UNUSED(IoStatus);\n    UNUSED(DeviceObject);\n\n    len2.QuadPart = Length;\n\n    if (CheckForReadOperation) {\n        if (FsRtlFastCheckLockForRead(&fcb->lock, FileOffset, &len2, LockKey, FileObject, PsGetCurrentProcess()))\n            return true;\n    } else {\n        if (!fcb->Vcb->readonly && !is_subvol_readonly(fcb->subvol, NULL) && FsRtlFastCheckLockForWrite(&fcb->lock, FileOffset, &len2, LockKey, FileObject, PsGetCurrentProcess()))\n            return true;\n    }\n\n    return false;\n}\n\n_Function_class_(FAST_IO_QUERY_NETWORK_OPEN_INFO)\nstatic BOOLEAN __stdcall fast_io_query_network_open_info(PFILE_OBJECT FileObject, BOOLEAN Wait, FILE_NETWORK_OPEN_INFORMATION* fnoi,\n                                                         PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) {\n    fcb* fcb;\n    ccb* ccb;\n    file_ref* fileref;\n\n    UNUSED(Wait);\n    UNUSED(IoStatus); // FIXME - really? What about IoStatus->Information?\n    UNUSED(DeviceObject);\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"(%p, %u, %p, %p, %p)\\n\", FileObject, Wait, fnoi, IoStatus, DeviceObject);\n\n    RtlZeroMemory(fnoi, sizeof(FILE_NETWORK_OPEN_INFORMATION));\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb || fcb == fcb->Vcb->volume_fcb) {\n        FsRtlExitFileSystem();\n        return false;\n    }\n\n    ccb = FileObject->FsContext2;\n\n    if (!ccb) {\n        FsRtlExitFileSystem();\n        return false;\n    }\n\n    fileref = ccb->fileref;\n\n    if (fcb == fcb->Vcb->dummy_fcb) {\n        LARGE_INTEGER time;\n\n        KeQuerySystemTime(&time);\n        fnoi->CreationTime = fnoi->LastAccessTime = fnoi->LastWriteTime = fnoi->ChangeTime = time;\n    } else {\n        INODE_ITEM* ii;\n\n        if (fcb->ads) {\n            if (!fileref || !fileref->parent) {\n                ERR(\"no fileref for stream\\n\");\n                FsRtlExitFileSystem();\n                return false;\n            }\n\n            ii = &fileref->parent->fcb->inode_item;\n        } else\n            ii = &fcb->inode_item;\n\n        fnoi->CreationTime.QuadPart = unix_time_to_win(&ii->otime);\n        fnoi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);\n        fnoi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);\n        fnoi->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime);\n    }\n\n    if (fcb->ads) {\n        fnoi->AllocationSize.QuadPart = fnoi->EndOfFile.QuadPart = fcb->adsdata.Length;\n        fnoi->FileAttributes = fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fileref->parent->fcb->atts;\n    } else {\n        fnoi->AllocationSize.QuadPart = fcb_alloc_size(fcb);\n        fnoi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;\n        fnoi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts;\n    }\n\n    FsRtlExitFileSystem();\n\n    return true;\n}\n\n_Function_class_(FAST_IO_ACQUIRE_FOR_MOD_WRITE)\nstatic NTSTATUS __stdcall fast_io_acquire_for_mod_write(PFILE_OBJECT FileObject, PLARGE_INTEGER EndingOffset,\n                                                        struct _ERESOURCE **ResourceToRelease, PDEVICE_OBJECT DeviceObject) {\n    fcb* fcb;\n\n    TRACE(\"(%p, %I64x, %p, %p)\\n\", FileObject, EndingOffset ? EndingOffset->QuadPart : 0, ResourceToRelease, DeviceObject);\n\n    UNUSED(EndingOffset);\n    UNUSED(DeviceObject);\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    // Make sure we don't get interrupted by the flush thread, which can cause a deadlock\n\n    if (!ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, false))\n        return STATUS_CANT_WAIT;\n\n    if (!ExAcquireResourceExclusiveLite(fcb->Header.Resource, false)) {\n        ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n        TRACE(\"returning STATUS_CANT_WAIT\\n\");\n        return STATUS_CANT_WAIT;\n    }\n\n    // Ideally this would be PagingIoResource, but that doesn't play well with copy-on-write,\n    // as we can't guarantee that we won't need to do any reallocations.\n\n    *ResourceToRelease = fcb->Header.Resource;\n\n    TRACE(\"returning STATUS_SUCCESS\\n\");\n\n    return STATUS_SUCCESS;\n}\n\n_Function_class_(FAST_IO_RELEASE_FOR_MOD_WRITE)\nstatic NTSTATUS __stdcall fast_io_release_for_mod_write(PFILE_OBJECT FileObject, struct _ERESOURCE *ResourceToRelease,\n                                                        PDEVICE_OBJECT DeviceObject) {\n    fcb* fcb;\n\n    TRACE(\"(%p, %p, %p)\\n\", FileObject, ResourceToRelease, DeviceObject);\n\n    UNUSED(DeviceObject);\n\n    fcb = FileObject->FsContext;\n\n    ExReleaseResourceLite(ResourceToRelease);\n\n    ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n\n    return STATUS_SUCCESS;\n}\n\n_Function_class_(FAST_IO_ACQUIRE_FOR_CCFLUSH)\nstatic NTSTATUS __stdcall fast_io_acquire_for_ccflush(PFILE_OBJECT FileObject, PDEVICE_OBJECT DeviceObject) {\n    UNUSED(FileObject);\n    UNUSED(DeviceObject);\n\n    IoSetTopLevelIrp((PIRP)FSRTL_CACHE_TOP_LEVEL_IRP);\n\n    return STATUS_SUCCESS;\n}\n\n_Function_class_(FAST_IO_RELEASE_FOR_CCFLUSH)\nstatic NTSTATUS __stdcall fast_io_release_for_ccflush(PFILE_OBJECT FileObject, PDEVICE_OBJECT DeviceObject) {\n    UNUSED(FileObject);\n    UNUSED(DeviceObject);\n\n    if (IoGetTopLevelIrp() == (PIRP)FSRTL_CACHE_TOP_LEVEL_IRP)\n        IoSetTopLevelIrp(NULL);\n\n    return STATUS_SUCCESS;\n}\n\n_Function_class_(FAST_IO_WRITE)\nstatic BOOLEAN __stdcall fast_io_write(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, ULONG Length, BOOLEAN Wait, ULONG LockKey, PVOID Buffer, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) {\n    fcb* fcb = FileObject->FsContext;\n    bool ret;\n\n    FsRtlEnterFileSystem();\n\n    if (!ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, Wait)) {\n        FsRtlExitFileSystem();\n        return false;\n    }\n\n    ret = FsRtlCopyWrite(FileObject, FileOffset, Length, Wait, LockKey, Buffer, IoStatus, DeviceObject);\n\n    if (ret)\n        fcb->inode_item.st_size = fcb->Header.FileSize.QuadPart;\n\n    ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n\n    FsRtlExitFileSystem();\n\n    return ret;\n}\n\n_Function_class_(FAST_IO_LOCK)\nstatic BOOLEAN __stdcall fast_io_lock(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, PLARGE_INTEGER Length, PEPROCESS ProcessId,\n                                      ULONG Key, BOOLEAN FailImmediately, BOOLEAN ExclusiveLock, PIO_STATUS_BLOCK IoStatus,\n                                      PDEVICE_OBJECT DeviceObject) {\n    BOOLEAN ret;\n    fcb* fcb = FileObject->FsContext;\n\n    UNUSED(DeviceObject);\n\n    TRACE(\"(%p, %I64x, %I64x, %p, %lx, %u, %u, %p, %p)\\n\", FileObject, FileOffset ? FileOffset->QuadPart : 0, Length ? Length->QuadPart : 0,\n          ProcessId, Key, FailImmediately, ExclusiveLock, IoStatus, DeviceObject);\n\n    if (fcb->type != BTRFS_TYPE_FILE) {\n        WARN(\"can only lock files\\n\");\n        IoStatus->Status = STATUS_INVALID_PARAMETER;\n        IoStatus->Information = 0;\n        return true;\n    }\n\n    FsRtlEnterFileSystem();\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    ret = FsRtlFastLock(&fcb->lock, FileObject, FileOffset, Length, ProcessId, Key, FailImmediately,\n                        ExclusiveLock, IoStatus, NULL, false);\n\n    if (ret)\n        fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n    FsRtlExitFileSystem();\n\n    return ret;\n}\n\n_Function_class_(FAST_IO_UNLOCK_SINGLE)\nstatic BOOLEAN __stdcall fast_io_unlock_single(PFILE_OBJECT FileObject, PLARGE_INTEGER FileOffset, PLARGE_INTEGER Length, PEPROCESS ProcessId,\n                                               ULONG Key, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) {\n    fcb* fcb = FileObject->FsContext;\n\n    UNUSED(DeviceObject);\n\n    TRACE(\"(%p, %I64x, %I64x, %p, %lx, %p, %p)\\n\", FileObject, FileOffset ? FileOffset->QuadPart : 0, Length ? Length->QuadPart : 0,\n          ProcessId, Key, IoStatus, DeviceObject);\n\n    IoStatus->Information = 0;\n\n    if (fcb->type != BTRFS_TYPE_FILE) {\n        WARN(\"can only lock files\\n\");\n        IoStatus->Status = STATUS_INVALID_PARAMETER;\n        return true;\n    }\n\n    FsRtlEnterFileSystem();\n\n    IoStatus->Status = FsRtlFastUnlockSingle(&fcb->lock, FileObject, FileOffset, Length, ProcessId, Key, NULL, false);\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n\n    FsRtlExitFileSystem();\n\n    return true;\n}\n\n_Function_class_(FAST_IO_UNLOCK_ALL)\nstatic BOOLEAN __stdcall fast_io_unlock_all(PFILE_OBJECT FileObject, PEPROCESS ProcessId, PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) {\n    fcb* fcb = FileObject->FsContext;\n\n    UNUSED(DeviceObject);\n\n    TRACE(\"(%p, %p, %p, %p)\\n\", FileObject, ProcessId, IoStatus, DeviceObject);\n\n    IoStatus->Information = 0;\n\n    if (fcb->type != BTRFS_TYPE_FILE) {\n        WARN(\"can only lock files\\n\");\n        IoStatus->Status = STATUS_INVALID_PARAMETER;\n        return true;\n    }\n\n    FsRtlEnterFileSystem();\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    IoStatus->Status = FsRtlFastUnlockAll(&fcb->lock, FileObject, ProcessId, NULL);\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    FsRtlExitFileSystem();\n\n    return true;\n}\n\n_Function_class_(FAST_IO_UNLOCK_ALL_BY_KEY)\nstatic BOOLEAN __stdcall fast_io_unlock_all_by_key(PFILE_OBJECT FileObject, PVOID ProcessId, ULONG Key,\n                                                   PIO_STATUS_BLOCK IoStatus, PDEVICE_OBJECT DeviceObject) {\n    fcb* fcb = FileObject->FsContext;\n\n    UNUSED(DeviceObject);\n\n    TRACE(\"(%p, %p, %lx, %p, %p)\\n\", FileObject, ProcessId, Key, IoStatus, DeviceObject);\n\n    IoStatus->Information = 0;\n\n    if (fcb->type != BTRFS_TYPE_FILE) {\n        WARN(\"can only lock files\\n\");\n        IoStatus->Status = STATUS_INVALID_PARAMETER;\n        return true;\n    }\n\n    FsRtlEnterFileSystem();\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    IoStatus->Status = FsRtlFastUnlockAllByKey(&fcb->lock, FileObject, ProcessId, Key, NULL);\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    FsRtlExitFileSystem();\n\n    return true;\n}\n\nstatic void __stdcall fast_io_acquire_for_create_section(_In_ PFILE_OBJECT FileObject) {\n    fcb* fcb;\n\n    TRACE(\"(%p)\\n\", FileObject);\n\n    if (!FileObject)\n        return;\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb)\n        return;\n\n    ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n}\n\nstatic void __stdcall fast_io_release_for_create_section(_In_ PFILE_OBJECT FileObject) {\n    fcb* fcb;\n\n    TRACE(\"(%p)\\n\", FileObject);\n\n    if (!FileObject)\n        return;\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb)\n        return;\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n    ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n}\n\nvoid init_fast_io_dispatch(FAST_IO_DISPATCH** fiod) {\n    RtlZeroMemory(&FastIoDispatch, sizeof(FastIoDispatch));\n\n    FastIoDispatch.SizeOfFastIoDispatch = sizeof(FAST_IO_DISPATCH);\n\n    FastIoDispatch.FastIoCheckIfPossible = fast_io_check_if_possible;\n    FastIoDispatch.FastIoRead = FsRtlCopyRead;\n    FastIoDispatch.FastIoWrite = fast_io_write;\n    FastIoDispatch.FastIoQueryBasicInfo = fast_query_basic_info;\n    FastIoDispatch.FastIoQueryStandardInfo = fast_query_standard_info;\n    FastIoDispatch.FastIoLock = fast_io_lock;\n    FastIoDispatch.FastIoUnlockSingle = fast_io_unlock_single;\n    FastIoDispatch.FastIoUnlockAll = fast_io_unlock_all;\n    FastIoDispatch.FastIoUnlockAllByKey = fast_io_unlock_all_by_key;\n    FastIoDispatch.AcquireFileForNtCreateSection = fast_io_acquire_for_create_section;\n    FastIoDispatch.ReleaseFileForNtCreateSection = fast_io_release_for_create_section;\n    FastIoDispatch.FastIoQueryNetworkOpenInfo = fast_io_query_network_open_info;\n    FastIoDispatch.AcquireForModWrite = fast_io_acquire_for_mod_write;\n    FastIoDispatch.MdlRead = FsRtlMdlReadDev;\n    FastIoDispatch.MdlReadComplete = FsRtlMdlReadCompleteDev;\n    FastIoDispatch.PrepareMdlWrite = FsRtlPrepareMdlWriteDev;\n    FastIoDispatch.MdlWriteComplete = FsRtlMdlWriteCompleteDev;\n    FastIoDispatch.ReleaseForModWrite = fast_io_release_for_mod_write;\n    FastIoDispatch.AcquireForCcFlush = fast_io_acquire_for_ccflush;\n    FastIoDispatch.ReleaseForCcFlush = fast_io_release_for_ccflush;\n\n    *fiod = &FastIoDispatch;\n}\n"
  },
  {
    "path": "src/fileinfo.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include \"crc32c.h\"\n\n// not currently in mingw - introduced with Windows 10\n#ifndef _MSC_VER\n#define FileIdInformation (enum _FILE_INFORMATION_CLASS)59\n#define FileHardLinkFullIdInformation (enum _FILE_INFORMATION_CLASS)62\n#define FileDispositionInformationEx (enum _FILE_INFORMATION_CLASS)64\n#define FileRenameInformationEx (enum _FILE_INFORMATION_CLASS)65\n#define FileStatInformation (enum _FILE_INFORMATION_CLASS)68\n#define FileStatLxInformation (enum _FILE_INFORMATION_CLASS)70\n#define FileCaseSensitiveInformation (enum _FILE_INFORMATION_CLASS)71\n#define FileLinkInformationEx (enum _FILE_INFORMATION_CLASS)72\n#define FileStorageReserveIdInformation (enum _FILE_INFORMATION_CLASS)74\n\ntypedef struct _FILE_ID_INFORMATION {\n    ULONGLONG VolumeSerialNumber;\n    FILE_ID_128 FileId;\n} FILE_ID_INFORMATION, *PFILE_ID_INFORMATION;\n\ntypedef struct _FILE_STAT_INFORMATION {\n    LARGE_INTEGER FileId;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER AllocationSize;\n    LARGE_INTEGER EndOfFile;\n    ULONG FileAttributes;\n    ULONG ReparseTag;\n    ULONG NumberOfLinks;\n    ACCESS_MASK EffectiveAccess;\n} FILE_STAT_INFORMATION, *PFILE_STAT_INFORMATION;\n\ntypedef struct _FILE_STAT_LX_INFORMATION {\n    LARGE_INTEGER FileId;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER AllocationSize;\n    LARGE_INTEGER EndOfFile;\n    ULONG         FileAttributes;\n    ULONG         ReparseTag;\n    ULONG         NumberOfLinks;\n    ACCESS_MASK   EffectiveAccess;\n    ULONG         LxFlags;\n    ULONG         LxUid;\n    ULONG         LxGid;\n    ULONG         LxMode;\n    ULONG         LxDeviceIdMajor;\n    ULONG         LxDeviceIdMinor;\n} FILE_STAT_LX_INFORMATION, *PFILE_STAT_LX_INFORMATION;\n\n#define LX_FILE_METADATA_HAS_UID        0x01\n#define LX_FILE_METADATA_HAS_GID        0x02\n#define LX_FILE_METADATA_HAS_MODE       0x04\n#define LX_FILE_METADATA_HAS_DEVICE_ID  0x08\n#define LX_FILE_CASE_SENSITIVE_DIR      0x10\n\ntypedef struct _FILE_RENAME_INFORMATION_EX {\n    union {\n        BOOLEAN ReplaceIfExists;\n        ULONG Flags;\n    };\n    HANDLE RootDirectory;\n    ULONG FileNameLength;\n    WCHAR FileName[1];\n} FILE_RENAME_INFORMATION_EX, *PFILE_RENAME_INFORMATION_EX;\n\ntypedef struct _FILE_DISPOSITION_INFORMATION_EX {\n    ULONG Flags;\n} FILE_DISPOSITION_INFORMATION_EX, *PFILE_DISPOSITION_INFORMATION_EX;\n\ntypedef struct _FILE_LINK_INFORMATION_EX {\n    union {\n        BOOLEAN ReplaceIfExists;\n        ULONG Flags;\n    };\n    HANDLE RootDirectory;\n    ULONG FileNameLength;\n    WCHAR FileName[1];\n} FILE_LINK_INFORMATION_EX, *PFILE_LINK_INFORMATION_EX;\n\ntypedef struct _FILE_CASE_SENSITIVE_INFORMATION {\n    ULONG Flags;\n} FILE_CASE_SENSITIVE_INFORMATION, *PFILE_CASE_SENSITIVE_INFORMATION;\n\ntypedef struct _FILE_LINK_ENTRY_FULL_ID_INFORMATION {\n    ULONG NextEntryOffset;\n    FILE_ID_128 ParentFileId;\n    ULONG FileNameLength;\n    WCHAR FileName[1];\n} FILE_LINK_ENTRY_FULL_ID_INFORMATION, *PFILE_LINK_ENTRY_FULL_ID_INFORMATION;\n\ntypedef struct _FILE_LINKS_FULL_ID_INFORMATION {\n    ULONG BytesNeeded;\n    ULONG EntriesReturned;\n    FILE_LINK_ENTRY_FULL_ID_INFORMATION Entry;\n} FILE_LINKS_FULL_ID_INFORMATION, *PFILE_LINKS_FULL_ID_INFORMATION;\n\n#define FILE_RENAME_REPLACE_IF_EXISTS                       0x001\n#define FILE_RENAME_POSIX_SEMANTICS                         0x002\n#define FILE_RENAME_SUPPRESS_PIN_STATE_INHERITANCE          0x004\n#define FILE_RENAME_SUPPRESS_STORAGE_RESERVE_INHERITANCE    0x008\n#define FILE_RENAME_NO_INCREASE_AVAILABLE_SPACE             0x010\n#define FILE_RENAME_NO_DECREASE_AVAILABLE_SPACE             0x020\n#define FILE_RENAME_IGNORE_READONLY_ATTRIBUTE               0x040\n#define FILE_RENAME_FORCE_RESIZE_TARGET_SR                  0x080\n#define FILE_RENAME_FORCE_RESIZE_SOURCE_SR                  0x100\n\n#define FILE_DISPOSITION_DELETE                         0x1\n#define FILE_DISPOSITION_POSIX_SEMANTICS                0x2\n#define FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK      0x4\n#define FILE_DISPOSITION_ON_CLOSE                       0x8\n\n#define FILE_LINK_REPLACE_IF_EXISTS                       0x001\n#define FILE_LINK_POSIX_SEMANTICS                         0x002\n#define FILE_LINK_SUPPRESS_STORAGE_RESERVE_INHERITANCE    0x008\n#define FILE_LINK_NO_INCREASE_AVAILABLE_SPACE             0x010\n#define FILE_LINK_NO_DECREASE_AVAILABLE_SPACE             0x020\n#define FILE_LINK_IGNORE_READONLY_ATTRIBUTE               0x040\n#define FILE_LINK_FORCE_RESIZE_TARGET_SR                  0x080\n#define FILE_LINK_FORCE_RESIZE_SOURCE_SR                  0x100\n\n#else\n\n#define FILE_RENAME_INFORMATION_EX FILE_RENAME_INFORMATION\n#define FILE_LINK_INFORMATION_EX FILE_LINK_INFORMATION\n\n#endif\n\nstatic NTSTATUS set_basic_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) {\n    FILE_BASIC_INFORMATION* fbi = Irp->AssociatedIrp.SystemBuffer;\n    fcb* fcb = FileObject->FsContext;\n    ccb* ccb = FileObject->FsContext2;\n    file_ref* fileref = ccb ? ccb->fileref : NULL;\n    ULONG defda, filter = 0;\n    bool inode_item_changed = false;\n    NTSTATUS Status;\n\n    if (fcb->ads) {\n        if (fileref && fileref->parent)\n            fcb = fileref->parent->fcb;\n        else {\n            ERR(\"stream did not have fileref\\n\");\n            return STATUS_INTERNAL_ERROR;\n        }\n    }\n\n    if (!ccb) {\n        ERR(\"ccb was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    TRACE(\"file = %p, attributes = %lx\\n\", FileObject, fbi->FileAttributes);\n\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (fbi->FileAttributes & FILE_ATTRIBUTE_DIRECTORY && fcb->type != BTRFS_TYPE_DIRECTORY) {\n        WARN(\"attempted to set FILE_ATTRIBUTE_DIRECTORY on non-directory\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (fcb->inode == SUBVOL_ROOT_INODE && is_subvol_readonly(fcb->subvol, Irp) &&\n        (fbi->FileAttributes == 0 || fbi->FileAttributes & FILE_ATTRIBUTE_READONLY)) {\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    // don't allow readonly subvol to be made r/w if send operation running on it\n    if (fcb->inode == SUBVOL_ROOT_INODE && fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY &&\n        fcb->subvol->send_ops > 0) {\n        Status = STATUS_DEVICE_NOT_READY;\n        goto end;\n    }\n\n    // times of -2 are some sort of undocumented behaviour to do with LXSS\n\n    if (fbi->CreationTime.QuadPart == -2)\n        fbi->CreationTime.QuadPart = 0;\n\n    if (fbi->LastAccessTime.QuadPart == -2)\n        fbi->LastAccessTime.QuadPart = 0;\n\n    if (fbi->LastWriteTime.QuadPart == -2)\n        fbi->LastWriteTime.QuadPart = 0;\n\n    if (fbi->ChangeTime.QuadPart == -2)\n        fbi->ChangeTime.QuadPart = 0;\n\n    if (fbi->CreationTime.QuadPart == -1)\n        ccb->user_set_creation_time = true;\n    else if (fbi->CreationTime.QuadPart != 0) {\n        win_time_to_unix(fbi->CreationTime, &fcb->inode_item.otime);\n        inode_item_changed = true;\n        filter |= FILE_NOTIFY_CHANGE_CREATION;\n\n        ccb->user_set_creation_time = true;\n    }\n\n    if (fbi->LastAccessTime.QuadPart == -1)\n        ccb->user_set_access_time = true;\n    else if (fbi->LastAccessTime.QuadPart != 0) {\n        win_time_to_unix(fbi->LastAccessTime, &fcb->inode_item.st_atime);\n        inode_item_changed = true;\n        filter |= FILE_NOTIFY_CHANGE_LAST_ACCESS;\n\n        ccb->user_set_access_time = true;\n    }\n\n    if (fbi->LastWriteTime.QuadPart == -1)\n        ccb->user_set_write_time = true;\n    else if (fbi->LastWriteTime.QuadPart != 0) {\n        win_time_to_unix(fbi->LastWriteTime, &fcb->inode_item.st_mtime);\n        inode_item_changed = true;\n        filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;\n\n        ccb->user_set_write_time = true;\n    }\n\n    if (fbi->ChangeTime.QuadPart == -1)\n        ccb->user_set_change_time = true;\n    else if (fbi->ChangeTime.QuadPart != 0) {\n        win_time_to_unix(fbi->ChangeTime, &fcb->inode_item.st_ctime);\n        inode_item_changed = true;\n        // no filter for this\n\n        ccb->user_set_change_time = true;\n    }\n\n    // FileAttributes == 0 means don't set - undocumented, but seen in fastfat\n    if (fbi->FileAttributes != 0) {\n        LARGE_INTEGER time;\n        BTRFS_TIME now;\n\n        fbi->FileAttributes &= ~FILE_ATTRIBUTE_NORMAL;\n\n        defda = get_file_attributes(Vcb, fcb->subvol, fcb->inode, fcb->type, fileref && fileref->dc && fileref->dc->name.Length >= sizeof(WCHAR) && fileref->dc->name.Buffer[0] == '.',\n                                    true, Irp);\n\n        if (fcb->type == BTRFS_TYPE_DIRECTORY)\n            fbi->FileAttributes |= FILE_ATTRIBUTE_DIRECTORY;\n        else if (fcb->type == BTRFS_TYPE_SYMLINK)\n            fbi->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;\n\n        fcb->atts_changed = true;\n\n        if (fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT)\n            fbi->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;\n\n        if (defda == fbi->FileAttributes)\n            fcb->atts_deleted = true;\n        else if (fcb->inode == SUBVOL_ROOT_INODE && (defda | FILE_ATTRIBUTE_READONLY) == (fbi->FileAttributes | FILE_ATTRIBUTE_READONLY))\n            fcb->atts_deleted = true;\n\n        fcb->atts = fbi->FileAttributes;\n\n        KeQuerySystemTime(&time);\n        win_time_to_unix(time, &now);\n\n        if (!ccb->user_set_change_time)\n            fcb->inode_item.st_ctime = now;\n\n        fcb->subvol->root_item.ctransid = Vcb->superblock.generation;\n        fcb->subvol->root_item.ctime = now;\n\n        if (fcb->inode == SUBVOL_ROOT_INODE) {\n            if (fbi->FileAttributes & FILE_ATTRIBUTE_READONLY)\n                fcb->subvol->root_item.flags |= BTRFS_SUBVOL_READONLY;\n            else\n                fcb->subvol->root_item.flags &= ~BTRFS_SUBVOL_READONLY;\n        }\n\n        inode_item_changed = true;\n\n        filter |= FILE_NOTIFY_CHANGE_ATTRIBUTES;\n    }\n\n    if (inode_item_changed) {\n        fcb->inode_item.transid = Vcb->superblock.generation;\n        fcb->inode_item.sequence++;\n        fcb->inode_item_changed = true;\n\n        mark_fcb_dirty(fcb);\n    }\n\n    if (filter != 0)\n        queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    return Status;\n}\n\nstatic NTSTATUS set_disposition_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, bool ex) {\n    fcb* fcb = FileObject->FsContext;\n    ccb* ccb = FileObject->FsContext2;\n    file_ref* fileref = ccb ? ccb->fileref : NULL;\n    ULONG atts, flags;\n    NTSTATUS Status;\n\n    if (!fileref)\n        return STATUS_INVALID_PARAMETER;\n\n    if (ex) {\n        FILE_DISPOSITION_INFORMATION_EX* fdi = Irp->AssociatedIrp.SystemBuffer;\n\n        flags = fdi->Flags;\n    } else {\n        FILE_DISPOSITION_INFORMATION* fdi = Irp->AssociatedIrp.SystemBuffer;\n\n        flags = fdi->DeleteFile ? FILE_DISPOSITION_DELETE : 0;\n    }\n\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    TRACE(\"changing delete_on_close to %s for fcb %p\\n\", flags & FILE_DISPOSITION_DELETE ? \"true\" : \"false\", fcb);\n\n    if (fcb->ads) {\n        if (fileref->parent)\n            atts = fileref->parent->fcb->atts;\n        else {\n            ERR(\"no fileref for stream\\n\");\n            Status = STATUS_INTERNAL_ERROR;\n            goto end;\n        }\n    } else\n        atts = fcb->atts;\n\n    TRACE(\"atts = %lx\\n\", atts);\n\n    if (atts & FILE_ATTRIBUTE_READONLY) {\n        TRACE(\"not allowing readonly file to be deleted\\n\");\n        Status = STATUS_CANNOT_DELETE;\n        goto end;\n    }\n\n    if (fcb->inode == SUBVOL_ROOT_INODE && fcb->subvol->id == BTRFS_ROOT_FSTREE) {\n        WARN(\"not allowing \\\\$Root to be deleted\\n\");\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    // FIXME - can we skip this bit for subvols?\n    if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0 && (!fileref || fileref->fcb != Vcb->dummy_fcb)) {\n        TRACE(\"directory not empty\\n\");\n        Status = STATUS_DIRECTORY_NOT_EMPTY;\n        goto end;\n    }\n\n    if (!MmFlushImageSection(&fcb->nonpaged->segment_object, MmFlushForDelete)) {\n        if (!ex || flags & FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK) {\n            TRACE(\"trying to delete file which is being mapped as an image\\n\");\n            Status = STATUS_CANNOT_DELETE;\n            goto end;\n        }\n    }\n\n    ccb->fileref->delete_on_close = flags & FILE_DISPOSITION_DELETE;\n\n    FileObject->DeletePending = flags & FILE_DISPOSITION_DELETE;\n\n    if (flags & FILE_DISPOSITION_DELETE && flags & FILE_DISPOSITION_POSIX_SEMANTICS)\n        ccb->fileref->posix_delete = true;\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    // send notification that directory is about to be deleted\n    if (NT_SUCCESS(Status) && flags & FILE_DISPOSITION_DELETE && fcb->type == BTRFS_TYPE_DIRECTORY) {\n        FsRtlNotifyFullChangeDirectory(Vcb->NotifySync, &Vcb->DirNotifyList, FileObject->FsContext,\n                                       NULL, false, false, 0, NULL, NULL, NULL);\n    }\n\n    return Status;\n}\n\nbool has_open_children(file_ref* fileref) {\n    LIST_ENTRY* le = fileref->children.Flink;\n\n    if (IsListEmpty(&fileref->children))\n        return false;\n\n    while (le != &fileref->children) {\n        file_ref* c = CONTAINING_RECORD(le, file_ref, list_entry);\n\n        if (c->open_count > 0)\n            return true;\n\n        if (has_open_children(c))\n            return true;\n\n        le = le->Flink;\n    }\n\n    return false;\n}\n\nstatic NTSTATUS duplicate_fcb(fcb* oldfcb, fcb** pfcb) {\n    device_extension* Vcb = oldfcb->Vcb;\n    fcb* fcb;\n    LIST_ENTRY* le;\n\n    // FIXME - we can skip a lot of this if the inode is about to be deleted\n\n    fcb = create_fcb(Vcb, PagedPool); // FIXME - what if we duplicate the paging file?\n    if (!fcb) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    fcb->Vcb = Vcb;\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n    fcb->Header.AllocationSize = oldfcb->Header.AllocationSize;\n    fcb->Header.FileSize = oldfcb->Header.FileSize;\n    fcb->Header.ValidDataLength = oldfcb->Header.ValidDataLength;\n\n    fcb->type = oldfcb->type;\n\n    if (oldfcb->ads) {\n        fcb->ads = true;\n        fcb->adshash = oldfcb->adshash;\n        fcb->adsmaxlen = oldfcb->adsmaxlen;\n\n        if (oldfcb->adsxattr.Buffer && oldfcb->adsxattr.Length > 0) {\n            fcb->adsxattr.Length = oldfcb->adsxattr.Length;\n            fcb->adsxattr.MaximumLength = fcb->adsxattr.Length + 1;\n            fcb->adsxattr.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->adsxattr.MaximumLength, ALLOC_TAG);\n\n            if (!fcb->adsxattr.Buffer) {\n                ERR(\"out of memory\\n\");\n                free_fcb(fcb);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(fcb->adsxattr.Buffer, oldfcb->adsxattr.Buffer, fcb->adsxattr.Length);\n            fcb->adsxattr.Buffer[fcb->adsxattr.Length] = 0;\n        }\n\n        if (oldfcb->adsdata.Buffer && oldfcb->adsdata.Length > 0) {\n            fcb->adsdata.Length = fcb->adsdata.MaximumLength = oldfcb->adsdata.Length;\n            fcb->adsdata.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->adsdata.MaximumLength, ALLOC_TAG);\n\n            if (!fcb->adsdata.Buffer) {\n                ERR(\"out of memory\\n\");\n                free_fcb(fcb);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(fcb->adsdata.Buffer, oldfcb->adsdata.Buffer, fcb->adsdata.Length);\n        }\n\n        goto end;\n    }\n\n    RtlCopyMemory(&fcb->inode_item, &oldfcb->inode_item, sizeof(INODE_ITEM));\n    fcb->inode_item_changed = true;\n\n    if (oldfcb->sd && RtlLengthSecurityDescriptor(oldfcb->sd) > 0) {\n        fcb->sd = ExAllocatePoolWithTag(PagedPool, RtlLengthSecurityDescriptor(oldfcb->sd), ALLOC_TAG);\n        if (!fcb->sd) {\n            ERR(\"out of memory\\n\");\n            free_fcb(fcb);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(fcb->sd, oldfcb->sd, RtlLengthSecurityDescriptor(oldfcb->sd));\n    }\n\n    fcb->atts = oldfcb->atts;\n\n    le = oldfcb->extents.Flink;\n    while (le != &oldfcb->extents) {\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if (!ext->ignore) {\n            extent* ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG);\n\n            if (!ext2) {\n                ERR(\"out of memory\\n\");\n                free_fcb(fcb);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            ext2->offset = ext->offset;\n            ext2->datalen = ext->datalen;\n\n            if (ext2->datalen > 0)\n                RtlCopyMemory(&ext2->extent_data, &ext->extent_data, ext2->datalen);\n\n            ext2->unique = false;\n            ext2->ignore = false;\n            ext2->inserted = true;\n\n            if (ext->csum) {\n                ULONG len;\n                EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n                if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE)\n                    len = (ULONG)ed2->num_bytes;\n                else\n                    len = (ULONG)ed2->size;\n\n                len = (len * sizeof(uint32_t)) >> Vcb->sector_shift;\n\n                ext2->csum = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);\n                if (!ext2->csum) {\n                    ERR(\"out of memory\\n\");\n                    free_fcb(fcb);\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(ext2->csum, ext->csum, len);\n            } else\n                ext2->csum = NULL;\n\n            InsertTailList(&fcb->extents, &ext2->list_entry);\n        }\n\n        le = le->Flink;\n    }\n\n    le = oldfcb->hardlinks.Flink;\n    while (le != &oldfcb->hardlinks) {\n        hardlink *hl = CONTAINING_RECORD(le, hardlink, list_entry), *hl2;\n\n        hl2 = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG);\n\n        if (!hl2) {\n            ERR(\"out of memory\\n\");\n            free_fcb(fcb);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        hl2->parent = hl->parent;\n        hl2->index = hl->index;\n\n        hl2->name.Length = hl2->name.MaximumLength = hl->name.Length;\n        hl2->name.Buffer = ExAllocatePoolWithTag(PagedPool, hl2->name.MaximumLength, ALLOC_TAG);\n\n        if (!hl2->name.Buffer) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(hl2);\n            free_fcb(fcb);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(hl2->name.Buffer, hl->name.Buffer, hl->name.Length);\n\n        hl2->utf8.Length = hl2->utf8.MaximumLength = hl->utf8.Length;\n        hl2->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl2->utf8.MaximumLength, ALLOC_TAG);\n\n        if (!hl2->utf8.Buffer) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(hl2->name.Buffer);\n            ExFreePool(hl2);\n            free_fcb(fcb);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(hl2->utf8.Buffer, hl->utf8.Buffer, hl->utf8.Length);\n\n        InsertTailList(&fcb->hardlinks, &hl2->list_entry);\n\n        le = le->Flink;\n    }\n\n    if (oldfcb->reparse_xattr.Buffer && oldfcb->reparse_xattr.Length > 0) {\n        fcb->reparse_xattr.Length = fcb->reparse_xattr.MaximumLength = oldfcb->reparse_xattr.Length;\n\n        fcb->reparse_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->reparse_xattr.MaximumLength, ALLOC_TAG);\n        if (!fcb->reparse_xattr.Buffer) {\n            ERR(\"out of memory\\n\");\n            free_fcb(fcb);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(fcb->reparse_xattr.Buffer, oldfcb->reparse_xattr.Buffer, fcb->reparse_xattr.Length);\n    }\n\n    if (oldfcb->ea_xattr.Buffer && oldfcb->ea_xattr.Length > 0) {\n        fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = oldfcb->ea_xattr.Length;\n\n        fcb->ea_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, fcb->ea_xattr.MaximumLength, ALLOC_TAG);\n        if (!fcb->ea_xattr.Buffer) {\n            ERR(\"out of memory\\n\");\n            free_fcb(fcb);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(fcb->ea_xattr.Buffer, oldfcb->ea_xattr.Buffer, fcb->ea_xattr.Length);\n    }\n\n    fcb->prop_compression = oldfcb->prop_compression;\n\n    le = oldfcb->xattrs.Flink;\n    while (le != &oldfcb->xattrs) {\n        xattr* xa = CONTAINING_RECORD(le, xattr, list_entry);\n\n        if (xa->valuelen > 0) {\n            xattr* xa2;\n\n            xa2 = ExAllocatePoolWithTag(PagedPool, offsetof(xattr, data[0]) + xa->namelen + xa->valuelen, ALLOC_TAG);\n\n            if (!xa2) {\n                ERR(\"out of memory\\n\");\n                free_fcb(fcb);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            xa2->namelen = xa->namelen;\n            xa2->valuelen = xa->valuelen;\n            xa2->dirty = xa->dirty;\n            memcpy(xa2->data, xa->data, xa->namelen + xa->valuelen);\n\n            InsertTailList(&fcb->xattrs, &xa2->list_entry);\n        }\n\n        le = le->Flink;\n    }\n\nend:\n    *pfcb = fcb;\n\n    return STATUS_SUCCESS;\n}\n\ntypedef struct _move_entry {\n    file_ref* fileref;\n    fcb* dummyfcb;\n    file_ref* dummyfileref;\n    struct _move_entry* parent;\n    LIST_ENTRY list_entry;\n} move_entry;\n\nstatic NTSTATUS add_children_to_move_list(device_extension* Vcb, move_entry* me, PIRP Irp) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n\n    ExAcquireResourceSharedLite(&me->fileref->fcb->nonpaged->dir_children_lock, true);\n\n    le = me->fileref->fcb->dir_children_index.Flink;\n\n    while (le != &me->fileref->fcb->dir_children_index) {\n        dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index);\n        file_ref* fr;\n        move_entry* me2;\n\n        Status = open_fileref_child(Vcb, me->fileref, &dc->name, true, true, dc->index == 0 ? true : false, PagedPool, &fr, Irp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"open_fileref_child returned %08lx\\n\", Status);\n            ExReleaseResourceLite(&me->fileref->fcb->nonpaged->dir_children_lock);\n            return Status;\n        }\n\n        me2 = ExAllocatePoolWithTag(PagedPool, sizeof(move_entry), ALLOC_TAG);\n        if (!me2) {\n            ERR(\"out of memory\\n\");\n            ExReleaseResourceLite(&me->fileref->fcb->nonpaged->dir_children_lock);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        me2->fileref = fr;\n        me2->dummyfcb = NULL;\n        me2->dummyfileref = NULL;\n        me2->parent = me;\n\n        InsertHeadList(&me->list_entry, &me2->list_entry);\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&me->fileref->fcb->nonpaged->dir_children_lock);\n\n    return STATUS_SUCCESS;\n}\n\nvoid remove_dir_child_from_hash_lists(fcb* fcb, dir_child* dc) {\n    uint8_t c;\n\n    c = dc->hash >> 24;\n\n    if (fcb->hash_ptrs[c] == &dc->list_entry_hash) {\n        if (dc->list_entry_hash.Flink == &fcb->dir_children_hash)\n            fcb->hash_ptrs[c] = NULL;\n        else {\n            dir_child* dc2 = CONTAINING_RECORD(dc->list_entry_hash.Flink, dir_child, list_entry_hash);\n\n            if (dc2->hash >> 24 == c)\n                fcb->hash_ptrs[c] = &dc2->list_entry_hash;\n            else\n                fcb->hash_ptrs[c] = NULL;\n        }\n    }\n\n    RemoveEntryList(&dc->list_entry_hash);\n\n    c = dc->hash_uc >> 24;\n\n    if (fcb->hash_ptrs_uc[c] == &dc->list_entry_hash_uc) {\n        if (dc->list_entry_hash_uc.Flink == &fcb->dir_children_hash_uc)\n            fcb->hash_ptrs_uc[c] = NULL;\n        else {\n            dir_child* dc2 = CONTAINING_RECORD(dc->list_entry_hash_uc.Flink, dir_child, list_entry_hash_uc);\n\n            if (dc2->hash_uc >> 24 == c)\n                fcb->hash_ptrs_uc[c] = &dc2->list_entry_hash_uc;\n            else\n                fcb->hash_ptrs_uc[c] = NULL;\n        }\n    }\n\n    RemoveEntryList(&dc->list_entry_hash_uc);\n}\n\nstatic NTSTATUS create_directory_fcb(device_extension* Vcb, root* r, fcb* parfcb, fcb** pfcb) {\n    NTSTATUS Status;\n    fcb* fcb;\n    SECURITY_SUBJECT_CONTEXT subjcont;\n    PSID owner;\n    BOOLEAN defaulted;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n\n    fcb = create_fcb(Vcb, PagedPool);\n    if (!fcb) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    fcb->Vcb = Vcb;\n\n    fcb->subvol = r;\n    fcb->inode = InterlockedIncrement64(&r->lastinode);\n    fcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&fcb->inode, sizeof(uint64_t));\n    fcb->type = BTRFS_TYPE_DIRECTORY;\n\n    fcb->inode_item.generation = Vcb->superblock.generation;\n    fcb->inode_item.transid = Vcb->superblock.generation;\n    fcb->inode_item.st_nlink = 1;\n    fcb->inode_item.st_mode = __S_IFDIR | inherit_mode(parfcb, true);\n    fcb->inode_item.st_atime = fcb->inode_item.st_ctime = fcb->inode_item.st_mtime = fcb->inode_item.otime = now;\n    fcb->inode_item.st_gid = GID_NOBODY;\n\n    fcb->atts = get_file_attributes(Vcb, fcb->subvol, fcb->inode, fcb->type, false, true, NULL);\n\n    SeCaptureSubjectContext(&subjcont);\n\n    Status = SeAssignSecurity(parfcb->sd, NULL, (void**)&fcb->sd, true, &subjcont, IoGetFileObjectGenericMapping(), PagedPool);\n\n    if (!NT_SUCCESS(Status)) {\n        reap_fcb(fcb);\n        ERR(\"SeAssignSecurity returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (!fcb->sd) {\n        reap_fcb(fcb);\n        ERR(\"SeAssignSecurity returned NULL security descriptor\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    Status = RtlGetOwnerSecurityDescriptor(fcb->sd, &owner, &defaulted);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlGetOwnerSecurityDescriptor returned %08lx\\n\", Status);\n        fcb->inode_item.st_uid = UID_NOBODY;\n        fcb->sd_dirty = true;\n    } else {\n        fcb->inode_item.st_uid = sid_to_uid(owner);\n        fcb->sd_dirty = fcb->inode_item.st_uid == UID_NOBODY;\n    }\n\n    find_gid(fcb, parfcb, &subjcont);\n\n    fcb->inode_item_changed = true;\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n    fcb->Header.AllocationSize.QuadPart = 0;\n    fcb->Header.FileSize.QuadPart = 0;\n    fcb->Header.ValidDataLength.QuadPart = 0;\n\n    fcb->created = true;\n\n    if (parfcb->inode_item.flags & BTRFS_INODE_COMPRESS)\n        fcb->inode_item.flags |= BTRFS_INODE_COMPRESS;\n\n    fcb->prop_compression = parfcb->prop_compression;\n    fcb->prop_compression_changed = fcb->prop_compression != PropCompression_None;\n\n    fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);\n    if (!fcb->hash_ptrs) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256);\n\n    fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);\n    if (!fcb->hash_ptrs_uc) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256);\n\n    acquire_fcb_lock_exclusive(Vcb);\n    add_fcb_to_subvol(fcb);\n    InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all);\n    r->fcbs_version++;\n    release_fcb_lock(Vcb);\n\n    mark_fcb_dirty(fcb);\n\n    *pfcb = fcb;\n\n    return STATUS_SUCCESS;\n}\n\nvoid add_fcb_to_subvol(_In_ _Requires_exclusive_lock_held_(_Curr_->Vcb->fcb_lock) fcb* fcb) {\n    LIST_ENTRY* lastle = NULL;\n    uint32_t hash = fcb->hash;\n\n    if (fcb->subvol->fcbs_ptrs[hash >> 24]) {\n        LIST_ENTRY* le = fcb->subvol->fcbs_ptrs[hash >> 24];\n\n        while (le != &fcb->subvol->fcbs) {\n            struct _fcb* fcb2 = CONTAINING_RECORD(le, struct _fcb, list_entry);\n\n            if (fcb2->hash > hash) {\n                lastle = le->Blink;\n                break;\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    if (!lastle) {\n        uint8_t c = hash >> 24;\n\n        if (c != 0xff) {\n            uint8_t d = c + 1;\n\n            do {\n                if (fcb->subvol->fcbs_ptrs[d]) {\n                    lastle = fcb->subvol->fcbs_ptrs[d]->Blink;\n                    break;\n                }\n\n                d++;\n            } while (d != 0);\n        }\n    }\n\n    if (lastle) {\n        InsertHeadList(lastle, &fcb->list_entry);\n\n        if (lastle == &fcb->subvol->fcbs || (CONTAINING_RECORD(lastle, struct _fcb, list_entry)->hash >> 24) != (hash >> 24))\n            fcb->subvol->fcbs_ptrs[hash >> 24] = &fcb->list_entry;\n    } else {\n        InsertTailList(&fcb->subvol->fcbs, &fcb->list_entry);\n\n        if (fcb->list_entry.Blink == &fcb->subvol->fcbs || (CONTAINING_RECORD(fcb->list_entry.Blink, struct _fcb, list_entry)->hash >> 24) != (hash >> 24))\n            fcb->subvol->fcbs_ptrs[hash >> 24] = &fcb->list_entry;\n    }\n}\n\nvoid remove_fcb_from_subvol(_In_ _Requires_exclusive_lock_held_(_Curr_->Vcb->fcb_lock) fcb* fcb) {\n    uint8_t c = fcb->hash >> 24;\n\n    if (fcb->subvol->fcbs_ptrs[c] == &fcb->list_entry) {\n        if (fcb->list_entry.Flink != &fcb->subvol->fcbs && (CONTAINING_RECORD(fcb->list_entry.Flink, struct _fcb, list_entry)->hash >> 24) == c)\n            fcb->subvol->fcbs_ptrs[c] = fcb->list_entry.Flink;\n        else\n            fcb->subvol->fcbs_ptrs[c] = NULL;\n    }\n\n    RemoveEntryList(&fcb->list_entry);\n}\n\nstatic NTSTATUS move_across_subvols(file_ref* fileref, ccb* ccb, file_ref* destdir, PANSI_STRING utf8, PUNICODE_STRING fnus, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    LIST_ENTRY move_list, *le;\n    move_entry* me;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n    file_ref* origparent;\n\n    // FIXME - make sure me->dummyfileref and me->dummyfcb get freed properly\n\n    InitializeListHead(&move_list);\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    acquire_fcb_lock_exclusive(fileref->fcb->Vcb);\n\n    me = ExAllocatePoolWithTag(PagedPool, sizeof(move_entry), ALLOC_TAG);\n\n    if (!me) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    origparent = fileref->parent;\n\n    me->fileref = fileref;\n    increase_fileref_refcount(me->fileref);\n    me->dummyfcb = NULL;\n    me->dummyfileref = NULL;\n    me->parent = NULL;\n\n    InsertTailList(&move_list, &me->list_entry);\n\n    le = move_list.Flink;\n    while (le != &move_list) {\n        me = CONTAINING_RECORD(le, move_entry, list_entry);\n\n        ExAcquireResourceSharedLite(me->fileref->fcb->Header.Resource, true);\n\n        if (!me->fileref->fcb->ads && me->fileref->fcb->subvol == origparent->fcb->subvol) {\n            Status = add_children_to_move_list(fileref->fcb->Vcb, me, Irp);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"add_children_to_move_list returned %08lx\\n\", Status);\n                ExReleaseResourceLite(me->fileref->fcb->Header.Resource);\n                goto end;\n            }\n        }\n\n        ExReleaseResourceLite(me->fileref->fcb->Header.Resource);\n\n        le = le->Flink;\n    }\n\n    send_notification_fileref(fileref, fileref->fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED, NULL);\n\n    // loop through list and create new inodes\n\n    le = move_list.Flink;\n    while (le != &move_list) {\n        me = CONTAINING_RECORD(le, move_entry, list_entry);\n\n        if (me->fileref->fcb->inode != SUBVOL_ROOT_INODE && me->fileref->fcb != fileref->fcb->Vcb->dummy_fcb) {\n            if (!me->dummyfcb) {\n                ULONG defda;\n\n                ExAcquireResourceExclusiveLite(me->fileref->fcb->Header.Resource, true);\n\n                Status = duplicate_fcb(me->fileref->fcb, &me->dummyfcb);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"duplicate_fcb returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(me->fileref->fcb->Header.Resource);\n                    goto end;\n                }\n\n                me->dummyfcb->subvol = me->fileref->fcb->subvol;\n                me->dummyfcb->inode = me->fileref->fcb->inode;\n                me->dummyfcb->hash = me->fileref->fcb->hash;\n\n                if (!me->dummyfcb->ads) {\n                    me->dummyfcb->sd_dirty = me->fileref->fcb->sd_dirty;\n                    me->dummyfcb->atts_changed = me->fileref->fcb->atts_changed;\n                    me->dummyfcb->atts_deleted = me->fileref->fcb->atts_deleted;\n                    me->dummyfcb->extents_changed = me->fileref->fcb->extents_changed;\n                    me->dummyfcb->reparse_xattr_changed = me->fileref->fcb->reparse_xattr_changed;\n                    me->dummyfcb->ea_changed = me->fileref->fcb->ea_changed;\n                }\n\n                me->dummyfcb->created = me->fileref->fcb->created;\n                me->dummyfcb->deleted = me->fileref->fcb->deleted;\n                mark_fcb_dirty(me->dummyfcb);\n\n                if (!me->fileref->fcb->ads) {\n                    LIST_ENTRY* le2;\n\n                    me->fileref->fcb->subvol = destdir->fcb->subvol;\n                    me->fileref->fcb->inode = InterlockedIncrement64(&destdir->fcb->subvol->lastinode);\n                    me->fileref->fcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&me->fileref->fcb->inode, sizeof(uint64_t));\n                    me->fileref->fcb->inode_item.st_nlink = 1;\n\n                    defda = get_file_attributes(me->fileref->fcb->Vcb, me->fileref->fcb->subvol, me->fileref->fcb->inode,\n                                                me->fileref->fcb->type, me->fileref->dc && me->fileref->dc->name.Length >= sizeof(WCHAR) && me->fileref->dc->name.Buffer[0] == '.',\n                                                true, Irp);\n\n                    me->fileref->fcb->sd_dirty = !!me->fileref->fcb->sd;\n                    me->fileref->fcb->atts_changed = defda != me->fileref->fcb->atts;\n                    me->fileref->fcb->extents_changed = !IsListEmpty(&me->fileref->fcb->extents);\n                    me->fileref->fcb->reparse_xattr_changed = !!me->fileref->fcb->reparse_xattr.Buffer;\n                    me->fileref->fcb->ea_changed = !!me->fileref->fcb->ea_xattr.Buffer;\n                    me->fileref->fcb->xattrs_changed = !IsListEmpty(&me->fileref->fcb->xattrs);\n                    me->fileref->fcb->inode_item_changed = true;\n\n                    le2 = me->fileref->fcb->xattrs.Flink;\n                    while (le2 != &me->fileref->fcb->xattrs) {\n                        xattr* xa = CONTAINING_RECORD(le2, xattr, list_entry);\n\n                        xa->dirty = true;\n\n                        le2 = le2->Flink;\n                    }\n\n                    if (le == move_list.Flink) { // first entry\n                        me->fileref->fcb->inode_item.transid = me->fileref->fcb->Vcb->superblock.generation;\n                        me->fileref->fcb->inode_item.sequence++;\n\n                        if (!ccb->user_set_change_time)\n                            me->fileref->fcb->inode_item.st_ctime = now;\n                    }\n\n                    le2 = me->fileref->fcb->extents.Flink;\n                    while (le2 != &me->fileref->fcb->extents) {\n                        extent* ext = CONTAINING_RECORD(le2, extent, list_entry);\n\n                        if (!ext->ignore && (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC)) {\n                            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n                            if (ed2->size != 0) {\n                                chunk* c = get_chunk_from_address(me->fileref->fcb->Vcb, ed2->address);\n\n                                if (!c) {\n                                    ERR(\"get_chunk_from_address(%I64x) failed\\n\", ed2->address);\n                                } else {\n                                    Status = update_changed_extent_ref(me->fileref->fcb->Vcb, c, ed2->address, ed2->size, me->fileref->fcb->subvol->id, me->fileref->fcb->inode,\n                                                                       ext->offset - ed2->offset, 1, me->fileref->fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp);\n\n                                    if (!NT_SUCCESS(Status)) {\n                                        ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                                        ExReleaseResourceLite(me->fileref->fcb->Header.Resource);\n                                        goto end;\n                                    }\n                                }\n\n                            }\n                        }\n\n                        le2 = le2->Flink;\n                    }\n\n                    add_fcb_to_subvol(me->dummyfcb);\n                    remove_fcb_from_subvol(me->fileref->fcb);\n                    add_fcb_to_subvol(me->fileref->fcb);\n                } else {\n                    me->fileref->fcb->subvol = me->parent->fileref->fcb->subvol;\n                    me->fileref->fcb->inode = me->parent->fileref->fcb->inode;\n                    me->fileref->fcb->hash = me->parent->fileref->fcb->hash;\n\n                    // put stream after parent in FCB list\n                    InsertHeadList(&me->parent->fileref->fcb->list_entry, &me->fileref->fcb->list_entry);\n                }\n\n                me->fileref->fcb->created = true;\n\n                InsertTailList(&me->fileref->fcb->Vcb->all_fcbs, &me->dummyfcb->list_entry_all);\n\n                while (!IsListEmpty(&me->fileref->fcb->hardlinks)) {\n                    hardlink* hl = CONTAINING_RECORD(RemoveHeadList(&me->fileref->fcb->hardlinks), hardlink, list_entry);\n\n                    if (hl->name.Buffer)\n                        ExFreePool(hl->name.Buffer);\n\n                    if (hl->utf8.Buffer)\n                        ExFreePool(hl->utf8.Buffer);\n\n                    ExFreePool(hl);\n                }\n\n                me->fileref->fcb->inode_item_changed = true;\n                mark_fcb_dirty(me->fileref->fcb);\n\n                if ((!me->dummyfcb->ads && me->dummyfcb->inode_item.st_nlink > 1) || (me->dummyfcb->ads && me->parent->dummyfcb->inode_item.st_nlink > 1)) {\n                    LIST_ENTRY* le2 = le->Flink;\n\n                    while (le2 != &move_list) {\n                        move_entry* me2 = CONTAINING_RECORD(le2, move_entry, list_entry);\n\n                        if (me2->fileref->fcb == me->fileref->fcb && !me2->fileref->fcb->ads) {\n                            me2->dummyfcb = me->dummyfcb;\n                            InterlockedIncrement(&me->dummyfcb->refcount);\n                        }\n\n                        le2 = le2->Flink;\n                    }\n                }\n\n                ExReleaseResourceLite(me->fileref->fcb->Header.Resource);\n            } else {\n                ExAcquireResourceExclusiveLite(me->fileref->fcb->Header.Resource, true);\n                me->fileref->fcb->inode_item.st_nlink++;\n                me->fileref->fcb->inode_item_changed = true;\n                ExReleaseResourceLite(me->fileref->fcb->Header.Resource);\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    fileref->fcb->subvol->root_item.ctransid = fileref->fcb->Vcb->superblock.generation;\n    fileref->fcb->subvol->root_item.ctime = now;\n\n    // loop through list and create new filerefs\n\n    le = move_list.Flink;\n    while (le != &move_list) {\n        hardlink* hl;\n        bool name_changed = false;\n\n        me = CONTAINING_RECORD(le, move_entry, list_entry);\n\n        me->dummyfileref = create_fileref(fileref->fcb->Vcb);\n        if (!me->dummyfileref) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        if (me->fileref->fcb == me->fileref->fcb->Vcb->dummy_fcb) {\n            root* r = me->parent ? me->parent->fileref->fcb->subvol : destdir->fcb->subvol;\n\n            Status = create_directory_fcb(me->fileref->fcb->Vcb, r, me->fileref->parent->fcb, &me->fileref->fcb);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"create_directory_fcb returned %08lx\\n\", Status);\n                goto end;\n            }\n\n            me->fileref->dc->key.obj_id = me->fileref->fcb->inode;\n            me->fileref->dc->key.obj_type = TYPE_INODE_ITEM;\n\n            me->dummyfileref->fcb = me->fileref->fcb->Vcb->dummy_fcb;\n        } else if (me->fileref->fcb->inode == SUBVOL_ROOT_INODE) {\n            me->dummyfileref->fcb = me->fileref->fcb;\n\n            me->fileref->fcb->subvol->parent = le == move_list.Flink ? destdir->fcb->subvol->id : me->parent->fileref->fcb->subvol->id;\n        } else\n            me->dummyfileref->fcb = me->dummyfcb;\n\n        InterlockedIncrement(&me->dummyfileref->fcb->refcount);\n\n        me->dummyfileref->oldutf8 = me->fileref->oldutf8;\n        me->dummyfileref->oldindex = me->fileref->dc->index;\n\n        if (le == move_list.Flink && (me->fileref->dc->utf8.Length != utf8->Length || RtlCompareMemory(me->fileref->dc->utf8.Buffer, utf8->Buffer, utf8->Length) != utf8->Length))\n            name_changed = true;\n\n        if (!me->dummyfileref->oldutf8.Buffer) {\n            me->dummyfileref->oldutf8.Buffer = ExAllocatePoolWithTag(PagedPool, me->fileref->dc->utf8.Length, ALLOC_TAG);\n            if (!me->dummyfileref->oldutf8.Buffer) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            RtlCopyMemory(me->dummyfileref->oldutf8.Buffer, me->fileref->dc->utf8.Buffer, me->fileref->dc->utf8.Length);\n\n            me->dummyfileref->oldutf8.Length = me->dummyfileref->oldutf8.MaximumLength = me->fileref->dc->utf8.Length;\n        }\n\n        me->dummyfileref->delete_on_close = me->fileref->delete_on_close;\n        me->dummyfileref->deleted = me->fileref->deleted;\n\n        me->dummyfileref->created = me->fileref->created;\n        me->fileref->created = true;\n\n        me->dummyfileref->parent = me->parent ? me->parent->dummyfileref : origparent;\n        increase_fileref_refcount(me->dummyfileref->parent);\n\n        ExAcquireResourceExclusiveLite(&me->dummyfileref->parent->fcb->nonpaged->dir_children_lock, true);\n        InsertTailList(&me->dummyfileref->parent->children, &me->dummyfileref->list_entry);\n        ExReleaseResourceLite(&me->dummyfileref->parent->fcb->nonpaged->dir_children_lock);\n\n        if (me->dummyfileref->fcb->type == BTRFS_TYPE_DIRECTORY)\n            me->dummyfileref->fcb->fileref = me->dummyfileref;\n\n        if (!me->parent) {\n            RemoveEntryList(&me->fileref->list_entry);\n\n            increase_fileref_refcount(destdir);\n\n            if (me->fileref->dc) {\n                // remove from old parent\n                ExAcquireResourceExclusiveLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock, true);\n                RemoveEntryList(&me->fileref->dc->list_entry_index);\n                remove_dir_child_from_hash_lists(me->fileref->parent->fcb, me->fileref->dc);\n                ExReleaseResourceLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock);\n\n                me->fileref->parent->fcb->inode_item.st_size -= me->fileref->dc->utf8.Length * 2;\n                me->fileref->parent->fcb->inode_item.transid = me->fileref->fcb->Vcb->superblock.generation;\n                me->fileref->parent->fcb->inode_item.sequence++;\n                me->fileref->parent->fcb->inode_item.st_ctime = now;\n                me->fileref->parent->fcb->inode_item.st_mtime = now;\n                me->fileref->parent->fcb->inode_item_changed = true;\n                mark_fcb_dirty(me->fileref->parent->fcb);\n\n                if (name_changed) {\n                    ExFreePool(me->fileref->dc->utf8.Buffer);\n                    ExFreePool(me->fileref->dc->name.Buffer);\n                    ExFreePool(me->fileref->dc->name_uc.Buffer);\n\n                    me->fileref->dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8->Length, ALLOC_TAG);\n                    if (!me->fileref->dc->utf8.Buffer) {\n                        ERR(\"out of memory\\n\");\n                        Status = STATUS_INSUFFICIENT_RESOURCES;\n                        goto end;\n                    }\n\n                    me->fileref->dc->utf8.Length = me->fileref->dc->utf8.MaximumLength = utf8->Length;\n                    RtlCopyMemory(me->fileref->dc->utf8.Buffer, utf8->Buffer, utf8->Length);\n\n                    me->fileref->dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, fnus->Length, ALLOC_TAG);\n                    if (!me->fileref->dc->name.Buffer) {\n                        ERR(\"out of memory\\n\");\n                        Status = STATUS_INSUFFICIENT_RESOURCES;\n                        goto end;\n                    }\n\n                    me->fileref->dc->name.Length = me->fileref->dc->name.MaximumLength = fnus->Length;\n                    RtlCopyMemory(me->fileref->dc->name.Buffer, fnus->Buffer, fnus->Length);\n\n                    Status = RtlUpcaseUnicodeString(&fileref->dc->name_uc, &fileref->dc->name, true);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n                        goto end;\n                    }\n\n                    me->fileref->dc->hash = calc_crc32c(0xffffffff, (uint8_t*)me->fileref->dc->name.Buffer, me->fileref->dc->name.Length);\n                    me->fileref->dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)me->fileref->dc->name_uc.Buffer, me->fileref->dc->name_uc.Length);\n                }\n\n                if (me->fileref->dc->key.obj_type == TYPE_INODE_ITEM)\n                    me->fileref->dc->key.obj_id = me->fileref->fcb->inode;\n\n                // add to new parent\n\n                ExAcquireResourceExclusiveLite(&destdir->fcb->nonpaged->dir_children_lock, true);\n\n                if (IsListEmpty(&destdir->fcb->dir_children_index))\n                    me->fileref->dc->index = 2;\n                else {\n                    dir_child* dc2 = CONTAINING_RECORD(destdir->fcb->dir_children_index.Blink, dir_child, list_entry_index);\n\n                    me->fileref->dc->index = max(2, dc2->index + 1);\n                }\n\n                InsertTailList(&destdir->fcb->dir_children_index, &me->fileref->dc->list_entry_index);\n                insert_dir_child_into_hash_lists(destdir->fcb, me->fileref->dc);\n                ExReleaseResourceLite(&destdir->fcb->nonpaged->dir_children_lock);\n            }\n\n            free_fileref(me->fileref->parent);\n            me->fileref->parent = destdir;\n\n            ExAcquireResourceExclusiveLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock, true);\n            InsertTailList(&me->fileref->parent->children, &me->fileref->list_entry);\n            ExReleaseResourceLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock);\n\n            TRACE(\"me->fileref->parent->fcb->inode_item.st_size (inode %I64x) was %I64x\\n\", me->fileref->parent->fcb->inode, me->fileref->parent->fcb->inode_item.st_size);\n            me->fileref->parent->fcb->inode_item.st_size += me->fileref->dc->utf8.Length * 2;\n            TRACE(\"me->fileref->parent->fcb->inode_item.st_size (inode %I64x) now %I64x\\n\", me->fileref->parent->fcb->inode, me->fileref->parent->fcb->inode_item.st_size);\n            me->fileref->parent->fcb->inode_item.transid = me->fileref->fcb->Vcb->superblock.generation;\n            me->fileref->parent->fcb->inode_item.sequence++;\n            me->fileref->parent->fcb->inode_item.st_ctime = now;\n            me->fileref->parent->fcb->inode_item.st_mtime = now;\n            me->fileref->parent->fcb->inode_item_changed = true;\n            mark_fcb_dirty(me->fileref->parent->fcb);\n        } else {\n            if (me->fileref->dc) {\n                ExAcquireResourceExclusiveLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock, true);\n                RemoveEntryList(&me->fileref->dc->list_entry_index);\n\n                if (!me->fileref->fcb->ads)\n                    remove_dir_child_from_hash_lists(me->fileref->parent->fcb, me->fileref->dc);\n\n                ExReleaseResourceLite(&me->fileref->parent->fcb->nonpaged->dir_children_lock);\n\n                ExAcquireResourceExclusiveLite(&me->parent->fileref->fcb->nonpaged->dir_children_lock, true);\n\n                if (me->fileref->fcb->ads)\n                    InsertHeadList(&me->parent->fileref->fcb->dir_children_index, &me->fileref->dc->list_entry_index);\n                else {\n                    if (me->fileref->fcb->inode != SUBVOL_ROOT_INODE)\n                        me->fileref->dc->key.obj_id = me->fileref->fcb->inode;\n\n                    if (IsListEmpty(&me->parent->fileref->fcb->dir_children_index))\n                        me->fileref->dc->index = 2;\n                    else {\n                        dir_child* dc2 = CONTAINING_RECORD(me->parent->fileref->fcb->dir_children_index.Blink, dir_child, list_entry_index);\n\n                        me->fileref->dc->index = max(2, dc2->index + 1);\n                    }\n\n                    InsertTailList(&me->parent->fileref->fcb->dir_children_index, &me->fileref->dc->list_entry_index);\n                    insert_dir_child_into_hash_lists(me->parent->fileref->fcb, me->fileref->dc);\n                }\n\n                ExReleaseResourceLite(&me->parent->fileref->fcb->nonpaged->dir_children_lock);\n            }\n        }\n\n        if (!me->dummyfileref->fcb->ads) {\n            Status = delete_fileref(me->dummyfileref, NULL, false, Irp, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_fileref returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        if (me->fileref->fcb->inode_item.st_nlink > 1) {\n            hl = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG);\n            if (!hl) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            hl->parent = me->fileref->parent->fcb->inode;\n            hl->index = me->fileref->dc->index;\n\n            hl->utf8.Length = hl->utf8.MaximumLength = me->fileref->dc->utf8.Length;\n            hl->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl->utf8.MaximumLength, ALLOC_TAG);\n            if (!hl->utf8.Buffer) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                ExFreePool(hl);\n                goto end;\n            }\n\n            RtlCopyMemory(hl->utf8.Buffer, me->fileref->dc->utf8.Buffer, me->fileref->dc->utf8.Length);\n\n            hl->name.Length = hl->name.MaximumLength = me->fileref->dc->name.Length;\n            hl->name.Buffer = ExAllocatePoolWithTag(PagedPool, hl->name.MaximumLength, ALLOC_TAG);\n            if (!hl->name.Buffer) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                ExFreePool(hl->utf8.Buffer);\n                ExFreePool(hl);\n                goto end;\n            }\n\n            RtlCopyMemory(hl->name.Buffer, me->fileref->dc->name.Buffer, me->fileref->dc->name.Length);\n\n            InsertTailList(&me->fileref->fcb->hardlinks, &hl->list_entry);\n        }\n\n        mark_fileref_dirty(me->fileref);\n\n        le = le->Flink;\n    }\n\n    // loop through, and only mark streams as deleted if their parent inodes are also deleted\n\n    le = move_list.Flink;\n    while (le != &move_list) {\n        me = CONTAINING_RECORD(le, move_entry, list_entry);\n\n        if (me->dummyfileref->fcb->ads && me->parent->dummyfileref->fcb->deleted) {\n            Status = delete_fileref(me->dummyfileref, NULL, false, Irp, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_fileref returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    destdir->fcb->subvol->root_item.ctransid = destdir->fcb->Vcb->superblock.generation;\n    destdir->fcb->subvol->root_item.ctime = now;\n\n    me = CONTAINING_RECORD(move_list.Flink, move_entry, list_entry);\n    send_notification_fileref(fileref, fileref->fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL);\n    send_notification_fileref(me->dummyfileref->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n    send_notification_fileref(fileref->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    while (!IsListEmpty(&move_list)) {\n        le = RemoveHeadList(&move_list);\n        me = CONTAINING_RECORD(le, move_entry, list_entry);\n\n        if (me->dummyfcb)\n            free_fcb(me->dummyfcb);\n\n        if (me->dummyfileref)\n            free_fileref(me->dummyfileref);\n\n        free_fileref(me->fileref);\n\n        ExFreePool(me);\n    }\n\n    destdir->fcb->subvol->fcbs_version++;\n    fileref->fcb->subvol->fcbs_version++;\n\n    release_fcb_lock(fileref->fcb->Vcb);\n\n    return Status;\n}\n\nvoid insert_dir_child_into_hash_lists(fcb* fcb, dir_child* dc) {\n    bool inserted;\n    LIST_ENTRY* le;\n    uint8_t c, d;\n\n    c = dc->hash >> 24;\n\n    inserted = false;\n\n    d = c;\n    do {\n        le = fcb->hash_ptrs[d];\n\n        if (d == 0)\n            break;\n\n        d--;\n    } while (!le);\n\n    if (!le)\n        le = fcb->dir_children_hash.Flink;\n\n    while (le != &fcb->dir_children_hash) {\n        dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_hash);\n\n        if (dc2->hash > dc->hash) {\n            InsertHeadList(le->Blink, &dc->list_entry_hash);\n            inserted = true;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!inserted)\n        InsertTailList(&fcb->dir_children_hash, &dc->list_entry_hash);\n\n    if (!fcb->hash_ptrs[c])\n        fcb->hash_ptrs[c] = &dc->list_entry_hash;\n    else {\n        dir_child* dc2 = CONTAINING_RECORD(fcb->hash_ptrs[c], dir_child, list_entry_hash);\n\n        if (dc2->hash > dc->hash)\n            fcb->hash_ptrs[c] = &dc->list_entry_hash;\n    }\n\n    c = dc->hash_uc >> 24;\n\n    inserted = false;\n\n    d = c;\n    do {\n        le = fcb->hash_ptrs_uc[d];\n\n        if (d == 0)\n            break;\n\n        d--;\n    } while (!le);\n\n    if (!le)\n        le = fcb->dir_children_hash_uc.Flink;\n\n    while (le != &fcb->dir_children_hash_uc) {\n        dir_child* dc2 = CONTAINING_RECORD(le, dir_child, list_entry_hash_uc);\n\n        if (dc2->hash_uc > dc->hash_uc) {\n            InsertHeadList(le->Blink, &dc->list_entry_hash_uc);\n            inserted = true;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!inserted)\n        InsertTailList(&fcb->dir_children_hash_uc, &dc->list_entry_hash_uc);\n\n    if (!fcb->hash_ptrs_uc[c])\n        fcb->hash_ptrs_uc[c] = &dc->list_entry_hash_uc;\n    else {\n        dir_child* dc2 = CONTAINING_RECORD(fcb->hash_ptrs_uc[c], dir_child, list_entry_hash_uc);\n\n        if (dc2->hash_uc > dc->hash_uc)\n            fcb->hash_ptrs_uc[c] = &dc->list_entry_hash_uc;\n    }\n}\n\nstatic NTSTATUS rename_stream_to_file(device_extension* Vcb, file_ref* fileref, ccb* ccb, ULONG flags,\n                                      PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    file_ref* ofr;\n    ANSI_STRING adsdata;\n    dir_child* dc;\n    fcb* dummyfcb;\n\n    if (fileref->fcb->type != BTRFS_TYPE_FILE)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE) && fileref->parent->fcb->atts & FILE_ATTRIBUTE_READONLY) {\n        WARN(\"trying to rename stream on readonly file\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (Irp->RequestorMode == UserMode && ccb && !(ccb->access & DELETE)) {\n        WARN(\"insufficient permissions\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (!(flags & FILE_RENAME_REPLACE_IF_EXISTS)) // file will always exist\n        return STATUS_OBJECT_NAME_COLLISION;\n\n    // FIXME - POSIX overwrites of stream?\n\n    ofr = fileref->parent;\n\n    if (ofr->open_count > 0) {\n        WARN(\"trying to overwrite open file\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (ofr->fcb->inode_item.st_size > 0) {\n        WARN(\"can only overwrite existing stream if it is zero-length\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    dummyfcb = create_fcb(Vcb, PagedPool);\n    if (!dummyfcb) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    // copy parent fcb onto this one\n\n    fileref->fcb->subvol = ofr->fcb->subvol;\n    fileref->fcb->inode = ofr->fcb->inode;\n    fileref->fcb->hash = ofr->fcb->hash;\n    fileref->fcb->type = ofr->fcb->type;\n    fileref->fcb->inode_item = ofr->fcb->inode_item;\n\n    fileref->fcb->sd = ofr->fcb->sd;\n    ofr->fcb->sd = NULL;\n\n    fileref->fcb->deleted = ofr->fcb->deleted;\n    fileref->fcb->atts = ofr->fcb->atts;\n\n    fileref->fcb->reparse_xattr = ofr->fcb->reparse_xattr;\n    ofr->fcb->reparse_xattr.Buffer = NULL;\n    ofr->fcb->reparse_xattr.Length = ofr->fcb->reparse_xattr.MaximumLength = 0;\n\n    fileref->fcb->ea_xattr = ofr->fcb->ea_xattr;\n    ofr->fcb->ea_xattr.Buffer = NULL;\n    ofr->fcb->ea_xattr.Length = ofr->fcb->ea_xattr.MaximumLength = 0;\n\n    fileref->fcb->ealen = ofr->fcb->ealen;\n\n    while (!IsListEmpty(&ofr->fcb->hardlinks)) {\n        InsertTailList(&fileref->fcb->hardlinks, RemoveHeadList(&ofr->fcb->hardlinks));\n    }\n\n    fileref->fcb->inode_item_changed = true;\n    fileref->fcb->prop_compression = ofr->fcb->prop_compression;\n\n    while (!IsListEmpty(&ofr->fcb->xattrs)) {\n        InsertTailList(&fileref->fcb->xattrs, RemoveHeadList(&ofr->fcb->xattrs));\n    }\n\n    fileref->fcb->marked_as_orphan = ofr->fcb->marked_as_orphan;\n    fileref->fcb->case_sensitive = ofr->fcb->case_sensitive;\n    fileref->fcb->case_sensitive_set = ofr->fcb->case_sensitive_set;\n\n    while (!IsListEmpty(&ofr->fcb->dir_children_index)) {\n        InsertTailList(&fileref->fcb->dir_children_index, RemoveHeadList(&ofr->fcb->dir_children_index));\n    }\n\n    while (!IsListEmpty(&ofr->fcb->dir_children_hash)) {\n        InsertTailList(&fileref->fcb->dir_children_hash, RemoveHeadList(&ofr->fcb->dir_children_hash));\n    }\n\n    while (!IsListEmpty(&ofr->fcb->dir_children_hash_uc)) {\n        InsertTailList(&fileref->fcb->dir_children_hash_uc, RemoveHeadList(&ofr->fcb->dir_children_hash_uc));\n    }\n\n    fileref->fcb->hash_ptrs = ofr->fcb->hash_ptrs;\n    fileref->fcb->hash_ptrs_uc = ofr->fcb->hash_ptrs_uc;\n\n    ofr->fcb->hash_ptrs = NULL;\n    ofr->fcb->hash_ptrs_uc = NULL;\n\n    fileref->fcb->sd_dirty = ofr->fcb->sd_dirty;\n    fileref->fcb->sd_deleted = ofr->fcb->sd_deleted;\n    fileref->fcb->atts_changed = ofr->fcb->atts_changed;\n    fileref->fcb->atts_deleted = ofr->fcb->atts_deleted;\n    fileref->fcb->extents_changed = true;\n    fileref->fcb->reparse_xattr_changed = ofr->fcb->reparse_xattr_changed;\n    fileref->fcb->ea_changed = ofr->fcb->ea_changed;\n    fileref->fcb->prop_compression_changed = ofr->fcb->prop_compression_changed;\n    fileref->fcb->xattrs_changed = ofr->fcb->xattrs_changed;\n    fileref->fcb->created = ofr->fcb->created;\n    fileref->fcb->ads = false;\n\n    if (fileref->fcb->adsxattr.Buffer) {\n        ExFreePool(fileref->fcb->adsxattr.Buffer);\n        fileref->fcb->adsxattr.Length = fileref->fcb->adsxattr.MaximumLength = 0;\n        fileref->fcb->adsxattr.Buffer = NULL;\n    }\n\n    adsdata = fileref->fcb->adsdata;\n\n    fileref->fcb->adsdata.Buffer = NULL;\n    fileref->fcb->adsdata.Length = fileref->fcb->adsdata.MaximumLength = 0;\n\n    acquire_fcb_lock_exclusive(Vcb);\n\n    RemoveEntryList(&fileref->fcb->list_entry);\n    InsertHeadList(ofr->fcb->list_entry.Blink, &fileref->fcb->list_entry);\n\n    if (fileref->fcb->subvol->fcbs_ptrs[fileref->fcb->hash >> 24] == &ofr->fcb->list_entry)\n        fileref->fcb->subvol->fcbs_ptrs[fileref->fcb->hash >> 24] = &fileref->fcb->list_entry;\n\n    RemoveEntryList(&ofr->fcb->list_entry);\n\n    release_fcb_lock(Vcb);\n\n    ofr->fcb->list_entry.Flink = ofr->fcb->list_entry.Blink = NULL;\n\n    mark_fcb_dirty(fileref->fcb);\n\n    // mark old parent fcb so it gets ignored by flush_fcb\n    ofr->fcb->created = true;\n    ofr->fcb->deleted = true;\n\n    mark_fcb_dirty(ofr->fcb);\n\n    // copy parent fileref onto this one\n\n    fileref->oldutf8 = ofr->oldutf8;\n    ofr->oldutf8.Buffer = NULL;\n    ofr->oldutf8.Length = ofr->oldutf8.MaximumLength = 0;\n\n    fileref->oldindex = ofr->oldindex;\n    fileref->delete_on_close = ofr->delete_on_close;\n    fileref->posix_delete = ofr->posix_delete;\n    fileref->deleted = ofr->deleted;\n    fileref->created = ofr->created;\n\n    fileref->parent = ofr->parent;\n\n    RemoveEntryList(&fileref->list_entry);\n    InsertHeadList(ofr->list_entry.Blink, &fileref->list_entry);\n    RemoveEntryList(&ofr->list_entry);\n    ofr->list_entry.Flink = ofr->list_entry.Blink = NULL;\n\n    while (!IsListEmpty(&ofr->children)) {\n        file_ref* fr = CONTAINING_RECORD(RemoveHeadList(&ofr->children), file_ref, list_entry);\n\n        free_fileref(fr->parent);\n\n        fr->parent = fileref;\n        InterlockedIncrement(&fileref->refcount);\n\n        InsertTailList(&fileref->children, &fr->list_entry);\n    }\n\n    dc = fileref->dc;\n\n    fileref->dc = ofr->dc;\n    fileref->dc->fileref = fileref;\n\n    mark_fileref_dirty(fileref);\n\n    // mark old parent fileref so it gets ignored by flush_fileref\n    ofr->created = true;\n    ofr->deleted = true;\n\n    // write file data\n\n    fileref->fcb->inode_item.st_size = adsdata.Length;\n\n    if (adsdata.Length > 0) {\n        bool make_inline = adsdata.Length <= Vcb->options.max_inline;\n\n        if (make_inline) {\n            EXTENT_DATA* ed = ExAllocatePoolWithTag(PagedPool, (uint16_t)(offsetof(EXTENT_DATA, data[0]) + adsdata.Length), ALLOC_TAG);\n            if (!ed) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(adsdata.Buffer);\n                reap_fcb(dummyfcb);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            ed->generation = Vcb->superblock.generation;\n            ed->decoded_size = adsdata.Length;\n            ed->compression = BTRFS_COMPRESSION_NONE;\n            ed->encryption = BTRFS_ENCRYPTION_NONE;\n            ed->encoding = BTRFS_ENCODING_NONE;\n            ed->type = EXTENT_TYPE_INLINE;\n\n            RtlCopyMemory(ed->data, adsdata.Buffer, adsdata.Length);\n\n            ExFreePool(adsdata.Buffer);\n\n            Status = add_extent_to_fcb(fileref->fcb, 0, ed, (uint16_t)(offsetof(EXTENT_DATA, data[0]) + adsdata.Length), false, NULL, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"add_extent_to_fcb returned %08lx\\n\", Status);\n                ExFreePool(ed);\n                reap_fcb(dummyfcb);\n                return Status;\n            }\n\n            ExFreePool(ed);\n        } else if (adsdata.Length & (Vcb->superblock.sector_size - 1)) {\n            char* newbuf = ExAllocatePoolWithTag(PagedPool, (uint16_t)sector_align(adsdata.Length, Vcb->superblock.sector_size), ALLOC_TAG);\n            if (!newbuf) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(adsdata.Buffer);\n                reap_fcb(dummyfcb);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(newbuf, adsdata.Buffer, adsdata.Length);\n            RtlZeroMemory(newbuf + adsdata.Length, (uint16_t)(sector_align(adsdata.Length, Vcb->superblock.sector_size) - adsdata.Length));\n\n            ExFreePool(adsdata.Buffer);\n\n            adsdata.Buffer = newbuf;\n            adsdata.Length = adsdata.MaximumLength = (uint16_t)sector_align(adsdata.Length, Vcb->superblock.sector_size);\n        }\n\n        if (!make_inline) {\n            Status = do_write_file(fileref->fcb, 0, adsdata.Length, adsdata.Buffer, Irp, false, 0, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"do_write_file returned %08lx\\n\", Status);\n                ExFreePool(adsdata.Buffer);\n                reap_fcb(dummyfcb);\n                return Status;\n            }\n\n            ExFreePool(adsdata.Buffer);\n        }\n\n        fileref->fcb->inode_item.st_blocks = adsdata.Length;\n        fileref->fcb->inode_item_changed = true;\n    }\n\n    RemoveEntryList(&dc->list_entry_index);\n\n    if (dc->utf8.Buffer)\n        ExFreePool(dc->utf8.Buffer);\n\n    if (dc->name.Buffer)\n        ExFreePool(dc->name.Buffer);\n\n    if (dc->name_uc.Buffer)\n        ExFreePool(dc->name_uc.Buffer);\n\n    ExFreePool(dc);\n\n    // FIXME - csums?\n\n    // add dummy deleted xattr with old name\n\n    dummyfcb->Vcb = Vcb;\n    dummyfcb->subvol = fileref->fcb->subvol;\n    dummyfcb->inode = fileref->fcb->inode;\n    dummyfcb->hash = fileref->fcb->hash;\n    dummyfcb->adsxattr = fileref->fcb->adsxattr;\n    dummyfcb->adshash = fileref->fcb->adshash;\n    dummyfcb->ads = true;\n    dummyfcb->deleted = true;\n\n    acquire_fcb_lock_exclusive(Vcb);\n    add_fcb_to_subvol(dummyfcb);\n    InsertTailList(&Vcb->all_fcbs, &dummyfcb->list_entry_all);\n    dummyfcb->subvol->fcbs_version++;\n    release_fcb_lock(Vcb);\n\n    // FIXME - dummyfileref as well?\n\n    mark_fcb_dirty(dummyfcb);\n\n    free_fcb(dummyfcb);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS rename_stream(device_extension* Vcb, file_ref* fileref, ccb* ccb, FILE_RENAME_INFORMATION_EX* fri,\n                              ULONG flags, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    UNICODE_STRING fn;\n    file_ref* sf = NULL;\n    uint16_t newmaxlen;\n    ULONG utf8len;\n    ANSI_STRING utf8;\n    UNICODE_STRING utf16, utf16uc;\n    ANSI_STRING adsxattr;\n    uint32_t crc32;\n    fcb* dummyfcb;\n\n    static const WCHAR datasuf[] = L\":$DATA\";\n    static const char xapref[] = \"user.\";\n\n    if (!fileref) {\n        ERR(\"fileref not set\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (!fileref->parent) {\n        ERR(\"fileref->parent not set\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (fri->FileNameLength < sizeof(WCHAR)) {\n        WARN(\"filename too short\\n\");\n        return STATUS_OBJECT_NAME_INVALID;\n    }\n\n    if (fri->FileName[0] != ':') {\n        WARN(\"destination filename must begin with a colon\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (Irp->RequestorMode == UserMode && ccb && !(ccb->access & DELETE)) {\n        WARN(\"insufficient permissions\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    fn.Buffer = &fri->FileName[1];\n    fn.Length = fn.MaximumLength = (USHORT)(fri->FileNameLength - sizeof(WCHAR));\n\n    // remove :$DATA suffix\n    if (fn.Length >= sizeof(datasuf) - sizeof(WCHAR) &&\n        RtlCompareMemory(&fn.Buffer[(fn.Length - sizeof(datasuf) + sizeof(WCHAR))/sizeof(WCHAR)], datasuf, sizeof(datasuf) - sizeof(WCHAR)) == sizeof(datasuf) - sizeof(WCHAR))\n        fn.Length -= sizeof(datasuf) - sizeof(WCHAR);\n\n    if (fn.Length == 0)\n        return rename_stream_to_file(Vcb, fileref, ccb, flags, Irp, rollback);\n\n    Status = check_file_name_valid(&fn, false, true);\n    if (!NT_SUCCESS(Status)) {\n        WARN(\"invalid stream name %.*S\\n\", (int)(fn.Length / sizeof(WCHAR)), fn.Buffer);\n        return Status;\n    }\n\n    if (!(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE) && fileref->parent->fcb->atts & FILE_ATTRIBUTE_READONLY) {\n        WARN(\"trying to rename stream on readonly file\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    Status = open_fileref_child(Vcb, fileref->parent, &fn, fileref->parent->fcb->case_sensitive, true, true, PagedPool, &sf, Irp);\n    if (Status != STATUS_OBJECT_NAME_NOT_FOUND) {\n        if (Status == STATUS_SUCCESS) {\n            if (fileref == sf || sf->deleted) {\n                free_fileref(sf);\n                sf = NULL;\n            } else {\n                if (!(flags & FILE_RENAME_REPLACE_IF_EXISTS)) {\n                    Status = STATUS_OBJECT_NAME_COLLISION;\n                    goto end;\n                }\n\n                // FIXME - POSIX overwrites of stream?\n\n                if (sf->open_count > 0) {\n                    WARN(\"trying to overwrite open file\\n\");\n                    Status = STATUS_ACCESS_DENIED;\n                    goto end;\n                }\n\n                if (sf->fcb->adsdata.Length > 0) {\n                    WARN(\"can only overwrite existing stream if it is zero-length\\n\");\n                    Status = STATUS_INVALID_PARAMETER;\n                    goto end;\n                }\n\n                Status = delete_fileref(sf, NULL, false, Irp, rollback);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_fileref returned %08lx\\n\", Status);\n                    goto end;\n                }\n            }\n        } else {\n            ERR(\"open_fileref_child returned %08lx\\n\", Status);\n            goto end;\n        }\n    }\n\n    Status = utf16_to_utf8(NULL, 0, &utf8len, fn.Buffer, fn.Length);\n    if (!NT_SUCCESS(Status))\n        goto end;\n\n    utf8.MaximumLength = utf8.Length = (uint16_t)utf8len;\n    utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG);\n    if (!utf8.Buffer) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    Status = utf16_to_utf8(utf8.Buffer, utf8len, &utf8len, fn.Buffer, fn.Length);\n    if (!NT_SUCCESS(Status)) {\n        ExFreePool(utf8.Buffer);\n        goto end;\n    }\n\n    adsxattr.Length = adsxattr.MaximumLength = sizeof(xapref) - 1 + utf8.Length;\n    adsxattr.Buffer = ExAllocatePoolWithTag(PagedPool, adsxattr.MaximumLength, ALLOC_TAG);\n    if (!adsxattr.Buffer) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        ExFreePool(utf8.Buffer);\n        goto end;\n    }\n\n    RtlCopyMemory(adsxattr.Buffer, xapref, sizeof(xapref) - 1);\n    RtlCopyMemory(&adsxattr.Buffer[sizeof(xapref) - 1], utf8.Buffer, utf8.Length);\n\n    // don't allow if it's one of our reserved names\n\n    if ((adsxattr.Length == sizeof(EA_DOSATTRIB) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_DOSATTRIB, adsxattr.Length) == adsxattr.Length) ||\n        (adsxattr.Length == sizeof(EA_EA) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_EA, adsxattr.Length) == adsxattr.Length) ||\n        (adsxattr.Length == sizeof(EA_REPARSE) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_REPARSE, adsxattr.Length) == adsxattr.Length) ||\n        (adsxattr.Length == sizeof(EA_CASE_SENSITIVE) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_CASE_SENSITIVE, adsxattr.Length) == adsxattr.Length)) {\n        Status = STATUS_OBJECT_NAME_INVALID;\n        ExFreePool(utf8.Buffer);\n        ExFreePool(adsxattr.Buffer);\n        goto end;\n    }\n\n    utf16.Length = utf16.MaximumLength = fn.Length;\n    utf16.Buffer = ExAllocatePoolWithTag(PagedPool, utf16.MaximumLength, ALLOC_TAG);\n    if (!utf16.Buffer) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        ExFreePool(utf8.Buffer);\n        ExFreePool(adsxattr.Buffer);\n        goto end;\n    }\n\n    RtlCopyMemory(utf16.Buffer, fn.Buffer, fn.Length);\n\n    newmaxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) -\n                offsetof(DIR_ITEM, name[0]);\n\n    if (newmaxlen < adsxattr.Length) {\n        WARN(\"cannot rename as data too long\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        ExFreePool(utf8.Buffer);\n        ExFreePool(utf16.Buffer);\n        ExFreePool(adsxattr.Buffer);\n        goto end;\n    }\n\n    newmaxlen -= adsxattr.Length;\n\n    if (newmaxlen < fileref->fcb->adsdata.Length) {\n        WARN(\"cannot rename as data too long\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        ExFreePool(utf8.Buffer);\n        ExFreePool(utf16.Buffer);\n        ExFreePool(adsxattr.Buffer);\n        goto end;\n    }\n\n    Status = RtlUpcaseUnicodeString(&utf16uc, &fn, true);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n        ExFreePool(utf8.Buffer);\n        ExFreePool(utf16.Buffer);\n        ExFreePool(adsxattr.Buffer);\n        goto end;\n    }\n\n    // add dummy deleted xattr with old name\n\n    dummyfcb = create_fcb(Vcb, PagedPool);\n    if (!dummyfcb) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        ExFreePool(utf8.Buffer);\n        ExFreePool(utf16.Buffer);\n        ExFreePool(utf16uc.Buffer);\n        ExFreePool(adsxattr.Buffer);\n        goto end;\n    }\n\n    dummyfcb->Vcb = Vcb;\n    dummyfcb->subvol = fileref->fcb->subvol;\n    dummyfcb->inode = fileref->fcb->inode;\n    dummyfcb->hash = fileref->fcb->hash;\n    dummyfcb->adsxattr = fileref->fcb->adsxattr;\n    dummyfcb->adshash = fileref->fcb->adshash;\n    dummyfcb->ads = true;\n    dummyfcb->deleted = true;\n\n    acquire_fcb_lock_exclusive(Vcb);\n    add_fcb_to_subvol(dummyfcb);\n    InsertTailList(&Vcb->all_fcbs, &dummyfcb->list_entry_all);\n    dummyfcb->subvol->fcbs_version++;\n    release_fcb_lock(Vcb);\n\n    mark_fcb_dirty(dummyfcb);\n\n    free_fcb(dummyfcb);\n\n    // change fcb values\n\n    fileref->dc->utf8 = utf8;\n    fileref->dc->name = utf16;\n    fileref->dc->name_uc = utf16uc;\n\n    crc32 = calc_crc32c(0xfffffffe, (uint8_t*)adsxattr.Buffer, adsxattr.Length);\n\n    fileref->fcb->adsxattr = adsxattr;\n    fileref->fcb->adshash = crc32;\n    fileref->fcb->adsmaxlen = newmaxlen;\n\n    fileref->fcb->created = true;\n\n    mark_fcb_dirty(fileref->fcb);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (sf)\n        free_fileref(sf);\n\n    return Status;\n}\n\nstatic NTSTATUS rename_file_to_stream(device_extension* Vcb, file_ref* fileref, ccb* ccb, FILE_RENAME_INFORMATION_EX* fri,\n                                      ULONG flags, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    UNICODE_STRING fn;\n    file_ref* sf = NULL;\n    uint16_t newmaxlen;\n    ULONG utf8len;\n    ANSI_STRING utf8;\n    UNICODE_STRING utf16, utf16uc;\n    ANSI_STRING adsxattr, adsdata;\n    uint32_t crc32;\n    fcb* dummyfcb;\n    file_ref* dummyfileref;\n    dir_child* dc;\n    LIST_ENTRY* le;\n\n    static const WCHAR datasuf[] = L\":$DATA\";\n    static const char xapref[] = \"user.\";\n\n    if (!fileref) {\n        ERR(\"fileref not set\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (fri->FileNameLength < sizeof(WCHAR)) {\n        WARN(\"filename too short\\n\");\n        return STATUS_OBJECT_NAME_INVALID;\n    }\n\n    if (fri->FileName[0] != ':') {\n        WARN(\"destination filename must begin with a colon\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (Irp->RequestorMode == UserMode && ccb && !(ccb->access & DELETE)) {\n        WARN(\"insufficient permissions\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (fileref->fcb->type != BTRFS_TYPE_FILE)\n        return STATUS_INVALID_PARAMETER;\n\n    fn.Buffer = &fri->FileName[1];\n    fn.Length = fn.MaximumLength = (USHORT)(fri->FileNameLength - sizeof(WCHAR));\n\n    // remove :$DATA suffix\n    if (fn.Length >= sizeof(datasuf) - sizeof(WCHAR) &&\n        RtlCompareMemory(&fn.Buffer[(fn.Length - sizeof(datasuf) + sizeof(WCHAR))/sizeof(WCHAR)], datasuf, sizeof(datasuf) - sizeof(WCHAR)) == sizeof(datasuf) - sizeof(WCHAR))\n        fn.Length -= sizeof(datasuf) - sizeof(WCHAR);\n\n    if (fn.Length == 0) {\n        WARN(\"not allowing overwriting file with itself\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    Status = check_file_name_valid(&fn, false, true);\n    if (!NT_SUCCESS(Status)) {\n        WARN(\"invalid stream name %.*S\\n\", (int)(fn.Length / sizeof(WCHAR)), fn.Buffer);\n        return Status;\n    }\n\n    if (!(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE) && fileref->fcb->atts & FILE_ATTRIBUTE_READONLY) {\n        WARN(\"trying to rename stream on readonly file\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    Status = open_fileref_child(Vcb, fileref, &fn, fileref->fcb->case_sensitive, true, true, PagedPool, &sf, Irp);\n    if (Status != STATUS_OBJECT_NAME_NOT_FOUND) {\n        if (Status == STATUS_SUCCESS) {\n            if (fileref == sf || sf->deleted) {\n                free_fileref(sf);\n                sf = NULL;\n            } else {\n                if (!(flags & FILE_RENAME_REPLACE_IF_EXISTS)) {\n                    Status = STATUS_OBJECT_NAME_COLLISION;\n                    goto end;\n                }\n\n                // FIXME - POSIX overwrites of stream?\n\n                if (sf->open_count > 0) {\n                    WARN(\"trying to overwrite open file\\n\");\n                    Status = STATUS_ACCESS_DENIED;\n                    goto end;\n                }\n\n                if (sf->fcb->adsdata.Length > 0) {\n                    WARN(\"can only overwrite existing stream if it is zero-length\\n\");\n                    Status = STATUS_INVALID_PARAMETER;\n                    goto end;\n                }\n\n                Status = delete_fileref(sf, NULL, false, Irp, rollback);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_fileref returned %08lx\\n\", Status);\n                    goto end;\n                }\n            }\n        } else {\n            ERR(\"open_fileref_child returned %08lx\\n\", Status);\n            goto end;\n        }\n    }\n\n    Status = utf16_to_utf8(NULL, 0, &utf8len, fn.Buffer, fn.Length);\n    if (!NT_SUCCESS(Status))\n        goto end;\n\n    utf8.MaximumLength = utf8.Length = (uint16_t)utf8len;\n    utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG);\n    if (!utf8.Buffer) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    Status = utf16_to_utf8(utf8.Buffer, utf8len, &utf8len, fn.Buffer, fn.Length);\n    if (!NT_SUCCESS(Status)) {\n        ExFreePool(utf8.Buffer);\n        goto end;\n    }\n\n    adsxattr.Length = adsxattr.MaximumLength = sizeof(xapref) - 1 + utf8.Length;\n    adsxattr.Buffer = ExAllocatePoolWithTag(PagedPool, adsxattr.MaximumLength, ALLOC_TAG);\n    if (!adsxattr.Buffer) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        ExFreePool(utf8.Buffer);\n        goto end;\n    }\n\n    RtlCopyMemory(adsxattr.Buffer, xapref, sizeof(xapref) - 1);\n    RtlCopyMemory(&adsxattr.Buffer[sizeof(xapref) - 1], utf8.Buffer, utf8.Length);\n\n    // don't allow if it's one of our reserved names\n\n    if ((adsxattr.Length == sizeof(EA_DOSATTRIB) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_DOSATTRIB, adsxattr.Length) == adsxattr.Length) ||\n        (adsxattr.Length == sizeof(EA_EA) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_EA, adsxattr.Length) == adsxattr.Length) ||\n        (adsxattr.Length == sizeof(EA_REPARSE) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_REPARSE, adsxattr.Length) == adsxattr.Length) ||\n        (adsxattr.Length == sizeof(EA_CASE_SENSITIVE) - sizeof(WCHAR) && RtlCompareMemory(adsxattr.Buffer, EA_CASE_SENSITIVE, adsxattr.Length) == adsxattr.Length)) {\n        Status = STATUS_OBJECT_NAME_INVALID;\n        ExFreePool(utf8.Buffer);\n        ExFreePool(adsxattr.Buffer);\n        goto end;\n    }\n\n    utf16.Length = utf16.MaximumLength = fn.Length;\n    utf16.Buffer = ExAllocatePoolWithTag(PagedPool, utf16.MaximumLength, ALLOC_TAG);\n    if (!utf16.Buffer) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        ExFreePool(utf8.Buffer);\n        ExFreePool(adsxattr.Buffer);\n        goto end;\n    }\n\n    RtlCopyMemory(utf16.Buffer, fn.Buffer, fn.Length);\n\n    newmaxlen = Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) -\n                offsetof(DIR_ITEM, name[0]);\n\n    if (newmaxlen < adsxattr.Length) {\n        WARN(\"cannot rename as data too long\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        ExFreePool(utf8.Buffer);\n        ExFreePool(utf16.Buffer);\n        ExFreePool(adsxattr.Buffer);\n        goto end;\n    }\n\n    newmaxlen -= adsxattr.Length;\n\n    if (newmaxlen < fileref->fcb->inode_item.st_size) {\n        WARN(\"cannot rename as data too long\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        ExFreePool(utf8.Buffer);\n        ExFreePool(utf16.Buffer);\n        ExFreePool(adsxattr.Buffer);\n        goto end;\n    }\n\n    Status = RtlUpcaseUnicodeString(&utf16uc, &fn, true);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n        ExFreePool(utf8.Buffer);\n        ExFreePool(utf16.Buffer);\n        ExFreePool(adsxattr.Buffer);\n        goto end;\n    }\n\n    // read existing file data\n\n    if (fileref->fcb->inode_item.st_size > 0) {\n        ULONG bytes_read;\n\n        adsdata.Length = adsdata.MaximumLength = (uint16_t)fileref->fcb->inode_item.st_size;\n\n        adsdata.Buffer = ExAllocatePoolWithTag(PagedPool, adsdata.MaximumLength, ALLOC_TAG);\n        if (!adsdata.Buffer) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            ExFreePool(utf8.Buffer);\n            ExFreePool(utf16.Buffer);\n            ExFreePool(utf16uc.Buffer);\n            ExFreePool(adsxattr.Buffer);\n            goto end;\n        }\n\n        Status = read_file(fileref->fcb, (uint8_t*)adsdata.Buffer, 0, adsdata.Length, &bytes_read, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            ExFreePool(utf8.Buffer);\n            ExFreePool(utf16.Buffer);\n            ExFreePool(utf16uc.Buffer);\n            ExFreePool(adsxattr.Buffer);\n            ExFreePool(adsdata.Buffer);\n            goto end;\n        }\n\n        if (bytes_read < fileref->fcb->inode_item.st_size) {\n            ERR(\"short read\\n\");\n            Status = STATUS_INTERNAL_ERROR;\n            ExFreePool(utf8.Buffer);\n            ExFreePool(utf16.Buffer);\n            ExFreePool(utf16uc.Buffer);\n            ExFreePool(adsxattr.Buffer);\n            ExFreePool(adsdata.Buffer);\n            goto end;\n        }\n    } else\n        adsdata.Buffer = NULL;\n\n    dc = ExAllocatePoolWithTag(PagedPool, sizeof(dir_child), ALLOC_TAG);\n    if (!dc) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;;\n        ExFreePool(utf8.Buffer);\n        ExFreePool(utf16.Buffer);\n        ExFreePool(utf16uc.Buffer);\n        ExFreePool(adsxattr.Buffer);\n\n        if (adsdata.Buffer)\n            ExFreePool(adsdata.Buffer);\n\n        goto end;\n    }\n\n    // add dummy deleted fcb with old name\n\n    Status = duplicate_fcb(fileref->fcb, &dummyfcb);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"duplicate_fcb returned %08lx\\n\", Status);\n        ExFreePool(utf8.Buffer);\n        ExFreePool(utf16.Buffer);\n        ExFreePool(utf16uc.Buffer);\n        ExFreePool(adsxattr.Buffer);\n\n        if (adsdata.Buffer)\n            ExFreePool(adsdata.Buffer);\n\n        ExFreePool(dc);\n\n        goto end;\n    }\n\n    dummyfileref = create_fileref(Vcb);\n    if (!dummyfileref) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        ExFreePool(utf8.Buffer);\n        ExFreePool(utf16.Buffer);\n        ExFreePool(utf16uc.Buffer);\n        ExFreePool(adsxattr.Buffer);\n\n        if (adsdata.Buffer)\n            ExFreePool(adsdata.Buffer);\n\n        ExFreePool(dc);\n\n        reap_fcb(dummyfcb);\n\n        goto end;\n    }\n\n    dummyfileref->fcb = dummyfcb;\n\n    dummyfcb->Vcb = Vcb;\n    dummyfcb->subvol = fileref->fcb->subvol;\n    dummyfcb->inode = fileref->fcb->inode;\n    dummyfcb->hash = fileref->fcb->hash;\n\n    if (fileref->fcb->inode_item.st_size > 0) {\n        Status = excise_extents(Vcb, dummyfcb, 0, sector_align(fileref->fcb->inode_item.st_size, Vcb->superblock.sector_size),\n                                Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"excise_extents returned %08lx\\n\", Status);\n            ExFreePool(utf8.Buffer);\n            ExFreePool(utf16.Buffer);\n            ExFreePool(utf16uc.Buffer);\n            ExFreePool(adsxattr.Buffer);\n            ExFreePool(adsdata.Buffer);\n            ExFreePool(dc);\n\n            reap_fileref(Vcb, dummyfileref);\n            reap_fcb(dummyfcb);\n\n            goto end;\n        }\n\n        dummyfcb->inode_item.st_size = 0;\n        dummyfcb->Header.AllocationSize.QuadPart = 0;\n        dummyfcb->Header.FileSize.QuadPart = 0;\n        dummyfcb->Header.ValidDataLength.QuadPart = 0;\n    }\n\n    dummyfcb->hash_ptrs = fileref->fcb->hash_ptrs;\n    dummyfcb->hash_ptrs_uc = fileref->fcb->hash_ptrs_uc;\n    dummyfcb->created = fileref->fcb->created;\n\n    le = fileref->fcb->extents.Flink;\n    while (le != &fileref->fcb->extents) {\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        ext->ignore = true;\n\n        le = le->Flink;\n    }\n\n    while (!IsListEmpty(&fileref->fcb->dir_children_index)) {\n        InsertTailList(&dummyfcb->dir_children_index, RemoveHeadList(&fileref->fcb->dir_children_index));\n    }\n\n    while (!IsListEmpty(&fileref->fcb->dir_children_hash)) {\n        InsertTailList(&dummyfcb->dir_children_hash, RemoveHeadList(&fileref->fcb->dir_children_hash));\n    }\n\n    while (!IsListEmpty(&fileref->fcb->dir_children_hash_uc)) {\n        InsertTailList(&dummyfcb->dir_children_hash_uc, RemoveHeadList(&fileref->fcb->dir_children_hash_uc));\n    }\n\n    InsertTailList(&Vcb->all_fcbs, &dummyfcb->list_entry_all);\n\n    InsertHeadList(fileref->fcb->list_entry.Blink, &dummyfcb->list_entry);\n\n    if (fileref->fcb->subvol->fcbs_ptrs[dummyfcb->hash >> 24] == &fileref->fcb->list_entry)\n        fileref->fcb->subvol->fcbs_ptrs[dummyfcb->hash >> 24] = &dummyfcb->list_entry;\n\n    RemoveEntryList(&fileref->fcb->list_entry);\n    fileref->fcb->list_entry.Flink = fileref->fcb->list_entry.Blink = NULL;\n\n    mark_fcb_dirty(dummyfcb);\n\n    // create dummy fileref\n\n    dummyfileref->oldutf8 = fileref->oldutf8;\n    dummyfileref->oldindex = fileref->oldindex;\n    dummyfileref->delete_on_close = fileref->delete_on_close;\n    dummyfileref->posix_delete = fileref->posix_delete;\n    dummyfileref->deleted = fileref->deleted;\n    dummyfileref->created = fileref->created;\n    dummyfileref->parent = fileref->parent;\n\n    while (!IsListEmpty(&fileref->children)) {\n        file_ref* fr = CONTAINING_RECORD(RemoveHeadList(&fileref->children), file_ref, list_entry);\n\n        free_fileref(fr->parent);\n\n        fr->parent = dummyfileref;\n        InterlockedIncrement(&dummyfileref->refcount);\n\n        InsertTailList(&dummyfileref->children, &fr->list_entry);\n    }\n\n    InsertTailList(fileref->list_entry.Blink, &dummyfileref->list_entry);\n\n    RemoveEntryList(&fileref->list_entry);\n    InsertTailList(&dummyfileref->children, &fileref->list_entry);\n\n    dummyfileref->dc = fileref->dc;\n    dummyfileref->dc->fileref = dummyfileref;\n\n    mark_fileref_dirty(dummyfileref);\n\n    // change fcb values\n\n    fileref->fcb->hash_ptrs = NULL;\n    fileref->fcb->hash_ptrs_uc = NULL;\n\n    fileref->fcb->ads = true;\n\n    fileref->oldutf8.Length = fileref->oldutf8.MaximumLength = 0;\n    fileref->oldutf8.Buffer = NULL;\n\n    RtlZeroMemory(dc, sizeof(dir_child));\n\n    dc->utf8 = utf8;\n    dc->name = utf16;\n    dc->hash = calc_crc32c(0xffffffff, (uint8_t*)dc->name.Buffer, dc->name.Length);\n    dc->name_uc = utf16uc;\n    dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)dc->name_uc.Buffer, dc->name_uc.Length);\n    dc->fileref = fileref;\n    InsertTailList(&dummyfcb->dir_children_index, &dc->list_entry_index);\n\n    fileref->dc = dc;\n    fileref->parent = dummyfileref;\n\n    crc32 = calc_crc32c(0xfffffffe, (uint8_t*)adsxattr.Buffer, adsxattr.Length);\n\n    fileref->fcb->adsxattr = adsxattr;\n    fileref->fcb->adshash = crc32;\n    fileref->fcb->adsmaxlen = newmaxlen;\n    fileref->fcb->adsdata = adsdata;\n\n    fileref->fcb->created = true;\n\n    mark_fcb_dirty(fileref->fcb);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (sf)\n        free_fileref(sf);\n\n    return Status;\n}\n\nstatic NTSTATUS set_rename_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, PFILE_OBJECT tfo, bool ex) {\n    FILE_RENAME_INFORMATION_EX* fri = Irp->AssociatedIrp.SystemBuffer;\n    fcb* fcb = FileObject->FsContext;\n    ccb* ccb = FileObject->FsContext2;\n    file_ref *fileref = ccb ? ccb->fileref : NULL, *oldfileref = NULL, *related = NULL, *fr2 = NULL;\n    WCHAR* fn;\n    ULONG fnlen, utf8len, origutf8len;\n    UNICODE_STRING fnus;\n    ANSI_STRING utf8;\n    NTSTATUS Status;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n    LIST_ENTRY rollback, *le;\n    hardlink* hl;\n    SECURITY_SUBJECT_CONTEXT subjcont;\n    ACCESS_MASK access;\n    ULONG flags;\n\n    InitializeListHead(&rollback);\n\n    if (ex)\n        flags = fri->Flags;\n    else\n        flags = fri->ReplaceIfExists ? FILE_RENAME_REPLACE_IF_EXISTS : 0;\n\n    TRACE(\"tfo = %p\\n\", tfo);\n    TRACE(\"Flags = %lx\\n\", flags);\n    TRACE(\"RootDirectory = %p\\n\", fri->RootDirectory);\n    TRACE(\"FileName = %.*S\\n\", (int)(fri->FileNameLength / sizeof(WCHAR)), fri->FileName);\n\n    fn = fri->FileName;\n    fnlen = fri->FileNameLength / sizeof(WCHAR);\n\n    if (!tfo) {\n        if (!fileref || !fileref->parent) {\n            ERR(\"no fileref set and no directory given\\n\");\n            return STATUS_INVALID_PARAMETER;\n        }\n    } else {\n        LONG i;\n\n        while (fnlen > 0 && (fri->FileName[fnlen - 1] == '/' || fri->FileName[fnlen - 1] == '\\\\')) {\n            fnlen--;\n        }\n\n        if (fnlen == 0)\n            return STATUS_INVALID_PARAMETER;\n\n        for (i = fnlen - 1; i >= 0; i--) {\n            if (fri->FileName[i] == '\\\\' || fri->FileName[i] == '/') {\n                fn = &fri->FileName[i+1];\n                fnlen -= i + 1;\n                break;\n            }\n        }\n    }\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n    ExAcquireResourceExclusiveLite(&Vcb->fileref_lock, true);\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (fcb->inode == SUBVOL_ROOT_INODE && fcb->subvol->id == BTRFS_ROOT_FSTREE) {\n        WARN(\"not allowing \\\\$Root to be renamed\\n\");\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    if (fcb->ads) {\n        if (FileObject->SectionObjectPointer && FileObject->SectionObjectPointer->DataSectionObject) {\n            IO_STATUS_BLOCK iosb;\n\n            CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb);\n            if (!NT_SUCCESS(iosb.Status)) {\n                ERR(\"CcFlushCache returned %08lx\\n\", iosb.Status);\n                Status = iosb.Status;\n                goto end;\n            }\n        }\n\n        Status = rename_stream(Vcb, fileref, ccb, fri, flags, Irp, &rollback);\n        goto end;\n    } else if (fnlen >= 1 && fn[0] == ':') {\n        if (FileObject->SectionObjectPointer && FileObject->SectionObjectPointer->DataSectionObject) {\n            IO_STATUS_BLOCK iosb;\n\n            CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb);\n            if (!NT_SUCCESS(iosb.Status)) {\n                ERR(\"CcFlushCache returned %08lx\\n\", iosb.Status);\n                Status = iosb.Status;\n                goto end;\n            }\n        }\n\n        Status = rename_file_to_stream(Vcb, fileref, ccb, fri, flags, Irp, &rollback);\n        goto end;\n    }\n\n    fnus.Buffer = fn;\n    fnus.Length = fnus.MaximumLength = (uint16_t)(fnlen * sizeof(WCHAR));\n\n    TRACE(\"fnus = %.*S\\n\", (int)(fnus.Length / sizeof(WCHAR)), fnus.Buffer);\n\n    Status = check_file_name_valid(&fnus, false, false);\n    if (!NT_SUCCESS(Status))\n        goto end;\n\n    origutf8len = fileref->dc->utf8.Length;\n\n    Status = utf16_to_utf8(NULL, 0, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));\n    if (!NT_SUCCESS(Status))\n        goto end;\n\n    utf8.MaximumLength = utf8.Length = (uint16_t)utf8len;\n    utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG);\n    if (!utf8.Buffer) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    Status = utf16_to_utf8(utf8.Buffer, utf8len, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));\n    if (!NT_SUCCESS(Status))\n        goto end;\n\n    if (tfo && tfo->FsContext2) {\n        struct _ccb* relatedccb = tfo->FsContext2;\n\n        related = relatedccb->fileref;\n        increase_fileref_refcount(related);\n    } else if (fnus.Length >= sizeof(WCHAR) && fnus.Buffer[0] != '\\\\') {\n        related = fileref->parent;\n        increase_fileref_refcount(related);\n    }\n\n    Status = open_fileref(Vcb, &oldfileref, &fnus, related, false, NULL, NULL, PagedPool, ccb->case_sensitive,  Irp);\n\n    if (NT_SUCCESS(Status)) {\n        TRACE(\"destination file already exists\\n\");\n\n        if (fileref != oldfileref && !oldfileref->deleted) {\n            if (!(flags & FILE_RENAME_REPLACE_IF_EXISTS)) {\n                Status = STATUS_OBJECT_NAME_COLLISION;\n                goto end;\n            } else if (fileref == oldfileref) {\n                Status = STATUS_ACCESS_DENIED;\n                goto end;\n            } else if (!(flags & FILE_RENAME_POSIX_SEMANTICS) && (oldfileref->open_count > 0 || has_open_children(oldfileref)) && !oldfileref->deleted) {\n                WARN(\"trying to overwrite open file\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                goto end;\n            } else if (!(flags & FILE_RENAME_IGNORE_READONLY_ATTRIBUTE) && oldfileref->fcb->atts & FILE_ATTRIBUTE_READONLY) {\n                WARN(\"trying to overwrite readonly file\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                goto end;\n            } else if (oldfileref->fcb->type == BTRFS_TYPE_DIRECTORY) {\n                WARN(\"trying to overwrite directory\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                goto end;\n            }\n        }\n\n        if (fileref == oldfileref || oldfileref->deleted) {\n            free_fileref(oldfileref);\n            oldfileref = NULL;\n        }\n    }\n\n    if (!related) {\n        Status = open_fileref(Vcb, &related, &fnus, NULL, true, NULL, NULL, PagedPool, ccb->case_sensitive, Irp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"open_fileref returned %08lx\\n\", Status);\n            goto end;\n        }\n    }\n\n    if (related->fcb == Vcb->dummy_fcb) {\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    SeCaptureSubjectContext(&subjcont);\n\n    if (!SeAccessCheck(related->fcb->sd, &subjcont, false, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_ADD_SUBDIRECTORY : FILE_ADD_FILE, 0, NULL,\n        IoGetFileObjectGenericMapping(), Irp->RequestorMode, &access, &Status)) {\n        SeReleaseSubjectContext(&subjcont);\n        TRACE(\"SeAccessCheck failed, returning %08lx\\n\", Status);\n        goto end;\n    }\n\n    SeReleaseSubjectContext(&subjcont);\n\n    if (has_open_children(fileref)) {\n        WARN(\"trying to rename file with open children\\n\");\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    if (oldfileref) {\n        SeCaptureSubjectContext(&subjcont);\n\n        if (!SeAccessCheck(oldfileref->fcb->sd, &subjcont, false, DELETE, 0, NULL,\n                           IoGetFileObjectGenericMapping(), Irp->RequestorMode, &access, &Status)) {\n            SeReleaseSubjectContext(&subjcont);\n            TRACE(\"SeAccessCheck failed, returning %08lx\\n\", Status);\n            goto end;\n        }\n\n        SeReleaseSubjectContext(&subjcont);\n\n        if (oldfileref->open_count > 0 && flags & FILE_RENAME_POSIX_SEMANTICS) {\n            oldfileref->delete_on_close = true;\n            oldfileref->posix_delete = true;\n        }\n\n        Status = delete_fileref(oldfileref, NULL, oldfileref->open_count > 0 && flags & FILE_RENAME_POSIX_SEMANTICS, Irp, &rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_fileref returned %08lx\\n\", Status);\n            goto end;\n        }\n    }\n\n    if (fileref->parent->fcb->subvol != related->fcb->subvol && (fileref->fcb->subvol == fileref->parent->fcb->subvol || fileref->fcb == Vcb->dummy_fcb)) {\n        Status = move_across_subvols(fileref, ccb, related, &utf8, &fnus, Irp, &rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"move_across_subvols returned %08lx\\n\", Status);\n        }\n        goto end;\n    }\n\n    if (related == fileref->parent) { // keeping file in same directory\n        UNICODE_STRING oldfn, newfn;\n        USHORT name_offset;\n        ULONG reqlen, oldutf8len;\n\n        oldfn.Length = oldfn.MaximumLength = 0;\n\n        Status = fileref_get_filename(fileref, &oldfn, &name_offset, &reqlen);\n        if (Status != STATUS_BUFFER_OVERFLOW) {\n            ERR(\"fileref_get_filename returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        oldfn.Buffer = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG);\n        if (!oldfn.Buffer) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        oldfn.MaximumLength = (uint16_t)reqlen;\n\n        Status = fileref_get_filename(fileref, &oldfn, &name_offset, &reqlen);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"fileref_get_filename returned %08lx\\n\", Status);\n            ExFreePool(oldfn.Buffer);\n            goto end;\n        }\n\n        oldutf8len = fileref->dc->utf8.Length;\n\n        if (!fileref->created && !fileref->oldutf8.Buffer) {\n            fileref->oldutf8.Buffer = ExAllocatePoolWithTag(PagedPool, fileref->dc->utf8.Length, ALLOC_TAG);\n            if (!fileref->oldutf8.Buffer) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            fileref->oldutf8.Length = fileref->oldutf8.MaximumLength = fileref->dc->utf8.Length;\n            RtlCopyMemory(fileref->oldutf8.Buffer, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);\n        }\n\n        TRACE(\"renaming %.*S to %.*S\\n\", (int)(fileref->dc->name.Length / sizeof(WCHAR)), fileref->dc->name.Buffer, (int)(fnus.Length / sizeof(WCHAR)), fnus.Buffer);\n\n        mark_fileref_dirty(fileref);\n\n        if (fileref->dc) {\n            ExAcquireResourceExclusiveLite(&fileref->parent->fcb->nonpaged->dir_children_lock, true);\n\n            ExFreePool(fileref->dc->utf8.Buffer);\n            ExFreePool(fileref->dc->name.Buffer);\n            ExFreePool(fileref->dc->name_uc.Buffer);\n\n            fileref->dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.Length, ALLOC_TAG);\n            if (!fileref->dc->utf8.Buffer) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock);\n                ExFreePool(oldfn.Buffer);\n                goto end;\n            }\n\n            fileref->dc->utf8.Length = fileref->dc->utf8.MaximumLength = utf8.Length;\n            RtlCopyMemory(fileref->dc->utf8.Buffer, utf8.Buffer, utf8.Length);\n\n            fileref->dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, fnus.Length, ALLOC_TAG);\n            if (!fileref->dc->name.Buffer) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock);\n                ExFreePool(oldfn.Buffer);\n                goto end;\n            }\n\n            fileref->dc->name.Length = fileref->dc->name.MaximumLength = fnus.Length;\n            RtlCopyMemory(fileref->dc->name.Buffer, fnus.Buffer, fnus.Length);\n\n            Status = RtlUpcaseUnicodeString(&fileref->dc->name_uc, &fileref->dc->name, true);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n                ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock);\n                ExFreePool(oldfn.Buffer);\n                goto end;\n            }\n\n            remove_dir_child_from_hash_lists(fileref->parent->fcb, fileref->dc);\n\n            fileref->dc->hash = calc_crc32c(0xffffffff, (uint8_t*)fileref->dc->name.Buffer, fileref->dc->name.Length);\n            fileref->dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)fileref->dc->name_uc.Buffer, fileref->dc->name_uc.Length);\n\n            insert_dir_child_into_hash_lists(fileref->parent->fcb, fileref->dc);\n\n            ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock);\n        }\n\n        newfn.Length = newfn.MaximumLength = 0;\n\n        Status = fileref_get_filename(fileref, &newfn, &name_offset, &reqlen);\n        if (Status != STATUS_BUFFER_OVERFLOW) {\n            ERR(\"fileref_get_filename returned %08lx\\n\", Status);\n            ExFreePool(oldfn.Buffer);\n            goto end;\n        }\n\n        newfn.Buffer = ExAllocatePoolWithTag(PagedPool, reqlen, ALLOC_TAG);\n        if (!newfn.Buffer) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            ExFreePool(oldfn.Buffer);\n            goto end;\n        }\n\n        newfn.MaximumLength = (uint16_t)reqlen;\n\n        Status = fileref_get_filename(fileref, &newfn, &name_offset, &reqlen);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"fileref_get_filename returned %08lx\\n\", Status);\n            ExFreePool(oldfn.Buffer);\n            ExFreePool(newfn.Buffer);\n            goto end;\n        }\n\n        KeQuerySystemTime(&time);\n        win_time_to_unix(time, &now);\n\n        if (fcb != Vcb->dummy_fcb && (fileref->parent->fcb->subvol == fcb->subvol || !is_subvol_readonly(fcb->subvol, Irp))) {\n            fcb->inode_item.transid = Vcb->superblock.generation;\n            fcb->inode_item.sequence++;\n\n            if (!ccb->user_set_change_time)\n                fcb->inode_item.st_ctime = now;\n\n            fcb->inode_item_changed = true;\n            mark_fcb_dirty(fcb);\n        }\n\n        // update parent's INODE_ITEM\n\n        related->fcb->inode_item.transid = Vcb->superblock.generation;\n        TRACE(\"related->fcb->inode_item.st_size (inode %I64x) was %I64x\\n\", related->fcb->inode, related->fcb->inode_item.st_size);\n        related->fcb->inode_item.st_size = related->fcb->inode_item.st_size + (2 * utf8.Length) - (2* oldutf8len);\n        TRACE(\"related->fcb->inode_item.st_size (inode %I64x) now %I64x\\n\", related->fcb->inode, related->fcb->inode_item.st_size);\n        related->fcb->inode_item.sequence++;\n        related->fcb->inode_item.st_ctime = now;\n        related->fcb->inode_item.st_mtime = now;\n\n        related->fcb->inode_item_changed = true;\n        mark_fcb_dirty(related->fcb);\n        send_notification_fileref(related, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n\n        FsRtlNotifyFilterReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&oldfn, name_offset, NULL, NULL,\n                                      fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_RENAMED_OLD_NAME, NULL, NULL);\n        FsRtlNotifyFilterReportChange(fcb->Vcb->NotifySync, &fcb->Vcb->DirNotifyList, (PSTRING)&newfn, name_offset, NULL, NULL,\n                                      fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_RENAMED_NEW_NAME, NULL, NULL);\n\n        ExFreePool(oldfn.Buffer);\n        ExFreePool(newfn.Buffer);\n\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    // We move files by moving the existing fileref to the new directory, and\n    // replacing it with a dummy fileref with the same original values, but marked as deleted.\n\n    send_notification_fileref(fileref, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_REMOVED, NULL);\n\n    fr2 = create_fileref(Vcb);\n\n    fr2->fcb = fileref->fcb;\n    fr2->fcb->refcount++;\n\n    fr2->oldutf8 = fileref->oldutf8;\n    fr2->oldindex = fileref->dc->index;\n    fr2->delete_on_close = fileref->delete_on_close;\n    fr2->deleted = true;\n    fr2->created = fileref->created;\n    fr2->parent = fileref->parent;\n    fr2->dc = NULL;\n\n    if (!fr2->oldutf8.Buffer) {\n        fr2->oldutf8.Buffer = ExAllocatePoolWithTag(PagedPool, fileref->dc->utf8.Length, ALLOC_TAG);\n        if (!fr2->oldutf8.Buffer) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        RtlCopyMemory(fr2->oldutf8.Buffer, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);\n\n        fr2->oldutf8.Length = fr2->oldutf8.MaximumLength = fileref->dc->utf8.Length;\n    }\n\n    if (fr2->fcb->type == BTRFS_TYPE_DIRECTORY)\n        fr2->fcb->fileref = fr2;\n\n    if (fileref->fcb->inode == SUBVOL_ROOT_INODE)\n        fileref->fcb->subvol->parent = related->fcb->subvol->id;\n\n    fileref->oldutf8.Length = fileref->oldutf8.MaximumLength = 0;\n    fileref->oldutf8.Buffer = NULL;\n    fileref->deleted = false;\n    fileref->created = true;\n    fileref->parent = related;\n\n    ExAcquireResourceExclusiveLite(&fileref->parent->fcb->nonpaged->dir_children_lock, true);\n    InsertHeadList(&fileref->list_entry, &fr2->list_entry);\n    RemoveEntryList(&fileref->list_entry);\n    ExReleaseResourceLite(&fileref->parent->fcb->nonpaged->dir_children_lock);\n\n    mark_fileref_dirty(fr2);\n    mark_fileref_dirty(fileref);\n\n    if (fileref->dc) {\n        // remove from old parent\n        ExAcquireResourceExclusiveLite(&fr2->parent->fcb->nonpaged->dir_children_lock, true);\n        RemoveEntryList(&fileref->dc->list_entry_index);\n        remove_dir_child_from_hash_lists(fr2->parent->fcb, fileref->dc);\n        ExReleaseResourceLite(&fr2->parent->fcb->nonpaged->dir_children_lock);\n\n        if (fileref->dc->utf8.Length != utf8.Length || RtlCompareMemory(fileref->dc->utf8.Buffer, utf8.Buffer, utf8.Length) != utf8.Length) {\n            // handle changed name\n\n            ExFreePool(fileref->dc->utf8.Buffer);\n            ExFreePool(fileref->dc->name.Buffer);\n            ExFreePool(fileref->dc->name_uc.Buffer);\n\n            fileref->dc->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.Length, ALLOC_TAG);\n            if (!fileref->dc->utf8.Buffer) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            fileref->dc->utf8.Length = fileref->dc->utf8.MaximumLength = utf8.Length;\n            RtlCopyMemory(fileref->dc->utf8.Buffer, utf8.Buffer, utf8.Length);\n\n            fileref->dc->name.Buffer = ExAllocatePoolWithTag(PagedPool, fnus.Length, ALLOC_TAG);\n            if (!fileref->dc->name.Buffer) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            fileref->dc->name.Length = fileref->dc->name.MaximumLength = fnus.Length;\n            RtlCopyMemory(fileref->dc->name.Buffer, fnus.Buffer, fnus.Length);\n\n            Status = RtlUpcaseUnicodeString(&fileref->dc->name_uc, &fileref->dc->name, true);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"RtlUpcaseUnicodeString returned %08lx\\n\", Status);\n                goto end;\n            }\n\n            fileref->dc->hash = calc_crc32c(0xffffffff, (uint8_t*)fileref->dc->name.Buffer, fileref->dc->name.Length);\n            fileref->dc->hash_uc = calc_crc32c(0xffffffff, (uint8_t*)fileref->dc->name_uc.Buffer, fileref->dc->name_uc.Length);\n        }\n\n        // add to new parent\n        ExAcquireResourceExclusiveLite(&related->fcb->nonpaged->dir_children_lock, true);\n\n        if (IsListEmpty(&related->fcb->dir_children_index))\n            fileref->dc->index = 2;\n        else {\n            dir_child* dc2 = CONTAINING_RECORD(related->fcb->dir_children_index.Blink, dir_child, list_entry_index);\n\n            fileref->dc->index = max(2, dc2->index + 1);\n        }\n\n        InsertTailList(&related->fcb->dir_children_index, &fileref->dc->list_entry_index);\n        insert_dir_child_into_hash_lists(related->fcb, fileref->dc);\n        ExReleaseResourceLite(&related->fcb->nonpaged->dir_children_lock);\n    }\n\n    ExAcquireResourceExclusiveLite(&related->fcb->nonpaged->dir_children_lock, true);\n    InsertTailList(&related->children, &fileref->list_entry);\n    ExReleaseResourceLite(&related->fcb->nonpaged->dir_children_lock);\n\n    if (fcb->inode_item.st_nlink > 1) {\n        // add new hardlink entry to fcb\n\n        hl = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG);\n        if (!hl) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        hl->parent = related->fcb->inode;\n        hl->index = fileref->dc->index;\n\n        hl->name.Length = hl->name.MaximumLength = fnus.Length;\n        hl->name.Buffer = ExAllocatePoolWithTag(PagedPool, hl->name.MaximumLength, ALLOC_TAG);\n\n        if (!hl->name.Buffer) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(hl);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        RtlCopyMemory(hl->name.Buffer, fnus.Buffer, fnus.Length);\n\n        hl->utf8.Length = hl->utf8.MaximumLength = fileref->dc->utf8.Length;\n        hl->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl->utf8.MaximumLength, ALLOC_TAG);\n\n        if (!hl->utf8.Buffer) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(hl->name.Buffer);\n            ExFreePool(hl);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        RtlCopyMemory(hl->utf8.Buffer, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);\n\n        InsertTailList(&fcb->hardlinks, &hl->list_entry);\n    }\n\n    // delete old hardlink entry from fcb\n\n    le = fcb->hardlinks.Flink;\n    while (le != &fcb->hardlinks) {\n        hl = CONTAINING_RECORD(le, hardlink, list_entry);\n\n        if (hl->parent == fr2->parent->fcb->inode && hl->index == fr2->oldindex) {\n            RemoveEntryList(&hl->list_entry);\n\n            if (hl->utf8.Buffer)\n                ExFreePool(hl->utf8.Buffer);\n\n            if (hl->name.Buffer)\n                ExFreePool(hl->name.Buffer);\n\n            ExFreePool(hl);\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    // update inode's INODE_ITEM\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    if (fcb != Vcb->dummy_fcb && (fileref->parent->fcb->subvol == fcb->subvol || !is_subvol_readonly(fcb->subvol, Irp))) {\n        fcb->inode_item.transid = Vcb->superblock.generation;\n        fcb->inode_item.sequence++;\n\n        if (!ccb->user_set_change_time)\n            fcb->inode_item.st_ctime = now;\n\n        fcb->inode_item_changed = true;\n        mark_fcb_dirty(fcb);\n    }\n\n    // update new parent's INODE_ITEM\n\n    related->fcb->inode_item.transid = Vcb->superblock.generation;\n    TRACE(\"related->fcb->inode_item.st_size (inode %I64x) was %I64x\\n\", related->fcb->inode, related->fcb->inode_item.st_size);\n    related->fcb->inode_item.st_size += 2 * utf8len;\n    TRACE(\"related->fcb->inode_item.st_size (inode %I64x) now %I64x\\n\", related->fcb->inode, related->fcb->inode_item.st_size);\n    related->fcb->inode_item.sequence++;\n    related->fcb->inode_item.st_ctime = now;\n    related->fcb->inode_item.st_mtime = now;\n\n    related->fcb->inode_item_changed = true;\n    mark_fcb_dirty(related->fcb);\n\n    // update old parent's INODE_ITEM\n\n    fr2->parent->fcb->inode_item.transid = Vcb->superblock.generation;\n    TRACE(\"fr2->parent->fcb->inode_item.st_size (inode %I64x) was %I64x\\n\", fr2->parent->fcb->inode, fr2->parent->fcb->inode_item.st_size);\n    fr2->parent->fcb->inode_item.st_size -= 2 * origutf8len;\n    TRACE(\"fr2->parent->fcb->inode_item.st_size (inode %I64x) now %I64x\\n\", fr2->parent->fcb->inode, fr2->parent->fcb->inode_item.st_size);\n    fr2->parent->fcb->inode_item.sequence++;\n    fr2->parent->fcb->inode_item.st_ctime = now;\n    fr2->parent->fcb->inode_item.st_mtime = now;\n\n    free_fileref(fr2);\n\n    fr2->parent->fcb->inode_item_changed = true;\n    mark_fcb_dirty(fr2->parent->fcb);\n\n    send_notification_fileref(fileref, fcb->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL);\n    send_notification_fileref(related, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n    send_notification_fileref(fr2->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (oldfileref)\n        free_fileref(oldfileref);\n\n    if (!NT_SUCCESS(Status) && related)\n        free_fileref(related);\n\n    if (!NT_SUCCESS(Status) && fr2)\n        free_fileref(fr2);\n\n    if (NT_SUCCESS(Status))\n        clear_rollback(&rollback);\n    else\n        do_rollback(Vcb, &rollback);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n    ExReleaseResourceLite(&Vcb->fileref_lock);\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nNTSTATUS stream_set_end_of_file_information(device_extension* Vcb, uint16_t end, fcb* fcb, file_ref* fileref, bool advance_only) {\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n\n    TRACE(\"setting new end to %x bytes (currently %x)\\n\", end, fcb->adsdata.Length);\n\n    if (!fileref || !fileref->parent) {\n        ERR(\"no fileref for stream\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (end < fcb->adsdata.Length) {\n        if (advance_only)\n            return STATUS_SUCCESS;\n\n        TRACE(\"truncating stream to %x bytes\\n\", end);\n\n        fcb->adsdata.Length = end;\n    } else if (end > fcb->adsdata.Length) {\n        TRACE(\"extending stream to %x bytes\\n\", end);\n\n        if (end > fcb->adsmaxlen) {\n            ERR(\"error - xattr too long (%u > %lu)\\n\", end, fcb->adsmaxlen);\n            return STATUS_DISK_FULL;\n        }\n\n        if (end > fcb->adsdata.MaximumLength) {\n            char* data = ExAllocatePoolWithTag(PagedPool, end, ALLOC_TAG);\n            if (!data) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(data);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            if (fcb->adsdata.Buffer) {\n                RtlCopyMemory(data, fcb->adsdata.Buffer, fcb->adsdata.Length);\n                ExFreePool(fcb->adsdata.Buffer);\n            }\n\n            fcb->adsdata.Buffer = data;\n            fcb->adsdata.MaximumLength = end;\n        }\n\n        RtlZeroMemory(&fcb->adsdata.Buffer[fcb->adsdata.Length], end - fcb->adsdata.Length);\n\n        fcb->adsdata.Length = end;\n    }\n\n    mark_fcb_dirty(fcb);\n\n    fcb->Header.AllocationSize.QuadPart = end;\n    fcb->Header.FileSize.QuadPart = end;\n    fcb->Header.ValidDataLength.QuadPart = end;\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    fileref->parent->fcb->inode_item.transid = Vcb->superblock.generation;\n    fileref->parent->fcb->inode_item.sequence++;\n    fileref->parent->fcb->inode_item.st_ctime = now;\n\n    fileref->parent->fcb->inode_item_changed = true;\n    mark_fcb_dirty(fileref->parent->fcb);\n\n    fileref->parent->fcb->subvol->root_item.ctransid = Vcb->superblock.generation;\n    fileref->parent->fcb->subvol->root_item.ctime = now;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS set_end_of_file_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, bool advance_only) {\n    FILE_END_OF_FILE_INFORMATION* feofi = Irp->AssociatedIrp.SystemBuffer;\n    fcb* fcb = FileObject->FsContext;\n    ccb* ccb = FileObject->FsContext2;\n    file_ref* fileref = ccb ? ccb->fileref : NULL;\n    NTSTATUS Status;\n    LARGE_INTEGER time;\n    CC_FILE_SIZES ccfs;\n    LIST_ENTRY rollback;\n    bool set_size = false;\n    ULONG filter;\n    uint64_t new_end_of_file;\n\n    if (!fileref) {\n        ERR(\"fileref is NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    InitializeListHead(&rollback);\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (fileref ? fileref->deleted : fcb->deleted) {\n        Status = STATUS_FILE_CLOSED;\n        goto end;\n    }\n\n    if (fcb->ads) {\n        if (feofi->EndOfFile.QuadPart > 0xffff) {\n            Status = STATUS_DISK_FULL;\n            goto end;\n        }\n\n        if (feofi->EndOfFile.QuadPart < 0) {\n            Status = STATUS_INVALID_PARAMETER;\n            goto end;\n        }\n\n        Status = stream_set_end_of_file_information(Vcb, (uint16_t)feofi->EndOfFile.QuadPart, fcb, fileref, advance_only);\n\n        if (NT_SUCCESS(Status)) {\n            ccfs.AllocationSize = fcb->Header.AllocationSize;\n            ccfs.FileSize = fcb->Header.FileSize;\n            ccfs.ValidDataLength = fcb->Header.ValidDataLength;\n            set_size = true;\n        }\n\n        filter = FILE_NOTIFY_CHANGE_STREAM_SIZE;\n\n        if (!ccb->user_set_write_time) {\n            KeQuerySystemTime(&time);\n            win_time_to_unix(time, &fileref->parent->fcb->inode_item.st_mtime);\n            filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;\n\n            fileref->parent->fcb->inode_item_changed = true;\n            mark_fcb_dirty(fileref->parent->fcb);\n        }\n\n        queue_notification_fcb(fileref->parent, filter, FILE_ACTION_MODIFIED_STREAM, &fileref->dc->name);\n\n        goto end;\n    }\n\n    TRACE(\"file: %p\\n\", FileObject);\n    TRACE(\"paging IO: %s\\n\", Irp->Flags & IRP_PAGING_IO ? \"true\" : \"false\");\n    TRACE(\"FileObject: AllocationSize = %I64x, FileSize = %I64x, ValidDataLength = %I64x\\n\",\n        fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);\n\n    new_end_of_file = feofi->EndOfFile.QuadPart;\n\n    /* The lazy writer sometimes tries to round files to the next page size through CcSetValidData -\n     * ignore these. See fastfat!FatSetEndOfFileInfo, where Microsoft does the same as we're\n     * doing below. */\n    if (advance_only && new_end_of_file >= (uint64_t)fcb->Header.FileSize.QuadPart)\n        new_end_of_file = fcb->Header.FileSize.QuadPart;\n\n    TRACE(\"setting new end to %I64x bytes (currently %I64x)\\n\", new_end_of_file, fcb->inode_item.st_size);\n\n    if (new_end_of_file < fcb->inode_item.st_size) {\n        if (advance_only) {\n            Status = STATUS_SUCCESS;\n            goto end;\n        }\n\n        TRACE(\"truncating file to %I64x bytes\\n\", new_end_of_file);\n\n        if (!MmCanFileBeTruncated(&fcb->nonpaged->segment_object, &feofi->EndOfFile)) {\n            Status = STATUS_USER_MAPPED_FILE;\n            goto end;\n        }\n\n        Status = truncate_file(fcb, new_end_of_file, Irp, &rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - truncate_file failed\\n\");\n            goto end;\n        }\n    } else if (new_end_of_file > fcb->inode_item.st_size) {\n        TRACE(\"extending file to %I64x bytes\\n\", new_end_of_file);\n\n        Status = extend_file(fcb, fileref, new_end_of_file, false, NULL, &rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - extend_file failed\\n\");\n            goto end;\n        }\n    } else if (new_end_of_file == fcb->inode_item.st_size && advance_only) {\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    ccfs.AllocationSize = fcb->Header.AllocationSize;\n    ccfs.FileSize = fcb->Header.FileSize;\n    ccfs.ValidDataLength = fcb->Header.ValidDataLength;\n    set_size = true;\n\n    filter = FILE_NOTIFY_CHANGE_SIZE;\n\n    if (!ccb->user_set_write_time) {\n        KeQuerySystemTime(&time);\n        win_time_to_unix(time, &fcb->inode_item.st_mtime);\n        filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;\n    }\n\n    fcb->inode_item_changed = true;\n    mark_fcb_dirty(fcb);\n    queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (NT_SUCCESS(Status))\n        clear_rollback(&rollback);\n    else\n        do_rollback(Vcb, &rollback);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    if (set_size) {\n        try {\n            CcSetFileSizes(FileObject, &ccfs);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status))\n            ERR(\"CcSetFileSizes threw exception %08lx\\n\", Status);\n    }\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS set_allocation_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) {\n    FILE_ALLOCATION_INFORMATION* fai = Irp->AssociatedIrp.SystemBuffer;\n    fcb* fcb = FileObject->FsContext;\n    ccb* ccb = FileObject->FsContext2;\n    file_ref* fileref = ccb ? ccb->fileref : NULL;\n    NTSTATUS Status;\n    LARGE_INTEGER time;\n    CC_FILE_SIZES ccfs;\n    LIST_ENTRY rollback;\n    bool set_size = false;\n    ULONG filter;\n    uint64_t new_allocation_size, old_allocation_size;\n\n    if (!fileref) {\n        ERR(\"fileref is NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    InitializeListHead(&rollback);\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (fileref ? fileref->deleted : fcb->deleted) {\n        Status = STATUS_FILE_CLOSED;\n        goto end;\n    }\n\n    if (fcb->ads) {\n        if (fai->AllocationSize.QuadPart > 0xffff) {\n            Status = STATUS_DISK_FULL;\n            goto end;\n        }\n\n        if (fai->AllocationSize.QuadPart < 0) {\n            Status = STATUS_INVALID_PARAMETER;\n            goto end;\n        }\n\n        Status = stream_set_end_of_file_information(Vcb, (uint16_t)fai->AllocationSize.QuadPart, fcb, fileref, false);\n\n        if (NT_SUCCESS(Status)) {\n            ccfs.AllocationSize = fcb->Header.AllocationSize;\n            ccfs.FileSize = fcb->Header.FileSize;\n            ccfs.ValidDataLength = fcb->Header.ValidDataLength;\n            set_size = true;\n        }\n\n        filter = FILE_NOTIFY_CHANGE_STREAM_SIZE;\n\n        if (!ccb->user_set_write_time) {\n            KeQuerySystemTime(&time);\n            win_time_to_unix(time, &fileref->parent->fcb->inode_item.st_mtime);\n            filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;\n\n            fileref->parent->fcb->inode_item_changed = true;\n            mark_fcb_dirty(fileref->parent->fcb);\n        }\n\n        queue_notification_fcb(fileref->parent, filter, FILE_ACTION_MODIFIED_STREAM, &fileref->dc->name);\n\n        goto end;\n    }\n\n    TRACE(\"file: %p\\n\", FileObject);\n    TRACE(\"paging IO: %s\\n\", Irp->Flags & IRP_PAGING_IO ? \"true\" : \"false\");\n    TRACE(\"FileObject: AllocationSize = %I64x, FileSize = %I64x, ValidDataLength = %I64x\\n\",\n        fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);\n\n    new_allocation_size = sector_align(fai->AllocationSize.QuadPart, fcb->Vcb->superblock.sector_size);\n    old_allocation_size = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);\n\n    TRACE(\"setting new allocation size to %I64x bytes (currently %I64x)\\n\", new_allocation_size, old_allocation_size);\n\n    if (new_allocation_size < old_allocation_size) {\n        TRACE(\"truncating file to %I64x bytes\\n\", new_allocation_size);\n\n        if (!MmCanFileBeTruncated(&fcb->nonpaged->segment_object, &fai->AllocationSize)) {\n            Status = STATUS_USER_MAPPED_FILE;\n            goto end;\n        }\n\n        Status = truncate_file(fcb, new_allocation_size, Irp, &rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - truncate_file failed\\n\");\n            goto end;\n        }\n    } else if (new_allocation_size > old_allocation_size) {\n        TRACE(\"extending file to %I64x bytes\\n\", new_allocation_size);\n\n        Status = extend_file(fcb, fileref, new_allocation_size, true, NULL, &rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - extend_file failed\\n\");\n            goto end;\n        }\n    } else {\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    ccfs.AllocationSize = fcb->Header.AllocationSize;\n    ccfs.FileSize = fcb->Header.FileSize;\n    ccfs.ValidDataLength = fcb->Header.ValidDataLength;\n    set_size = true;\n\n    filter = FILE_NOTIFY_CHANGE_SIZE;\n\n    if (!ccb->user_set_write_time) {\n        KeQuerySystemTime(&time);\n        win_time_to_unix(time, &fcb->inode_item.st_mtime);\n        filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;\n    }\n\n    fcb->inode_item_changed = true;\n    mark_fcb_dirty(fcb);\n    queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (NT_SUCCESS(Status))\n        clear_rollback(&rollback);\n    else\n        do_rollback(Vcb, &rollback);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    if (set_size) {\n        try {\n            CcSetFileSizes(FileObject, &ccfs);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status))\n            ERR(\"CcSetFileSizes threw exception %08lx\\n\", Status);\n    }\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS set_position_information(PFILE_OBJECT FileObject, PIRP Irp) {\n    FILE_POSITION_INFORMATION* fpi = (FILE_POSITION_INFORMATION*)Irp->AssociatedIrp.SystemBuffer;\n\n    TRACE(\"setting the position on %p to %I64x\\n\", FileObject, fpi->CurrentByteOffset.QuadPart);\n\n    // FIXME - make sure aligned for FO_NO_INTERMEDIATE_BUFFERING\n\n    FileObject->CurrentByteOffset = fpi->CurrentByteOffset;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS set_link_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject, PFILE_OBJECT tfo, bool ex) {\n    FILE_LINK_INFORMATION_EX* fli = Irp->AssociatedIrp.SystemBuffer;\n    fcb *fcb = FileObject->FsContext, *tfofcb, *parfcb;\n    ccb* ccb = FileObject->FsContext2;\n    file_ref *fileref = ccb ? ccb->fileref : NULL, *oldfileref = NULL, *related = NULL, *fr2 = NULL;\n    WCHAR* fn;\n    ULONG fnlen, utf8len;\n    UNICODE_STRING fnus;\n    ANSI_STRING utf8;\n    NTSTATUS Status;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n    LIST_ENTRY rollback;\n    hardlink* hl;\n    ACCESS_MASK access;\n    SECURITY_SUBJECT_CONTEXT subjcont;\n    dir_child* dc = NULL;\n    ULONG flags;\n\n    InitializeListHead(&rollback);\n\n    // FIXME - check fli length\n    // FIXME - don't ignore fli->RootDirectory\n\n    if (ex)\n        flags = fli->Flags;\n    else\n        flags = fli->ReplaceIfExists ? FILE_LINK_REPLACE_IF_EXISTS : 0;\n\n    TRACE(\"flags = %lx\\n\", flags);\n    TRACE(\"RootDirectory = %p\\n\", fli->RootDirectory);\n    TRACE(\"FileNameLength = %lx\\n\", fli->FileNameLength);\n    TRACE(\"FileName = %.*S\\n\", (int)(fli->FileNameLength / sizeof(WCHAR)), fli->FileName);\n\n    fn = fli->FileName;\n    fnlen = fli->FileNameLength / sizeof(WCHAR);\n\n    if (!tfo) {\n        if (!fileref || !fileref->parent) {\n            ERR(\"no fileref set and no directory given\\n\");\n            return STATUS_INVALID_PARAMETER;\n        }\n\n        parfcb = fileref->parent->fcb;\n        tfofcb = NULL;\n    } else {\n        LONG i;\n\n        tfofcb = tfo->FsContext;\n        parfcb = tfofcb;\n\n        while (fnlen > 0 && (fli->FileName[fnlen - 1] == '/' || fli->FileName[fnlen - 1] == '\\\\')) {\n            fnlen--;\n        }\n\n        if (fnlen == 0)\n            return STATUS_INVALID_PARAMETER;\n\n        for (i = fnlen - 1; i >= 0; i--) {\n            if (fli->FileName[i] == '\\\\' || fli->FileName[i] == '/') {\n                fn = &fli->FileName[i+1];\n                fnlen = (fli->FileNameLength / sizeof(WCHAR)) - i - 1;\n                break;\n            }\n        }\n    }\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n    ExAcquireResourceExclusiveLite(&Vcb->fileref_lock, true);\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (fcb->type == BTRFS_TYPE_DIRECTORY) {\n        WARN(\"tried to create hard link on directory\\n\");\n        Status = STATUS_FILE_IS_A_DIRECTORY;\n        goto end;\n    }\n\n    if (fcb->ads) {\n        WARN(\"tried to create hard link on stream\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (fcb->inode_item.st_nlink >= 65535) {\n        Status = STATUS_TOO_MANY_LINKS;\n        goto end;\n    }\n\n    fnus.Buffer = fn;\n    fnus.Length = fnus.MaximumLength = (uint16_t)(fnlen * sizeof(WCHAR));\n\n    TRACE(\"fnus = %.*S\\n\", (int)(fnus.Length / sizeof(WCHAR)), fnus.Buffer);\n\n    Status = check_file_name_valid(&fnus, false, false);\n    if (!NT_SUCCESS(Status))\n        goto end;\n\n    Status = utf16_to_utf8(NULL, 0, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));\n    if (!NT_SUCCESS(Status))\n        goto end;\n\n    utf8.MaximumLength = utf8.Length = (uint16_t)utf8len;\n    utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG);\n    if (!utf8.Buffer) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    Status = utf16_to_utf8(utf8.Buffer, utf8len, &utf8len, fn, (ULONG)fnlen * sizeof(WCHAR));\n    if (!NT_SUCCESS(Status))\n        goto end;\n\n    if (tfo && tfo->FsContext2) {\n        struct _ccb* relatedccb = tfo->FsContext2;\n\n        related = relatedccb->fileref;\n        increase_fileref_refcount(related);\n    }\n\n    Status = open_fileref(Vcb, &oldfileref, &fnus, related, false, NULL, NULL, PagedPool, ccb->case_sensitive, Irp);\n\n    if (NT_SUCCESS(Status)) {\n        if (!oldfileref->deleted) {\n            WARN(\"destination file already exists\\n\");\n\n            if (!(flags & FILE_LINK_REPLACE_IF_EXISTS)) {\n                Status = STATUS_OBJECT_NAME_COLLISION;\n                goto end;\n            } else if (fileref == oldfileref) {\n                Status = STATUS_ACCESS_DENIED;\n                goto end;\n            } else if (!(flags & FILE_LINK_POSIX_SEMANTICS) && (oldfileref->open_count > 0 || has_open_children(oldfileref)) && !oldfileref->deleted) {\n                WARN(\"trying to overwrite open file\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                goto end;\n            } else if (!(flags & FILE_LINK_IGNORE_READONLY_ATTRIBUTE) && oldfileref->fcb->atts & FILE_ATTRIBUTE_READONLY) {\n                WARN(\"trying to overwrite readonly file\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                goto end;\n            } else if (oldfileref->fcb->type == BTRFS_TYPE_DIRECTORY) {\n                WARN(\"trying to overwrite directory\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                goto end;\n            }\n        } else {\n            free_fileref(oldfileref);\n            oldfileref = NULL;\n        }\n    }\n\n    if (!related) {\n        Status = open_fileref(Vcb, &related, &fnus, NULL, true, NULL, NULL, PagedPool, ccb->case_sensitive, Irp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"open_fileref returned %08lx\\n\", Status);\n            goto end;\n        }\n    }\n\n    SeCaptureSubjectContext(&subjcont);\n\n    if (!SeAccessCheck(related->fcb->sd, &subjcont, false, FILE_ADD_FILE, 0, NULL,\n                       IoGetFileObjectGenericMapping(), Irp->RequestorMode, &access, &Status)) {\n        SeReleaseSubjectContext(&subjcont);\n        TRACE(\"SeAccessCheck failed, returning %08lx\\n\", Status);\n        goto end;\n    }\n\n    SeReleaseSubjectContext(&subjcont);\n\n    if (fcb->subvol != parfcb->subvol) {\n        WARN(\"can't create hard link over subvolume boundary\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (oldfileref) {\n        SeCaptureSubjectContext(&subjcont);\n\n        if (!SeAccessCheck(oldfileref->fcb->sd, &subjcont, false, DELETE, 0, NULL,\n                           IoGetFileObjectGenericMapping(), Irp->RequestorMode, &access, &Status)) {\n            SeReleaseSubjectContext(&subjcont);\n            TRACE(\"SeAccessCheck failed, returning %08lx\\n\", Status);\n            goto end;\n        }\n\n        SeReleaseSubjectContext(&subjcont);\n\n        if (oldfileref->open_count > 0 && flags & FILE_RENAME_POSIX_SEMANTICS) {\n            oldfileref->delete_on_close = true;\n            oldfileref->posix_delete = true;\n        }\n\n        Status = delete_fileref(oldfileref, NULL, oldfileref->open_count > 0 && flags & FILE_RENAME_POSIX_SEMANTICS, Irp, &rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_fileref returned %08lx\\n\", Status);\n            goto end;\n        }\n    }\n\n    fr2 = create_fileref(Vcb);\n\n    fr2->fcb = fcb;\n    fcb->refcount++;\n\n    fr2->created = true;\n    fr2->parent = related;\n\n    Status = add_dir_child(related->fcb, fcb->inode, false, &utf8, &fnus, fcb->type, &dc);\n    if (!NT_SUCCESS(Status))\n        WARN(\"add_dir_child returned %08lx\\n\", Status);\n\n    fr2->dc = dc;\n    dc->fileref = fr2;\n\n    ExAcquireResourceExclusiveLite(&related->fcb->nonpaged->dir_children_lock, true);\n    InsertTailList(&related->children, &fr2->list_entry);\n    ExReleaseResourceLite(&related->fcb->nonpaged->dir_children_lock);\n\n    // add hardlink for existing fileref, if it's not there already\n    if (IsListEmpty(&fcb->hardlinks)) {\n        hl = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG);\n        if (!hl) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        hl->parent = fileref->parent->fcb->inode;\n        hl->index = fileref->dc->index;\n\n        hl->name.Length = hl->name.MaximumLength = fnus.Length;\n        hl->name.Buffer = ExAllocatePoolWithTag(PagedPool, fnus.Length, ALLOC_TAG);\n\n        if (!hl->name.Buffer) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(hl);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        RtlCopyMemory(hl->name.Buffer, fnus.Buffer, fnus.Length);\n\n        hl->utf8.Length = hl->utf8.MaximumLength = fileref->dc->utf8.Length;\n        hl->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl->utf8.MaximumLength, ALLOC_TAG);\n\n        if (!hl->utf8.Buffer) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(hl->name.Buffer);\n            ExFreePool(hl);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        RtlCopyMemory(hl->utf8.Buffer, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);\n\n        InsertTailList(&fcb->hardlinks, &hl->list_entry);\n    }\n\n    hl = ExAllocatePoolWithTag(PagedPool, sizeof(hardlink), ALLOC_TAG);\n    if (!hl) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    hl->parent = related->fcb->inode;\n    hl->index = dc->index;\n\n    hl->name.Length = hl->name.MaximumLength = fnus.Length;\n    hl->name.Buffer = ExAllocatePoolWithTag(PagedPool, hl->name.MaximumLength, ALLOC_TAG);\n\n    if (!hl->name.Buffer) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(hl);\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    RtlCopyMemory(hl->name.Buffer, fnus.Buffer, fnus.Length);\n\n    hl->utf8.Length = hl->utf8.MaximumLength = utf8.Length;\n    hl->utf8.Buffer = ExAllocatePoolWithTag(PagedPool, hl->utf8.MaximumLength, ALLOC_TAG);\n\n    if (!hl->utf8.Buffer) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(hl->name.Buffer);\n        ExFreePool(hl);\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    RtlCopyMemory(hl->utf8.Buffer, utf8.Buffer, utf8.Length);\n    ExFreePool(utf8.Buffer);\n\n    InsertTailList(&fcb->hardlinks, &hl->list_entry);\n\n    mark_fileref_dirty(fr2);\n    free_fileref(fr2);\n\n    // update inode's INODE_ITEM\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    fcb->inode_item.transid = Vcb->superblock.generation;\n    fcb->inode_item.sequence++;\n    fcb->inode_item.st_nlink++;\n\n    if (!ccb->user_set_change_time)\n        fcb->inode_item.st_ctime = now;\n\n    fcb->inode_item_changed = true;\n    mark_fcb_dirty(fcb);\n\n    // update parent's INODE_ITEM\n\n    parfcb->inode_item.transid = Vcb->superblock.generation;\n    TRACE(\"parfcb->inode_item.st_size (inode %I64x) was %I64x\\n\", parfcb->inode, parfcb->inode_item.st_size);\n    parfcb->inode_item.st_size += 2 * utf8len;\n    TRACE(\"parfcb->inode_item.st_size (inode %I64x) now %I64x\\n\", parfcb->inode, parfcb->inode_item.st_size);\n    parfcb->inode_item.sequence++;\n    parfcb->inode_item.st_ctime = now;\n\n    parfcb->inode_item_changed = true;\n    mark_fcb_dirty(parfcb);\n\n    send_notification_fileref(fr2, FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (oldfileref)\n        free_fileref(oldfileref);\n\n    if (!NT_SUCCESS(Status) && related)\n        free_fileref(related);\n\n    if (!NT_SUCCESS(Status) && fr2)\n        free_fileref(fr2);\n\n    if (NT_SUCCESS(Status))\n        clear_rollback(&rollback);\n    else\n        do_rollback(Vcb, &rollback);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n    ExReleaseResourceLite(&Vcb->fileref_lock);\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS set_valid_data_length_information(device_extension* Vcb, PIRP Irp, PFILE_OBJECT FileObject) {\n    FILE_VALID_DATA_LENGTH_INFORMATION* fvdli = Irp->AssociatedIrp.SystemBuffer;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    fcb* fcb = FileObject->FsContext;\n    ccb* ccb = FileObject->FsContext2;\n    file_ref* fileref = ccb ? ccb->fileref : NULL;\n    NTSTATUS Status;\n    LARGE_INTEGER time;\n    CC_FILE_SIZES ccfs;\n    LIST_ENTRY rollback;\n    bool set_size = false;\n    ULONG filter;\n\n    if (IrpSp->Parameters.SetFile.Length < sizeof(FILE_VALID_DATA_LENGTH_INFORMATION)) {\n        ERR(\"input buffer length was %lu, expected %Iu\\n\", IrpSp->Parameters.SetFile.Length, sizeof(FILE_VALID_DATA_LENGTH_INFORMATION));\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (!fileref) {\n        ERR(\"fileref is NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    InitializeListHead(&rollback);\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (fcb->atts & FILE_ATTRIBUTE_SPARSE_FILE) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (fvdli->ValidDataLength.QuadPart <= fcb->Header.ValidDataLength.QuadPart || fvdli->ValidDataLength.QuadPart > fcb->Header.FileSize.QuadPart) {\n        TRACE(\"invalid VDL of %I64u (current VDL = %I64u, file size = %I64u)\\n\", fvdli->ValidDataLength.QuadPart,\n              fcb->Header.ValidDataLength.QuadPart, fcb->Header.FileSize.QuadPart);\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (fileref ? fileref->deleted : fcb->deleted) {\n        Status = STATUS_FILE_CLOSED;\n        goto end;\n    }\n\n    // This function doesn't really do anything - the fsctl can only increase the value of ValidDataLength,\n    // and we set it to the max anyway.\n\n    ccfs.AllocationSize = fcb->Header.AllocationSize;\n    ccfs.FileSize = fcb->Header.FileSize;\n    ccfs.ValidDataLength = fvdli->ValidDataLength;\n    set_size = true;\n\n    filter = FILE_NOTIFY_CHANGE_SIZE;\n\n    if (!ccb->user_set_write_time) {\n        KeQuerySystemTime(&time);\n        win_time_to_unix(time, &fcb->inode_item.st_mtime);\n        filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;\n    }\n\n    fcb->inode_item_changed = true;\n    mark_fcb_dirty(fcb);\n\n    queue_notification_fcb(fileref, filter, FILE_ACTION_MODIFIED, NULL);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (NT_SUCCESS(Status))\n        clear_rollback(&rollback);\n    else\n        do_rollback(Vcb, &rollback);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    if (set_size) {\n        try {\n            CcSetFileSizes(FileObject, &ccfs);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status))\n            ERR(\"CcSetFileSizes threw exception %08lx\\n\", Status);\n        else\n            fcb->Header.AllocationSize = ccfs.AllocationSize;\n    }\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS set_case_sensitive_information(PIRP Irp) {\n    FILE_CASE_SENSITIVE_INFORMATION* fcsi = (FILE_CASE_SENSITIVE_INFORMATION*)Irp->AssociatedIrp.SystemBuffer;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n    if (IrpSp->Parameters.FileSystemControl.InputBufferLength < sizeof(FILE_CASE_SENSITIVE_INFORMATION))\n        return STATUS_INFO_LENGTH_MISMATCH;\n\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n\n    if (!FileObject)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb* fcb = FileObject->FsContext;\n\n    if (!fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!(fcb->atts & FILE_ATTRIBUTE_DIRECTORY)) {\n        WARN(\"cannot set case-sensitive flag on anything other than directory\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);\n\n    fcb->case_sensitive = fcsi->Flags & FILE_CS_FLAG_CASE_SENSITIVE_DIR;\n    mark_fcb_dirty(fcb);\n\n    ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n\n    return STATUS_SUCCESS;\n}\n\n_Dispatch_type_(IRP_MJ_SET_INFORMATION)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_set_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    NTSTATUS Status;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    fcb* fcb = IrpSp->FileObject->FsContext;\n    ccb* ccb = IrpSp->FileObject->FsContext2;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    top_level = is_top_level(Irp);\n\n    Irp->IoStatus.Information = 0;\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (!(Vcb->Vpb->Flags & VPB_MOUNTED)) {\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    if (Vcb->readonly && IrpSp->Parameters.SetFile.FileInformationClass != FilePositionInformation) {\n        Status = STATUS_MEDIA_WRITE_PROTECTED;\n        goto end;\n    }\n\n    if (!fcb) {\n        ERR(\"no fcb\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (!ccb) {\n        ERR(\"no ccb\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (fcb != Vcb->dummy_fcb && is_subvol_readonly(fcb->subvol, Irp) && IrpSp->Parameters.SetFile.FileInformationClass != FilePositionInformation &&\n        (fcb->inode != SUBVOL_ROOT_INODE || (IrpSp->Parameters.SetFile.FileInformationClass != FileBasicInformation && IrpSp->Parameters.SetFile.FileInformationClass != FileRenameInformation && IrpSp->Parameters.SetFile.FileInformationClass != FileRenameInformationEx))) {\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    Status = STATUS_NOT_IMPLEMENTED;\n\n    TRACE(\"set information\\n\");\n\n    FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL);\n\n    switch (IrpSp->Parameters.SetFile.FileInformationClass) {\n        case FileAllocationInformation:\n        {\n            TRACE(\"FileAllocationInformation\\n\");\n\n            if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_DATA)) {\n                WARN(\"insufficient privileges\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                break;\n            }\n\n            Status = set_allocation_information(Vcb, Irp, IrpSp->FileObject);\n            break;\n        }\n\n        case FileBasicInformation:\n        {\n            TRACE(\"FileBasicInformation\\n\");\n\n            if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) {\n                WARN(\"insufficient privileges\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                break;\n            }\n\n            Status = set_basic_information(Vcb, Irp, IrpSp->FileObject);\n\n            break;\n        }\n\n        case FileDispositionInformation:\n        {\n            TRACE(\"FileDispositionInformation\\n\");\n\n            if (Irp->RequestorMode == UserMode && !(ccb->access & DELETE)) {\n                WARN(\"insufficient privileges\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                break;\n            }\n\n            Status = set_disposition_information(Vcb, Irp, IrpSp->FileObject, false);\n\n            break;\n        }\n\n        case FileEndOfFileInformation:\n        {\n            TRACE(\"FileEndOfFileInformation\\n\");\n\n            if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) {\n                WARN(\"insufficient privileges\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                break;\n            }\n\n            Status = set_end_of_file_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.AdvanceOnly);\n\n            break;\n        }\n\n        case FileLinkInformation:\n            TRACE(\"FileLinkInformation\\n\");\n            Status = set_link_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, false);\n            break;\n\n        case FilePositionInformation:\n            TRACE(\"FilePositionInformation\\n\");\n            Status = set_position_information(IrpSp->FileObject, Irp);\n            break;\n\n        case FileRenameInformation:\n            TRACE(\"FileRenameInformation\\n\");\n            Status = set_rename_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, false);\n            break;\n\n        case FileValidDataLengthInformation:\n        {\n            TRACE(\"FileValidDataLengthInformation\\n\");\n\n            if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) {\n                WARN(\"insufficient privileges\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                break;\n            }\n\n            Status = set_valid_data_length_information(Vcb, Irp, IrpSp->FileObject);\n\n            break;\n        }\n\n#ifndef _MSC_VER\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wswitch\"\n#endif\n        case FileDispositionInformationEx:\n        {\n            TRACE(\"FileDispositionInformationEx\\n\");\n\n            if (Irp->RequestorMode == UserMode && !(ccb->access & DELETE)) {\n                WARN(\"insufficient privileges\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                break;\n            }\n\n            Status = set_disposition_information(Vcb, Irp, IrpSp->FileObject, true);\n\n            break;\n        }\n\n        case FileRenameInformationEx:\n            TRACE(\"FileRenameInformationEx\\n\");\n            Status = set_rename_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, true);\n            break;\n\n        case FileLinkInformationEx:\n            TRACE(\"FileLinkInformationEx\\n\");\n            Status = set_link_information(Vcb, Irp, IrpSp->FileObject, IrpSp->Parameters.SetFile.FileObject, true);\n            break;\n\n        case FileCaseSensitiveInformation:\n            TRACE(\"FileCaseSensitiveInformation\\n\");\n\n            if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) {\n                WARN(\"insufficient privileges\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                break;\n            }\n\n            Status = set_case_sensitive_information(Irp);\n            break;\n\n        case FileStorageReserveIdInformation:\n            WARN(\"unimplemented FileInformationClass FileStorageReserveIdInformation\\n\");\n            break;\n\n#ifndef _MSC_VER\n#pragma GCC diagnostic pop\n#endif\n\n        default:\n            WARN(\"unknown FileInformationClass %u\\n\", IrpSp->Parameters.SetFile.FileInformationClass);\n    }\n\nend:\n    Irp->IoStatus.Status = Status;\n\n    TRACE(\"returning %08lx\\n\", Status);\n\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\nstatic NTSTATUS fill_in_file_basic_information(FILE_BASIC_INFORMATION* fbi, INODE_ITEM* ii, LONG* length, fcb* fcb, file_ref* fileref) {\n    RtlZeroMemory(fbi, sizeof(FILE_BASIC_INFORMATION));\n\n    *length -= sizeof(FILE_BASIC_INFORMATION);\n\n    if (fcb == fcb->Vcb->dummy_fcb) {\n        LARGE_INTEGER time;\n\n        KeQuerySystemTime(&time);\n        fbi->CreationTime = fbi->LastAccessTime = fbi->LastWriteTime = fbi->ChangeTime = time;\n    } else {\n        fbi->CreationTime.QuadPart = unix_time_to_win(&ii->otime);\n        fbi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);\n        fbi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);\n        fbi->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime);\n    }\n\n    if (fcb->ads) {\n        if (!fileref || !fileref->parent) {\n            ERR(\"no fileref for stream\\n\");\n            return STATUS_INTERNAL_ERROR;\n        } else\n            fbi->FileAttributes = fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fileref->parent->fcb->atts;\n    } else\n        fbi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fill_in_file_network_open_information(FILE_NETWORK_OPEN_INFORMATION* fnoi, fcb* fcb, file_ref* fileref, LONG* length) {\n    INODE_ITEM* ii;\n\n    if (*length < (LONG)sizeof(FILE_NETWORK_OPEN_INFORMATION)) {\n        WARN(\"overflow\\n\");\n        return STATUS_BUFFER_OVERFLOW;\n    }\n\n    RtlZeroMemory(fnoi, sizeof(FILE_NETWORK_OPEN_INFORMATION));\n\n    *length -= sizeof(FILE_NETWORK_OPEN_INFORMATION);\n\n    if (fcb->ads) {\n        if (!fileref || !fileref->parent) {\n            ERR(\"no fileref for stream\\n\");\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        ii = &fileref->parent->fcb->inode_item;\n    } else\n        ii = &fcb->inode_item;\n\n    if (fcb == fcb->Vcb->dummy_fcb) {\n        LARGE_INTEGER time;\n\n        KeQuerySystemTime(&time);\n        fnoi->CreationTime = fnoi->LastAccessTime = fnoi->LastWriteTime = fnoi->ChangeTime = time;\n    } else {\n        fnoi->CreationTime.QuadPart = unix_time_to_win(&ii->otime);\n        fnoi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);\n        fnoi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);\n        fnoi->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime);\n    }\n\n    if (fcb->ads) {\n        fnoi->AllocationSize.QuadPart = fnoi->EndOfFile.QuadPart = fcb->adsdata.Length;\n        fnoi->FileAttributes = fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fileref->parent->fcb->atts;\n    } else {\n        fnoi->AllocationSize.QuadPart = fcb_alloc_size(fcb);\n        fnoi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;\n        fnoi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fill_in_file_standard_information(FILE_STANDARD_INFORMATION* fsi, fcb* fcb, file_ref* fileref, LONG* length) {\n    RtlZeroMemory(fsi, sizeof(FILE_STANDARD_INFORMATION));\n\n    *length -= sizeof(FILE_STANDARD_INFORMATION);\n\n    if (fcb->ads) {\n        if (!fileref || !fileref->parent) {\n            ERR(\"no fileref for stream\\n\");\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        fsi->AllocationSize.QuadPart = fsi->EndOfFile.QuadPart = fcb->adsdata.Length;\n        fsi->NumberOfLinks = fileref->parent->fcb->inode_item.st_nlink;\n        fsi->Directory = false;\n    } else {\n        fsi->AllocationSize.QuadPart = fcb_alloc_size(fcb);\n        fsi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;\n        fsi->NumberOfLinks = fcb->inode_item.st_nlink;\n        fsi->Directory = S_ISDIR(fcb->inode_item.st_mode);\n    }\n\n    TRACE(\"length = %I64u\\n\", fsi->EndOfFile.QuadPart);\n\n    fsi->DeletePending = fileref ? fileref->delete_on_close : false;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fill_in_file_internal_information(FILE_INTERNAL_INFORMATION* fii, fcb* fcb, LONG* length) {\n    *length -= sizeof(FILE_INTERNAL_INFORMATION);\n\n    fii->IndexNumber.QuadPart = make_file_id(fcb->subvol, fcb->inode);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fill_in_file_ea_information(FILE_EA_INFORMATION* eai, fcb* fcb, LONG* length) {\n    *length -= sizeof(FILE_EA_INFORMATION);\n\n    /* This value appears to be the size of the structure NTFS stores on disk, and not,\n     * as might be expected, the size of FILE_FULL_EA_INFORMATION (which is what we store).\n     * The formula is 4 bytes as a header, followed by 5 + NameLength + ValueLength for each\n     * item. */\n\n    eai->EaSize = fcb->ealen;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fill_in_file_position_information(FILE_POSITION_INFORMATION* fpi, PFILE_OBJECT FileObject, LONG* length) {\n    RtlZeroMemory(fpi, sizeof(FILE_POSITION_INFORMATION));\n\n    *length -= sizeof(FILE_POSITION_INFORMATION);\n\n    fpi->CurrentByteOffset = FileObject->CurrentByteOffset;\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS fileref_get_filename(file_ref* fileref, PUNICODE_STRING fn, USHORT* name_offset, ULONG* preqlen) {\n    file_ref* fr;\n    NTSTATUS Status;\n    ULONG reqlen = 0;\n    USHORT offset;\n    bool overflow = false;\n\n    // FIXME - we need a lock on filerefs' filepart\n\n    if (fileref == fileref->fcb->Vcb->root_fileref) {\n        if (fn->MaximumLength >= sizeof(WCHAR)) {\n            fn->Buffer[0] = '\\\\';\n            fn->Length = sizeof(WCHAR);\n\n            if (name_offset)\n                *name_offset = 0;\n\n            return STATUS_SUCCESS;\n        } else {\n            if (preqlen)\n                *preqlen = sizeof(WCHAR);\n            fn->Length = 0;\n\n            return STATUS_BUFFER_OVERFLOW;\n        }\n    }\n\n    fr = fileref;\n    offset = 0;\n\n    while (fr->parent) {\n        USHORT movelen;\n\n        if (!fr->dc)\n            return STATUS_INTERNAL_ERROR;\n\n        if (!overflow) {\n            if (fr->dc->name.Length + sizeof(WCHAR) + fn->Length > fn->MaximumLength)\n                overflow = true;\n        }\n\n        if (overflow)\n            movelen = fn->MaximumLength - fr->dc->name.Length - sizeof(WCHAR);\n        else\n            movelen = fn->Length;\n\n        if ((!overflow || fn->MaximumLength > fr->dc->name.Length + sizeof(WCHAR)) && movelen > 0) {\n            RtlMoveMemory(&fn->Buffer[(fr->dc->name.Length / sizeof(WCHAR)) + 1], fn->Buffer, movelen);\n            offset += fr->dc->name.Length + sizeof(WCHAR);\n        }\n\n        if (fn->MaximumLength >= sizeof(WCHAR)) {\n            fn->Buffer[0] = fr->fcb->ads ? ':' : '\\\\';\n            fn->Length += sizeof(WCHAR);\n\n            if (fn->MaximumLength > sizeof(WCHAR)) {\n                RtlCopyMemory(&fn->Buffer[1], fr->dc->name.Buffer, min(fr->dc->name.Length, fn->MaximumLength - sizeof(WCHAR)));\n                fn->Length += fr->dc->name.Length;\n            }\n\n            if (fn->Length > fn->MaximumLength) {\n                fn->Length = fn->MaximumLength;\n                overflow = true;\n            }\n        }\n\n        reqlen += sizeof(WCHAR) + fr->dc->name.Length;\n\n        fr = fr->parent;\n    }\n\n    offset += sizeof(WCHAR);\n\n    if (overflow) {\n        if (preqlen)\n            *preqlen = reqlen;\n        Status = STATUS_BUFFER_OVERFLOW;\n    } else {\n        if (name_offset)\n            *name_offset = offset;\n\n        Status = STATUS_SUCCESS;\n    }\n\n    return Status;\n}\n\nstatic NTSTATUS fill_in_file_name_information(FILE_NAME_INFORMATION* fni, fcb* fcb, file_ref* fileref, LONG* length) {\n    ULONG reqlen;\n    UNICODE_STRING fn;\n    NTSTATUS Status;\n    static const WCHAR datasuf[] = {':','$','D','A','T','A',0};\n    uint16_t datasuflen = sizeof(datasuf) - sizeof(WCHAR);\n\n    if (!fileref) {\n        ERR(\"called without fileref\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    *length -= (LONG)offsetof(FILE_NAME_INFORMATION, FileName[0]);\n\n    TRACE(\"maximum length is %li\\n\", *length);\n    fni->FileNameLength = 0;\n\n    fni->FileName[0] = 0;\n\n    fn.Buffer = fni->FileName;\n    fn.Length = 0;\n    fn.MaximumLength = (uint16_t)*length;\n\n    Status = fileref_get_filename(fileref, &fn, NULL, &reqlen);\n    if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {\n        ERR(\"fileref_get_filename returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (fcb->ads) {\n        if (Status == STATUS_BUFFER_OVERFLOW)\n            reqlen += datasuflen;\n        else {\n            if (fn.Length + datasuflen > fn.MaximumLength) {\n                RtlCopyMemory(&fn.Buffer[fn.Length / sizeof(WCHAR)], datasuf, fn.MaximumLength - fn.Length);\n                reqlen += datasuflen;\n                Status = STATUS_BUFFER_OVERFLOW;\n            } else {\n                RtlCopyMemory(&fn.Buffer[fn.Length / sizeof(WCHAR)], datasuf, datasuflen);\n                fn.Length += datasuflen;\n            }\n        }\n    }\n\n    if (Status == STATUS_BUFFER_OVERFLOW) {\n        *length = -1;\n        fni->FileNameLength = reqlen;\n        TRACE(\"%.*S (truncated)\\n\", (int)(fn.Length / sizeof(WCHAR)), fn.Buffer);\n    } else {\n        *length -= fn.Length;\n        fni->FileNameLength = fn.Length;\n        TRACE(\"%.*S\\n\", (int)(fn.Length / sizeof(WCHAR)), fn.Buffer);\n    }\n\n    return Status;\n}\n\nstatic NTSTATUS fill_in_file_attribute_information(FILE_ATTRIBUTE_TAG_INFORMATION* ati, fcb* fcb, ccb* ccb, LONG* length) {\n    *length -= sizeof(FILE_ATTRIBUTE_TAG_INFORMATION);\n\n    if (fcb->ads) {\n        if (!ccb->fileref || !ccb->fileref->parent) {\n            ERR(\"no fileref for stream\\n\");\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        ati->FileAttributes = ccb->fileref->parent->fcb->atts;\n    } else\n        ati->FileAttributes = fcb->atts;\n\n    if (!(ati->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))\n        ati->ReparseTag = 0;\n    else\n        ati->ReparseTag = get_reparse_tag_fcb(fcb);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fill_in_file_stream_information(FILE_STREAM_INFORMATION* fsi, file_ref* fileref, LONG* length) {\n    LONG reqsize;\n    LIST_ENTRY* le;\n    FILE_STREAM_INFORMATION *entry, *lastentry;\n    NTSTATUS Status;\n\n    static const WCHAR datasuf[] = L\":$DATA\";\n    UNICODE_STRING suf;\n\n    if (!fileref) {\n        ERR(\"fileref was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    suf.Buffer = (WCHAR*)datasuf;\n    suf.Length = suf.MaximumLength = sizeof(datasuf) - sizeof(WCHAR);\n\n    if (fileref->fcb->type != BTRFS_TYPE_DIRECTORY)\n        reqsize = sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR);\n    else\n        reqsize = 0;\n\n    ExAcquireResourceSharedLite(&fileref->fcb->nonpaged->dir_children_lock, true);\n\n    le = fileref->fcb->dir_children_index.Flink;\n    while (le != &fileref->fcb->dir_children_index) {\n        dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index);\n\n        if (dc->index == 0) {\n            reqsize = (ULONG)sector_align(reqsize, sizeof(LONGLONG));\n            reqsize += sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR) + dc->name.Length;\n        } else\n            break;\n\n        le = le->Flink;\n    }\n\n    TRACE(\"length = %li, reqsize = %lu\\n\", *length, reqsize);\n\n    if (reqsize > *length) {\n        Status = STATUS_BUFFER_OVERFLOW;\n        goto end;\n    }\n\n    entry = fsi;\n    lastentry = NULL;\n\n    if (fileref->fcb->type != BTRFS_TYPE_DIRECTORY) {\n        ULONG off;\n\n        entry->NextEntryOffset = 0;\n        entry->StreamNameLength = suf.Length + sizeof(WCHAR);\n        entry->StreamSize.QuadPart = fileref->fcb->inode_item.st_size;\n        entry->StreamAllocationSize.QuadPart = fcb_alloc_size(fileref->fcb);\n\n        entry->StreamName[0] = ':';\n        RtlCopyMemory(&entry->StreamName[1], suf.Buffer, suf.Length);\n\n        off = (ULONG)sector_align(sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR), sizeof(LONGLONG));\n\n        lastentry = entry;\n        entry = (FILE_STREAM_INFORMATION*)((uint8_t*)entry + off);\n    }\n\n    le = fileref->fcb->dir_children_index.Flink;\n    while (le != &fileref->fcb->dir_children_index) {\n        dir_child* dc = CONTAINING_RECORD(le, dir_child, list_entry_index);\n\n        if (dc->index == 0) {\n            ULONG off;\n\n            entry->NextEntryOffset = 0;\n            entry->StreamNameLength = dc->name.Length + suf.Length + sizeof(WCHAR);\n\n            if (dc->fileref)\n                entry->StreamSize.QuadPart = dc->fileref->fcb->adsdata.Length;\n            else\n                entry->StreamSize.QuadPart = dc->size;\n\n            entry->StreamAllocationSize.QuadPart = entry->StreamSize.QuadPart;\n\n            entry->StreamName[0] = ':';\n\n            RtlCopyMemory(&entry->StreamName[1], dc->name.Buffer, dc->name.Length);\n            RtlCopyMemory(&entry->StreamName[1 + (dc->name.Length / sizeof(WCHAR))], suf.Buffer, suf.Length);\n\n            if (lastentry)\n                lastentry->NextEntryOffset = (uint32_t)((uint8_t*)entry - (uint8_t*)lastentry);\n\n            off = (ULONG)sector_align(sizeof(FILE_STREAM_INFORMATION) - sizeof(WCHAR) + suf.Length + sizeof(WCHAR) + dc->name.Length, sizeof(LONGLONG));\n\n            lastentry = entry;\n            entry = (FILE_STREAM_INFORMATION*)((uint8_t*)entry + off);\n        } else\n            break;\n\n        le = le->Flink;\n    }\n\n    *length -= reqsize;\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&fileref->fcb->nonpaged->dir_children_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS fill_in_file_standard_link_information(FILE_STANDARD_LINK_INFORMATION* fsli, fcb* fcb, file_ref* fileref, LONG* length) {\n    TRACE(\"FileStandardLinkInformation\\n\");\n\n    // FIXME - NumberOfAccessibleLinks should subtract open links which have been marked as delete_on_close\n\n    fsli->NumberOfAccessibleLinks = fcb->inode_item.st_nlink;\n    fsli->TotalNumberOfLinks = fcb->inode_item.st_nlink;\n    fsli->DeletePending = fileref ? fileref->delete_on_close : false;\n    fsli->Directory = (!fcb->ads && fcb->type == BTRFS_TYPE_DIRECTORY) ? true : false;\n\n    *length -= sizeof(FILE_STANDARD_LINK_INFORMATION);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fill_in_hard_link_information(FILE_LINKS_INFORMATION* fli, file_ref* fileref, PIRP Irp, LONG* length) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    LONG bytes_needed;\n    FILE_LINK_ENTRY_INFORMATION* feli;\n    bool overflow = false;\n    fcb* fcb = fileref->fcb;\n    ULONG len;\n\n    if (fcb->ads)\n        return STATUS_INVALID_PARAMETER;\n\n    if (*length < (LONG)offsetof(FILE_LINKS_INFORMATION, Entry))\n        return STATUS_INVALID_PARAMETER;\n\n    RtlZeroMemory(fli, *length);\n\n    bytes_needed = offsetof(FILE_LINKS_INFORMATION, Entry);\n    len = bytes_needed;\n    feli = NULL;\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    if (fcb->inode == SUBVOL_ROOT_INODE) {\n        ULONG namelen;\n\n        if (fcb == fcb->Vcb->root_fileref->fcb)\n            namelen = sizeof(WCHAR);\n        else\n            namelen = fileref->dc->name.Length;\n\n        bytes_needed += sizeof(FILE_LINK_ENTRY_INFORMATION) - sizeof(WCHAR) + namelen;\n\n        if (bytes_needed > *length)\n            overflow = true;\n\n        if (!overflow) {\n            feli = &fli->Entry;\n\n            feli->NextEntryOffset = 0;\n            feli->ParentFileId = 0; // we use an inode of 0 to mean the parent of a subvolume\n\n            if (fcb == fcb->Vcb->root_fileref->fcb) {\n                feli->FileNameLength = 1;\n                feli->FileName[0] = '.';\n            } else {\n                feli->FileNameLength = fileref->dc->name.Length / sizeof(WCHAR);\n                RtlCopyMemory(feli->FileName, fileref->dc->name.Buffer, fileref->dc->name.Length);\n            }\n\n            fli->EntriesReturned++;\n\n            len = bytes_needed;\n        }\n    } else {\n        ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true);\n\n        if (IsListEmpty(&fcb->hardlinks)) {\n            if (!fileref->dc) {\n                ExReleaseResourceLite(&fcb->Vcb->fileref_lock);\n                Status = STATUS_INVALID_PARAMETER;\n                goto end;\n            }\n\n            bytes_needed += sizeof(FILE_LINK_ENTRY_INFORMATION) + fileref->dc->name.Length - sizeof(WCHAR);\n\n            if (bytes_needed > *length)\n                overflow = true;\n\n            if (!overflow) {\n                feli = &fli->Entry;\n\n                feli->NextEntryOffset = 0;\n                feli->ParentFileId = fileref->parent->fcb->inode;\n                feli->FileNameLength = fileref->dc->name.Length / sizeof(WCHAR);\n                RtlCopyMemory(feli->FileName, fileref->dc->name.Buffer, fileref->dc->name.Length);\n\n                fli->EntriesReturned++;\n\n                len = bytes_needed;\n            }\n        } else {\n            le = fcb->hardlinks.Flink;\n            while (le != &fcb->hardlinks) {\n                hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry);\n                file_ref* parfr;\n\n                TRACE(\"parent %I64x, index %I64x, name %.*S\\n\", hl->parent, hl->index, (int)(hl->name.Length / sizeof(WCHAR)), hl->name.Buffer);\n\n                Status = open_fileref_by_inode(fcb->Vcb, fcb->subvol, hl->parent, &parfr, Irp);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"open_fileref_by_inode returned %08lx\\n\", Status);\n                } else if (!parfr->deleted) {\n                    LIST_ENTRY* le2;\n                    bool found = false, deleted = false;\n                    UNICODE_STRING* fn = NULL;\n\n                    le2 = parfr->children.Flink;\n                    while (le2 != &parfr->children) {\n                        file_ref* fr2 = CONTAINING_RECORD(le2, file_ref, list_entry);\n\n                        if (fr2->dc && fr2->dc->index == hl->index) {\n                            found = true;\n                            deleted = fr2->deleted;\n\n                            if (!deleted)\n                                fn = &fr2->dc->name;\n\n                            break;\n                        }\n\n                        le2 = le2->Flink;\n                    }\n\n                    if (!found)\n                        fn = &hl->name;\n\n                    if (!deleted) {\n                        TRACE(\"fn = %.*S (found = %u)\\n\", (int)(fn->Length / sizeof(WCHAR)), fn->Buffer, found);\n\n                        if (feli)\n                            bytes_needed = (LONG)sector_align(bytes_needed, 8);\n\n                        bytes_needed += sizeof(FILE_LINK_ENTRY_INFORMATION) + fn->Length - sizeof(WCHAR);\n\n                        if (bytes_needed > *length)\n                            overflow = true;\n\n                        if (!overflow) {\n                            if (feli) {\n                                feli->NextEntryOffset = (ULONG)sector_align(sizeof(FILE_LINK_ENTRY_INFORMATION) + ((feli->FileNameLength - 1) * sizeof(WCHAR)), 8);\n                                feli = (FILE_LINK_ENTRY_INFORMATION*)((uint8_t*)feli + feli->NextEntryOffset);\n                            } else\n                                feli = &fli->Entry;\n\n                            feli->NextEntryOffset = 0;\n                            feli->ParentFileId = parfr->fcb->inode;\n                            feli->FileNameLength = fn->Length / sizeof(WCHAR);\n                            RtlCopyMemory(feli->FileName, fn->Buffer, fn->Length);\n\n                            fli->EntriesReturned++;\n\n                            len = bytes_needed;\n                        }\n                    }\n\n                    free_fileref(parfr);\n                }\n\n                le = le->Flink;\n            }\n        }\n\n        ExReleaseResourceLite(&fcb->Vcb->fileref_lock);\n    }\n\n    fli->BytesNeeded = bytes_needed;\n\n    *length -= len;\n\n    Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    return Status;\n}\n\nstatic NTSTATUS fill_in_hard_link_full_id_information(FILE_LINKS_FULL_ID_INFORMATION* flfii, file_ref* fileref, PIRP Irp, LONG* length) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    LONG bytes_needed;\n    FILE_LINK_ENTRY_FULL_ID_INFORMATION* flefii;\n    bool overflow = false;\n    fcb* fcb = fileref->fcb;\n    ULONG len;\n\n    if (fcb->ads)\n        return STATUS_INVALID_PARAMETER;\n\n    if (*length < (LONG)offsetof(FILE_LINKS_FULL_ID_INFORMATION, Entry))\n        return STATUS_INVALID_PARAMETER;\n\n    RtlZeroMemory(flfii, *length);\n\n    bytes_needed = offsetof(FILE_LINKS_FULL_ID_INFORMATION, Entry);\n    len = bytes_needed;\n    flefii = NULL;\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    if (fcb->inode == SUBVOL_ROOT_INODE) {\n        ULONG namelen;\n\n        if (fcb == fcb->Vcb->root_fileref->fcb)\n            namelen = sizeof(WCHAR);\n        else\n            namelen = fileref->dc->name.Length;\n\n        bytes_needed += offsetof(FILE_LINK_ENTRY_FULL_ID_INFORMATION, FileName[0]) + namelen;\n\n        if (bytes_needed > *length)\n            overflow = true;\n\n        if (!overflow) {\n            flefii = &flfii->Entry;\n\n            flefii->NextEntryOffset = 0;\n\n            if (fcb == fcb->Vcb->root_fileref->fcb) {\n                RtlZeroMemory(&flefii->ParentFileId.Identifier[0], sizeof(FILE_ID_128));\n                flefii->FileNameLength = 1;\n                flefii->FileName[0] = '.';\n            } else {\n                RtlCopyMemory(&flefii->ParentFileId.Identifier[0], &fileref->parent->fcb->inode, sizeof(uint64_t));\n                RtlCopyMemory(&flefii->ParentFileId.Identifier[sizeof(uint64_t)], &fileref->parent->fcb->subvol->id, sizeof(uint64_t));\n\n                flefii->FileNameLength = fileref->dc->name.Length / sizeof(WCHAR);\n                RtlCopyMemory(flefii->FileName, fileref->dc->name.Buffer, fileref->dc->name.Length);\n            }\n\n            flfii->EntriesReturned++;\n\n            len = bytes_needed;\n        }\n    } else {\n        ExAcquireResourceExclusiveLite(&fcb->Vcb->fileref_lock, true);\n\n        if (IsListEmpty(&fcb->hardlinks)) {\n            bytes_needed += offsetof(FILE_LINK_ENTRY_FULL_ID_INFORMATION, FileName[0]) + fileref->dc->name.Length;\n\n            if (bytes_needed > *length)\n                overflow = true;\n\n            if (!overflow) {\n                flefii = &flfii->Entry;\n\n                flefii->NextEntryOffset = 0;\n\n                RtlCopyMemory(&flefii->ParentFileId.Identifier[0], &fileref->parent->fcb->inode, sizeof(uint64_t));\n                RtlCopyMemory(&flefii->ParentFileId.Identifier[sizeof(uint64_t)], &fileref->parent->fcb->subvol->id, sizeof(uint64_t));\n\n                flefii->FileNameLength = fileref->dc->name.Length / sizeof(WCHAR);\n                RtlCopyMemory(flefii->FileName, fileref->dc->name.Buffer, fileref->dc->name.Length);\n\n                flfii->EntriesReturned++;\n\n                len = bytes_needed;\n            }\n        } else {\n            le = fcb->hardlinks.Flink;\n            while (le != &fcb->hardlinks) {\n                hardlink* hl = CONTAINING_RECORD(le, hardlink, list_entry);\n                file_ref* parfr;\n\n                TRACE(\"parent %I64x, index %I64x, name %.*S\\n\", hl->parent, hl->index, (int)(hl->name.Length / sizeof(WCHAR)), hl->name.Buffer);\n\n                Status = open_fileref_by_inode(fcb->Vcb, fcb->subvol, hl->parent, &parfr, Irp);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"open_fileref_by_inode returned %08lx\\n\", Status);\n                } else if (!parfr->deleted) {\n                    LIST_ENTRY* le2;\n                    bool found = false, deleted = false;\n                    UNICODE_STRING* fn = NULL;\n\n                    le2 = parfr->children.Flink;\n                    while (le2 != &parfr->children) {\n                        file_ref* fr2 = CONTAINING_RECORD(le2, file_ref, list_entry);\n\n                        if (fr2->dc->index == hl->index) {\n                            found = true;\n                            deleted = fr2->deleted;\n\n                            if (!deleted)\n                                fn = &fr2->dc->name;\n\n                            break;\n                        }\n\n                        le2 = le2->Flink;\n                    }\n\n                    if (!found)\n                        fn = &hl->name;\n\n                    if (!deleted) {\n                        TRACE(\"fn = %.*S (found = %u)\\n\", (int)(fn->Length / sizeof(WCHAR)), fn->Buffer, found);\n\n                        if (flefii)\n                            bytes_needed = (LONG)sector_align(bytes_needed, 8);\n\n                        bytes_needed += offsetof(FILE_LINK_ENTRY_FULL_ID_INFORMATION, FileName[0]) + fn->Length;\n\n                        if (bytes_needed > *length)\n                            overflow = true;\n\n                        if (!overflow) {\n                            if (flefii) {\n                                flefii->NextEntryOffset = (ULONG)sector_align(offsetof(FILE_LINK_ENTRY_FULL_ID_INFORMATION, FileName[0]) + (flefii->FileNameLength * sizeof(WCHAR)), 8);\n                                flefii = (FILE_LINK_ENTRY_FULL_ID_INFORMATION*)((uint8_t*)flefii + flefii->NextEntryOffset);\n                            } else\n                                flefii = &flfii->Entry;\n\n                            flefii->NextEntryOffset = 0;\n\n                            RtlCopyMemory(&flefii->ParentFileId.Identifier[0], &parfr->fcb->inode, sizeof(uint64_t));\n                            RtlCopyMemory(&flefii->ParentFileId.Identifier[sizeof(uint64_t)], &parfr->fcb->subvol->id, sizeof(uint64_t));\n\n                            flefii->FileNameLength = fn->Length / sizeof(WCHAR);\n                            RtlCopyMemory(flefii->FileName, fn->Buffer, fn->Length);\n\n                            flfii->EntriesReturned++;\n\n                            len = bytes_needed;\n                        }\n                    }\n\n                    free_fileref(parfr);\n                }\n\n                le = le->Flink;\n            }\n        }\n\n        ExReleaseResourceLite(&fcb->Vcb->fileref_lock);\n    }\n\n    flfii->BytesNeeded = bytes_needed;\n\n    *length -= len;\n\n    Status = overflow ? STATUS_BUFFER_OVERFLOW : STATUS_SUCCESS;\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    return Status;\n}\n\nstatic NTSTATUS fill_in_file_id_information(FILE_ID_INFORMATION* fii, fcb* fcb, LONG* length) {\n    RtlCopyMemory(&fii->VolumeSerialNumber, &fcb->Vcb->superblock.uuid.uuid[8], sizeof(uint64_t));\n    RtlCopyMemory(&fii->FileId.Identifier[0], &fcb->inode, sizeof(uint64_t));\n    RtlCopyMemory(&fii->FileId.Identifier[sizeof(uint64_t)], &fcb->subvol->id, sizeof(uint64_t));\n\n    *length -= sizeof(FILE_ID_INFORMATION);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fill_in_file_stat_information(FILE_STAT_INFORMATION* fsi, fcb* fcb, ccb* ccb, LONG* length) {\n    INODE_ITEM* ii;\n\n    fsi->FileId.QuadPart = make_file_id(fcb->subvol, fcb->inode);\n\n    if (fcb->ads)\n        ii = &ccb->fileref->parent->fcb->inode_item;\n    else\n        ii = &fcb->inode_item;\n\n    if (fcb == fcb->Vcb->dummy_fcb) {\n        LARGE_INTEGER time;\n\n        KeQuerySystemTime(&time);\n        fsi->CreationTime = fsi->LastAccessTime = fsi->LastWriteTime = fsi->ChangeTime = time;\n    } else {\n        fsi->CreationTime.QuadPart = unix_time_to_win(&ii->otime);\n        fsi->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);\n        fsi->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);\n        fsi->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime);\n    }\n\n    if (fcb->ads) {\n        fsi->AllocationSize.QuadPart = fsi->EndOfFile.QuadPart = fcb->adsdata.Length;\n        fsi->FileAttributes = ccb->fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : ccb->fileref->parent->fcb->atts;\n    } else {\n        fsi->AllocationSize.QuadPart = fcb_alloc_size(fcb);\n        fsi->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;\n        fsi->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts;\n    }\n\n    if (fcb->type == BTRFS_TYPE_SOCKET)\n        fsi->ReparseTag = IO_REPARSE_TAG_AF_UNIX;\n    else if (fcb->type == BTRFS_TYPE_FIFO)\n        fsi->ReparseTag = IO_REPARSE_TAG_LX_FIFO;\n    else if (fcb->type == BTRFS_TYPE_CHARDEV)\n        fsi->ReparseTag = IO_REPARSE_TAG_LX_CHR;\n    else if (fcb->type == BTRFS_TYPE_BLOCKDEV)\n        fsi->ReparseTag = IO_REPARSE_TAG_LX_BLK;\n    else if (!(fsi->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))\n        fsi->ReparseTag = 0;\n    else\n        fsi->ReparseTag = get_reparse_tag_fcb(fcb);\n\n    if (fcb->type == BTRFS_TYPE_SOCKET || fcb->type == BTRFS_TYPE_FIFO || fcb->type == BTRFS_TYPE_CHARDEV || fcb->type == BTRFS_TYPE_BLOCKDEV)\n        fsi->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;\n\n    if (fcb->ads)\n        fsi->NumberOfLinks = ccb->fileref->parent->fcb->inode_item.st_nlink;\n    else\n        fsi->NumberOfLinks = fcb->inode_item.st_nlink;\n\n    fsi->EffectiveAccess = ccb->access;\n\n    *length -= sizeof(FILE_STAT_INFORMATION);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fill_in_file_stat_lx_information(FILE_STAT_LX_INFORMATION* fsli, fcb* fcb, ccb* ccb, LONG* length) {\n    INODE_ITEM* ii;\n\n    fsli->FileId.QuadPart = make_file_id(fcb->subvol, fcb->inode);\n\n    if (fcb->ads)\n        ii = &ccb->fileref->parent->fcb->inode_item;\n    else\n        ii = &fcb->inode_item;\n\n    if (fcb == fcb->Vcb->dummy_fcb) {\n        LARGE_INTEGER time;\n\n        KeQuerySystemTime(&time);\n        fsli->CreationTime = fsli->LastAccessTime = fsli->LastWriteTime = fsli->ChangeTime = time;\n    } else {\n        fsli->CreationTime.QuadPart = unix_time_to_win(&ii->otime);\n        fsli->LastAccessTime.QuadPart = unix_time_to_win(&ii->st_atime);\n        fsli->LastWriteTime.QuadPart = unix_time_to_win(&ii->st_mtime);\n        fsli->ChangeTime.QuadPart = unix_time_to_win(&ii->st_ctime);\n    }\n\n    if (fcb->ads) {\n        fsli->AllocationSize.QuadPart = fsli->EndOfFile.QuadPart = fcb->adsdata.Length;\n        fsli->FileAttributes = ccb->fileref->parent->fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : ccb->fileref->parent->fcb->atts;\n    } else {\n        fsli->AllocationSize.QuadPart = fcb_alloc_size(fcb);\n        fsli->EndOfFile.QuadPart = S_ISDIR(fcb->inode_item.st_mode) ? 0 : fcb->inode_item.st_size;\n        fsli->FileAttributes = fcb->atts == 0 ? FILE_ATTRIBUTE_NORMAL : fcb->atts;\n    }\n\n    if (fcb->type == BTRFS_TYPE_SOCKET)\n        fsli->ReparseTag = IO_REPARSE_TAG_AF_UNIX;\n    else if (fcb->type == BTRFS_TYPE_FIFO)\n        fsli->ReparseTag = IO_REPARSE_TAG_LX_FIFO;\n    else if (fcb->type == BTRFS_TYPE_CHARDEV)\n        fsli->ReparseTag = IO_REPARSE_TAG_LX_CHR;\n    else if (fcb->type == BTRFS_TYPE_BLOCKDEV)\n        fsli->ReparseTag = IO_REPARSE_TAG_LX_BLK;\n    else if (!(fsli->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))\n        fsli->ReparseTag = 0;\n    else\n        fsli->ReparseTag = get_reparse_tag_fcb(fcb);\n\n    if (fcb->type == BTRFS_TYPE_SOCKET || fcb->type == BTRFS_TYPE_FIFO || fcb->type == BTRFS_TYPE_CHARDEV || fcb->type == BTRFS_TYPE_BLOCKDEV)\n        fsli->FileAttributes |= FILE_ATTRIBUTE_REPARSE_POINT;\n\n    if (fcb->ads)\n        fsli->NumberOfLinks = ccb->fileref->parent->fcb->inode_item.st_nlink;\n    else\n        fsli->NumberOfLinks = fcb->inode_item.st_nlink;\n\n    fsli->EffectiveAccess = ccb->access;\n    fsli->LxFlags = LX_FILE_METADATA_HAS_UID | LX_FILE_METADATA_HAS_GID | LX_FILE_METADATA_HAS_MODE | LX_FILE_METADATA_HAS_DEVICE_ID;\n\n    if (fcb->case_sensitive)\n        fsli->LxFlags |= LX_FILE_CASE_SENSITIVE_DIR;\n\n    fsli->LxUid = ii->st_uid;\n    fsli->LxGid = ii->st_gid;\n    fsli->LxMode = ii->st_mode;\n\n    if (ii->st_mode & __S_IFBLK || ii->st_mode & __S_IFCHR) {\n        fsli->LxDeviceIdMajor = (ii->st_rdev & 0xFFFFFFFFFFF00000) >> 20;\n        fsli->LxDeviceIdMinor = (ii->st_rdev & 0xFFFFF);\n    } else {\n        fsli->LxDeviceIdMajor = 0;\n        fsli->LxDeviceIdMinor = 0;\n    }\n\n    *length -= sizeof(FILE_STAT_LX_INFORMATION);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fill_in_file_case_sensitive_information(FILE_CASE_SENSITIVE_INFORMATION* fcsi, fcb* fcb, LONG* length) {\n    fcsi->Flags = fcb->case_sensitive ? FILE_CS_FLAG_CASE_SENSITIVE_DIR : 0;\n\n    *length -= sizeof(FILE_CASE_SENSITIVE_INFORMATION);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fill_in_file_compression_information(FILE_COMPRESSION_INFORMATION* fci, LONG* length, fcb* fcb) {\n    *length -= sizeof(FILE_COMPRESSION_INFORMATION);\n\n    memset(fci, 0, sizeof(FILE_COMPRESSION_INFORMATION));\n\n    if (fcb->ads)\n        fci->CompressedFileSize.QuadPart = fcb->adsdata.Length;\n    else if (!S_ISDIR(fcb->inode_item.st_mode))\n        fci->CompressedFileSize.QuadPart = fcb->inode_item.st_size;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS query_info(device_extension* Vcb, PFILE_OBJECT FileObject, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    LONG length = IrpSp->Parameters.QueryFile.Length;\n    fcb* fcb = FileObject->FsContext;\n    ccb* ccb = FileObject->FsContext2;\n    file_ref* fileref = ccb ? ccb->fileref : NULL;\n    NTSTATUS Status;\n\n    TRACE(\"(%p, %p, %p)\\n\", Vcb, FileObject, Irp);\n    TRACE(\"fcb = %p\\n\", fcb);\n\n    if (fcb == Vcb->volume_fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!ccb) {\n        ERR(\"ccb is NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    switch (IrpSp->Parameters.QueryFile.FileInformationClass) {\n        case FileAllInformation:\n        {\n            FILE_ALL_INFORMATION* fai = Irp->AssociatedIrp.SystemBuffer;\n            INODE_ITEM* ii;\n\n            TRACE(\"FileAllInformation\\n\");\n\n            if (Irp->RequestorMode != KernelMode && !(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) {\n                WARN(\"insufficient privileges\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                goto exit;\n            }\n\n            if (fcb->ads) {\n                if (!fileref || !fileref->parent) {\n                    ERR(\"no fileref for stream\\n\");\n                    Status = STATUS_INTERNAL_ERROR;\n                    goto exit;\n                }\n\n                ii = &fileref->parent->fcb->inode_item;\n            } else\n                ii = &fcb->inode_item;\n\n            // Access, mode, and alignment are all filled in by the kernel\n\n            if (length > 0)\n                fill_in_file_basic_information(&fai->BasicInformation, ii, &length, fcb, fileref);\n\n            if (length > 0)\n                fill_in_file_standard_information(&fai->StandardInformation, fcb, fileref, &length);\n\n            if (length > 0)\n                fill_in_file_internal_information(&fai->InternalInformation, fcb, &length);\n\n            if (length > 0)\n                fill_in_file_ea_information(&fai->EaInformation, fcb, &length);\n\n            length -= sizeof(FILE_ACCESS_INFORMATION);\n\n            if (length > 0)\n                fill_in_file_position_information(&fai->PositionInformation, FileObject, &length);\n\n            length -= sizeof(FILE_MODE_INFORMATION);\n\n            length -= sizeof(FILE_ALIGNMENT_INFORMATION);\n\n            if (length > 0)\n                fill_in_file_name_information(&fai->NameInformation, fcb, fileref, &length);\n\n            Status = STATUS_SUCCESS;\n\n            break;\n        }\n\n        case FileAttributeTagInformation:\n        {\n            FILE_ATTRIBUTE_TAG_INFORMATION* ati = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileAttributeTagInformation\\n\");\n\n            if (Irp->RequestorMode != KernelMode && !(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) {\n                WARN(\"insufficient privileges\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                goto exit;\n            }\n\n            ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n            Status = fill_in_file_attribute_information(ati, fcb, ccb, &length);\n            ExReleaseResourceLite(&Vcb->tree_lock);\n\n            break;\n        }\n\n        case FileBasicInformation:\n        {\n            FILE_BASIC_INFORMATION* fbi = Irp->AssociatedIrp.SystemBuffer;\n            INODE_ITEM* ii;\n\n            TRACE(\"FileBasicInformation\\n\");\n\n            if (Irp->RequestorMode != KernelMode && !(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) {\n                WARN(\"insufficient privileges\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                goto exit;\n            }\n\n            if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_BASIC_INFORMATION)) {\n                WARN(\"overflow\\n\");\n                Status = STATUS_BUFFER_OVERFLOW;\n                goto exit;\n            }\n\n            if (fcb->ads) {\n                if (!fileref || !fileref->parent) {\n                    ERR(\"no fileref for stream\\n\");\n                    Status = STATUS_INTERNAL_ERROR;\n                    goto exit;\n                }\n\n                ii = &fileref->parent->fcb->inode_item;\n            } else\n                ii = &fcb->inode_item;\n\n            Status = fill_in_file_basic_information(fbi, ii, &length, fcb, fileref);\n            break;\n        }\n\n        case FileCompressionInformation:\n        {\n            FILE_COMPRESSION_INFORMATION* fci = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileCompressionInformation\\n\");\n\n            Status = fill_in_file_compression_information(fci, &length, fcb);\n            break;\n        }\n\n        case FileEaInformation:\n        {\n            FILE_EA_INFORMATION* eai = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileEaInformation\\n\");\n\n            Status = fill_in_file_ea_information(eai, fcb, &length);\n\n            break;\n        }\n\n        case FileInternalInformation:\n        {\n            FILE_INTERNAL_INFORMATION* fii = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileInternalInformation\\n\");\n\n            Status = fill_in_file_internal_information(fii, fcb, &length);\n\n            break;\n        }\n\n        case FileNameInformation:\n        {\n            FILE_NAME_INFORMATION* fni = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileNameInformation\\n\");\n\n            Status = fill_in_file_name_information(fni, fcb, fileref, &length);\n\n            break;\n        }\n\n        case FileNetworkOpenInformation:\n        {\n            FILE_NETWORK_OPEN_INFORMATION* fnoi = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileNetworkOpenInformation\\n\");\n\n            if (Irp->RequestorMode != KernelMode && !(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES))) {\n                WARN(\"insufficient privileges\\n\");\n                Status = STATUS_ACCESS_DENIED;\n                goto exit;\n            }\n\n            Status = fill_in_file_network_open_information(fnoi, fcb, fileref, &length);\n\n            break;\n        }\n\n        case FilePositionInformation:\n        {\n            FILE_POSITION_INFORMATION* fpi = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FilePositionInformation\\n\");\n\n            Status = fill_in_file_position_information(fpi, FileObject, &length);\n\n            break;\n        }\n\n        case FileStandardInformation:\n        {\n            FILE_STANDARD_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileStandardInformation\\n\");\n\n            if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_STANDARD_INFORMATION)) {\n                WARN(\"overflow\\n\");\n                Status = STATUS_BUFFER_OVERFLOW;\n                goto exit;\n            }\n\n            Status = fill_in_file_standard_information(fsi, fcb, ccb->fileref, &length);\n\n            break;\n        }\n\n        case FileStreamInformation:\n        {\n            FILE_STREAM_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileStreamInformation\\n\");\n\n            Status = fill_in_file_stream_information(fsi, fileref, &length);\n\n            break;\n        }\n\n        case FileHardLinkInformation:\n        {\n            FILE_LINKS_INFORMATION* fli = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileHardLinkInformation\\n\");\n\n            ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n            Status = fill_in_hard_link_information(fli, fileref, Irp, &length);\n            ExReleaseResourceLite(&Vcb->tree_lock);\n\n            break;\n        }\n\n        case FileNormalizedNameInformation:\n        {\n            FILE_NAME_INFORMATION* fni = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileNormalizedNameInformation\\n\");\n\n            Status = fill_in_file_name_information(fni, fcb, fileref, &length);\n\n            break;\n        }\n\n        case FileStandardLinkInformation:\n        {\n            FILE_STANDARD_LINK_INFORMATION* fsli = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileStandardLinkInformation\\n\");\n\n            Status = fill_in_file_standard_link_information(fsli, fcb, ccb->fileref, &length);\n\n            break;\n        }\n\n        case FileRemoteProtocolInformation:\n            TRACE(\"FileRemoteProtocolInformation\\n\");\n            Status = STATUS_INVALID_PARAMETER;\n            goto exit;\n\n#ifndef _MSC_VER\n#pragma GCC diagnostic push\n#pragma GCC diagnostic ignored \"-Wswitch\"\n#endif\n        case FileIdInformation:\n        {\n            FILE_ID_INFORMATION* fii = Irp->AssociatedIrp.SystemBuffer;\n\n            if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_ID_INFORMATION)) {\n                WARN(\"overflow\\n\");\n                Status = STATUS_BUFFER_OVERFLOW;\n                goto exit;\n            }\n\n            TRACE(\"FileIdInformation\\n\");\n\n            Status = fill_in_file_id_information(fii, fcb, &length);\n\n            break;\n        }\n\n        case FileStatInformation:\n        {\n            FILE_STAT_INFORMATION* fsi = Irp->AssociatedIrp.SystemBuffer;\n\n            if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_STAT_INFORMATION)) {\n                WARN(\"overflow\\n\");\n                Status = STATUS_BUFFER_OVERFLOW;\n                goto exit;\n            }\n\n            TRACE(\"FileStatInformation\\n\");\n\n            Status = fill_in_file_stat_information(fsi, fcb, ccb, &length);\n\n            break;\n        }\n\n        case FileStatLxInformation:\n        {\n            FILE_STAT_LX_INFORMATION* fsli = Irp->AssociatedIrp.SystemBuffer;\n\n            if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_STAT_LX_INFORMATION)) {\n                WARN(\"overflow\\n\");\n                Status = STATUS_BUFFER_OVERFLOW;\n                goto exit;\n            }\n\n            TRACE(\"FileStatLxInformation\\n\");\n\n            Status = fill_in_file_stat_lx_information(fsli, fcb, ccb, &length);\n\n            break;\n        }\n\n        case FileCaseSensitiveInformation:\n        {\n            FILE_CASE_SENSITIVE_INFORMATION* fcsi = Irp->AssociatedIrp.SystemBuffer;\n\n            if (IrpSp->Parameters.QueryFile.Length < sizeof(FILE_CASE_SENSITIVE_INFORMATION)) {\n                WARN(\"overflow\\n\");\n                Status = STATUS_BUFFER_OVERFLOW;\n                goto exit;\n            }\n\n            TRACE(\"FileCaseSensitiveInformation\\n\");\n\n            Status = fill_in_file_case_sensitive_information(fcsi, fcb, &length);\n\n            break;\n        }\n\n        case FileHardLinkFullIdInformation:\n        {\n            FILE_LINKS_FULL_ID_INFORMATION* flfii = Irp->AssociatedIrp.SystemBuffer;\n\n            TRACE(\"FileHardLinkFullIdInformation\\n\");\n\n            ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n            Status = fill_in_hard_link_full_id_information(flfii, fileref, Irp, &length);\n            ExReleaseResourceLite(&Vcb->tree_lock);\n\n            break;\n        }\n#ifndef _MSC_VER\n#pragma GCC diagnostic pop\n#endif\n\n        default:\n            WARN(\"unknown FileInformationClass %u\\n\", IrpSp->Parameters.QueryFile.FileInformationClass);\n            Status = STATUS_INVALID_PARAMETER;\n            goto exit;\n    }\n\n    if (length < 0) {\n        length = 0;\n        Status = STATUS_BUFFER_OVERFLOW;\n    }\n\n    Irp->IoStatus.Information = IrpSp->Parameters.QueryFile.Length - length;\n\nexit:\n    TRACE(\"query_info returning %08lx\\n\", Status);\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_QUERY_INFORMATION)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_query_information(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp;\n    NTSTATUS Status;\n    fcb* fcb;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    Irp->IoStatus.Information = 0;\n\n    TRACE(\"query information\\n\");\n\n    IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n    fcb = IrpSp->FileObject->FsContext;\n    TRACE(\"fcb = %p\\n\", fcb);\n    TRACE(\"fcb->subvol = %p\\n\", fcb->subvol);\n\n    Status = query_info(fcb->Vcb, IrpSp->FileObject, Irp);\n\nend:\n    TRACE(\"returning %08lx\\n\", Status);\n\n    Irp->IoStatus.Status = Status;\n\n    IoCompleteRequest( Irp, IO_NO_INCREMENT );\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_QUERY_EA)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_query_ea(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    NTSTATUS Status;\n    bool top_level;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    fcb* fcb;\n    ccb* ccb;\n    FILE_FULL_EA_INFORMATION* ffei;\n    ULONG retlen = 0;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"(%p, %p)\\n\", DeviceObject, Irp);\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    ffei = map_user_buffer(Irp, NormalPagePriority);\n    if (!ffei) {\n        ERR(\"could not get output buffer\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (!FileObject) {\n        ERR(\"no file object\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb) {\n        ERR(\"no fcb\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (fcb == fcb->Vcb->volume_fcb) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    ccb = FileObject->FsContext2;\n\n    if (!ccb) {\n        ERR(\"no ccb\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_READ_EA | FILE_WRITE_EA))) {\n        WARN(\"insufficient privileges\\n\");\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    if (fcb->ads)\n        fcb = ccb->fileref->parent->fcb;\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    if (fcb->ea_xattr.Length == 0) {\n        Status = STATUS_NO_EAS_ON_FILE;\n        goto end2;\n    }\n\n    Status = STATUS_SUCCESS;\n\n    if (IrpSp->Parameters.QueryEa.EaList) {\n        FILE_FULL_EA_INFORMATION *ea, *out;\n        FILE_GET_EA_INFORMATION* in;\n        uint8_t padding = retlen % 4 > 0 ? (4 - (retlen % 4)) : 0;\n\n        in = IrpSp->Parameters.QueryEa.EaList;\n        out = NULL;\n\n        do {\n            bool found = false;\n            STRING s;\n\n            s.Length = s.MaximumLength = in->EaNameLength;\n            s.Buffer = in->EaName;\n\n            RtlUpperString(&s, &s);\n\n            ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer;\n\n            do {\n                if (ea->EaNameLength == in->EaNameLength &&\n                    RtlCompareMemory(ea->EaName, in->EaName, ea->EaNameLength) == ea->EaNameLength) {\n                    found = true;\n                    break;\n                }\n\n                if (ea->NextEntryOffset == 0)\n                    break;\n\n                ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);\n            } while (true);\n\n            if (offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + 1 + ea->EaValueLength > IrpSp->Parameters.QueryEa.Length - retlen - padding) {\n                Status = STATUS_BUFFER_OVERFLOW;\n                retlen = 0;\n                goto end2;\n            }\n\n            retlen += padding;\n\n            if (out) {\n                out->NextEntryOffset = (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + out->EaNameLength + 1 + out->EaValueLength + padding;\n                out = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)out) + out->NextEntryOffset);\n            } else\n                out = ffei;\n\n            out->NextEntryOffset = 0;\n\n            if (found) {\n                out->Flags = ea->Flags;\n                out->EaNameLength = ea->EaNameLength;\n                out->EaValueLength = ea->EaValueLength;\n                RtlCopyMemory(out->EaName, ea->EaName, ea->EaNameLength + ea->EaValueLength + 1);\n            } else {\n                out->Flags = 0;\n                out->EaNameLength = in->EaNameLength;\n                out->EaValueLength = 0;\n                RtlCopyMemory(out->EaName, in->EaName, in->EaNameLength);\n                out->EaName[in->EaNameLength] = 0;\n            }\n\n            retlen += (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + out->EaNameLength + 1 + out->EaValueLength;\n\n            if (IrpSp->Flags & SL_RETURN_SINGLE_ENTRY)\n                break;\n\n            if (in->NextEntryOffset == 0)\n                break;\n\n            in = (FILE_GET_EA_INFORMATION*)(((uint8_t*)in) + in->NextEntryOffset);\n        } while (true);\n    } else {\n        FILE_FULL_EA_INFORMATION *ea, *out;\n        ULONG index;\n\n        if (IrpSp->Flags & SL_INDEX_SPECIFIED) {\n            // The index is 1-based\n            if (IrpSp->Parameters.QueryEa.EaIndex == 0) {\n                Status = STATUS_NONEXISTENT_EA_ENTRY;\n                goto end2;\n            } else\n                index = IrpSp->Parameters.QueryEa.EaIndex - 1;\n        } else if (IrpSp->Flags & SL_RESTART_SCAN)\n            index = ccb->ea_index = 0;\n        else\n            index = ccb->ea_index;\n\n        ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer;\n\n        if (index > 0) {\n            ULONG i;\n\n            for (i = 0; i < index; i++) {\n                if (ea->NextEntryOffset == 0) { // last item\n                    Status = STATUS_NO_MORE_EAS;\n                    goto end2;\n                }\n\n                ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);\n            }\n        }\n\n        out = NULL;\n\n        do {\n            uint8_t padding = retlen % 4 > 0 ? (4 - (retlen % 4)) : 0;\n\n            if (offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + 1 + ea->EaValueLength > IrpSp->Parameters.QueryEa.Length - retlen - padding) {\n                Status = retlen == 0 ? STATUS_BUFFER_TOO_SMALL : STATUS_BUFFER_OVERFLOW;\n                goto end2;\n            }\n\n            retlen += padding;\n\n            if (out) {\n                out->NextEntryOffset = (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + out->EaNameLength + 1 + out->EaValueLength + padding;\n                out = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)out) + out->NextEntryOffset);\n            } else\n                out = ffei;\n\n            out->NextEntryOffset = 0;\n            out->Flags = ea->Flags;\n            out->EaNameLength = ea->EaNameLength;\n            out->EaValueLength = ea->EaValueLength;\n            RtlCopyMemory(out->EaName, ea->EaName, ea->EaNameLength + ea->EaValueLength + 1);\n\n            retlen += (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + 1 + ea->EaValueLength;\n\n            if (!(IrpSp->Flags & SL_INDEX_SPECIFIED))\n                ccb->ea_index++;\n\n            if (ea->NextEntryOffset == 0 || IrpSp->Flags & SL_RETURN_SINGLE_ENTRY)\n                break;\n\n            ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);\n        } while (true);\n    }\n\nend2:\n    ExReleaseResourceLite(fcb->Header.Resource);\n\nend:\n    TRACE(\"returning %08lx\\n\", Status);\n\n    Irp->IoStatus.Status = Status;\n    Irp->IoStatus.Information = NT_SUCCESS(Status) || Status == STATUS_BUFFER_OVERFLOW ? retlen : 0;\n\n    IoCompleteRequest( Irp, IO_NO_INCREMENT );\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_SET_EA)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_set_ea(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    NTSTATUS Status;\n    bool top_level;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    fcb* fcb;\n    ccb* ccb;\n    file_ref* fileref;\n    FILE_FULL_EA_INFORMATION* ffei;\n    ULONG offset;\n    LIST_ENTRY ealist;\n    ea_item* item;\n    FILE_FULL_EA_INFORMATION* ea;\n    LIST_ENTRY* le;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"(%p, %p)\\n\", DeviceObject, Irp);\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (Vcb->readonly) {\n        Status = STATUS_MEDIA_WRITE_PROTECTED;\n        goto end;\n    }\n\n    ffei = map_user_buffer(Irp, NormalPagePriority);\n    if (!ffei) {\n        ERR(\"could not get output buffer\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    Status = IoCheckEaBufferValidity(ffei, IrpSp->Parameters.SetEa.Length, &offset);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoCheckEaBufferValidity returned %08lx (error at offset %lu)\\n\", Status, offset);\n        goto end;\n    }\n\n    if (!FileObject) {\n        ERR(\"no file object\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb) {\n        ERR(\"no fcb\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (fcb == fcb->Vcb->volume_fcb) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    ccb = FileObject->FsContext2;\n\n    if (!ccb) {\n        ERR(\"no ccb\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_EA)) {\n        WARN(\"insufficient privileges\\n\");\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    if (fcb->ads) {\n        fileref = ccb->fileref->parent;\n        fcb = fileref->fcb;\n    } else\n        fileref = ccb->fileref;\n\n    InitializeListHead(&ealist);\n\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (fcb->ea_xattr.Length > 0) {\n        ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer;\n\n        do {\n            item = ExAllocatePoolWithTag(PagedPool, sizeof(ea_item), ALLOC_TAG);\n            if (!item) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end2;\n            }\n\n            item->name.Length = item->name.MaximumLength = ea->EaNameLength;\n            item->name.Buffer = ea->EaName;\n\n            item->value.Length = item->value.MaximumLength = ea->EaValueLength;\n            item->value.Buffer = &ea->EaName[ea->EaNameLength + 1];\n\n            item->flags = ea->Flags;\n\n            InsertTailList(&ealist, &item->list_entry);\n\n            if (ea->NextEntryOffset == 0)\n                break;\n\n            ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);\n        } while (true);\n    }\n\n    ea = ffei;\n\n    do {\n        STRING s;\n        bool found = false;\n\n        s.Length = s.MaximumLength = ea->EaNameLength;\n        s.Buffer = ea->EaName;\n\n        RtlUpperString(&s, &s);\n\n        le = ealist.Flink;\n        while (le != &ealist) {\n            item = CONTAINING_RECORD(le, ea_item, list_entry);\n\n            if (item->name.Length == s.Length &&\n                RtlCompareMemory(item->name.Buffer, s.Buffer, s.Length) == s.Length) {\n                item->flags = ea->Flags;\n                item->value.Length = item->value.MaximumLength = ea->EaValueLength;\n                item->value.Buffer = &ea->EaName[ea->EaNameLength + 1];\n                found = true;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        if (!found) {\n            item = ExAllocatePoolWithTag(PagedPool, sizeof(ea_item), ALLOC_TAG);\n            if (!item) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end2;\n            }\n\n            item->name.Length = item->name.MaximumLength = ea->EaNameLength;\n            item->name.Buffer = ea->EaName;\n\n            item->value.Length = item->value.MaximumLength = ea->EaValueLength;\n            item->value.Buffer = &ea->EaName[ea->EaNameLength + 1];\n\n            item->flags = ea->Flags;\n\n            InsertTailList(&ealist, &item->list_entry);\n        }\n\n        if (ea->NextEntryOffset == 0)\n            break;\n\n        ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);\n    } while (true);\n\n    // remove entries with zero-length value\n    le = ealist.Flink;\n    while (le != &ealist) {\n        LIST_ENTRY* le2 = le->Flink;\n\n        item = CONTAINING_RECORD(le, ea_item, list_entry);\n\n        if (item->value.Length == 0) {\n            RemoveEntryList(&item->list_entry);\n            ExFreePool(item);\n        }\n\n        le = le2;\n    }\n\n    // handle LXSS values\n    le = ealist.Flink;\n    while (le != &ealist) {\n        LIST_ENTRY* le2 = le->Flink;\n\n        item = CONTAINING_RECORD(le, ea_item, list_entry);\n\n        if (item->name.Length == sizeof(lxuid) - 1 && RtlCompareMemory(item->name.Buffer, lxuid, item->name.Length) == item->name.Length) {\n            if (item->value.Length < sizeof(uint32_t)) {\n                ERR(\"uid value was shorter than expected\\n\");\n                Status = STATUS_INVALID_PARAMETER;\n                goto end2;\n            }\n\n            if (Irp->RequestorMode == KernelMode || ccb->access & FILE_WRITE_ATTRIBUTES) {\n                RtlCopyMemory(&fcb->inode_item.st_uid, item->value.Buffer, sizeof(uint32_t));\n                fcb->sd_dirty = true;\n                fcb->sd_deleted = false;\n            }\n\n            RemoveEntryList(&item->list_entry);\n            ExFreePool(item);\n        } else if (item->name.Length == sizeof(lxgid) - 1 && RtlCompareMemory(item->name.Buffer, lxgid, item->name.Length) == item->name.Length) {\n            if (item->value.Length < sizeof(uint32_t)) {\n                ERR(\"gid value was shorter than expected\\n\");\n                Status = STATUS_INVALID_PARAMETER;\n                goto end2;\n            }\n\n            if (Irp->RequestorMode == KernelMode || ccb->access & FILE_WRITE_ATTRIBUTES)\n                RtlCopyMemory(&fcb->inode_item.st_gid, item->value.Buffer, sizeof(uint32_t));\n\n            RemoveEntryList(&item->list_entry);\n            ExFreePool(item);\n        } else if (item->name.Length == sizeof(lxmod) - 1 && RtlCompareMemory(item->name.Buffer, lxmod, item->name.Length) == item->name.Length) {\n            if (item->value.Length < sizeof(uint32_t)) {\n                ERR(\"mode value was shorter than expected\\n\");\n                Status = STATUS_INVALID_PARAMETER;\n                goto end2;\n            }\n\n            if (Irp->RequestorMode == KernelMode || ccb->access & FILE_WRITE_ATTRIBUTES) {\n                uint32_t allowed = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH | S_ISGID | S_ISVTX | S_ISUID;\n                uint32_t val;\n\n                RtlCopyMemory(&val, item->value.Buffer, sizeof(uint32_t));\n\n                fcb->inode_item.st_mode &= ~allowed;\n                fcb->inode_item.st_mode |= val & allowed;\n            }\n\n            RemoveEntryList(&item->list_entry);\n            ExFreePool(item);\n        }\n\n        le = le2;\n    }\n\n    if (IsListEmpty(&ealist)) {\n        fcb->ealen = 0;\n\n        if (fcb->ea_xattr.Buffer)\n            ExFreePool(fcb->ea_xattr.Buffer);\n\n        fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = 0;\n        fcb->ea_xattr.Buffer = NULL;\n    } else {\n        uint16_t size = 0;\n        char *buf, *oldbuf;\n\n        le = ealist.Flink;\n        while (le != &ealist) {\n            item = CONTAINING_RECORD(le, ea_item, list_entry);\n\n            if (size % 4 > 0)\n                size += 4 - (size % 4);\n\n            size += (uint16_t)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + item->name.Length + 1 + item->value.Length;\n\n            le = le->Flink;\n        }\n\n        buf = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG);\n        if (!buf) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end2;\n        }\n\n        oldbuf = fcb->ea_xattr.Buffer;\n\n        fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = size;\n        fcb->ea_xattr.Buffer = buf;\n\n        fcb->ealen = 4;\n        ea = NULL;\n\n        le = ealist.Flink;\n        while (le != &ealist) {\n            item = CONTAINING_RECORD(le, ea_item, list_entry);\n\n            if (ea) {\n                ea->NextEntryOffset = (ULONG)offsetof(FILE_FULL_EA_INFORMATION, EaName[0]) + ea->EaNameLength + ea->EaValueLength;\n\n                if (ea->NextEntryOffset % 4 > 0)\n                    ea->NextEntryOffset += 4 - (ea->NextEntryOffset % 4);\n\n                ea = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)ea) + ea->NextEntryOffset);\n            } else\n                ea = (FILE_FULL_EA_INFORMATION*)fcb->ea_xattr.Buffer;\n\n            ea->NextEntryOffset = 0;\n            ea->Flags = item->flags;\n            ea->EaNameLength = (UCHAR)item->name.Length;\n            ea->EaValueLength = item->value.Length;\n\n            RtlCopyMemory(ea->EaName, item->name.Buffer, item->name.Length);\n            ea->EaName[item->name.Length] = 0;\n            RtlCopyMemory(&ea->EaName[item->name.Length + 1], item->value.Buffer, item->value.Length);\n\n            fcb->ealen += 5 + item->name.Length + item->value.Length;\n\n            le = le->Flink;\n        }\n\n        if (oldbuf)\n            ExFreePool(oldbuf);\n    }\n\n    fcb->ea_changed = true;\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    fcb->inode_item.transid = Vcb->superblock.generation;\n    fcb->inode_item.sequence++;\n\n    if (!ccb->user_set_change_time)\n        fcb->inode_item.st_ctime = now;\n\n    fcb->inode_item_changed = true;\n    mark_fcb_dirty(fcb);\n\n    send_notification_fileref(fileref, FILE_NOTIFY_CHANGE_EA, FILE_ACTION_MODIFIED, NULL);\n\n    Status = STATUS_SUCCESS;\n\nend2:\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    while (!IsListEmpty(&ealist)) {\n        le = RemoveHeadList(&ealist);\n\n        item = CONTAINING_RECORD(le, ea_item, list_entry);\n\n        ExFreePool(item);\n    }\n\nend:\n    TRACE(\"returning %08lx\\n\", Status);\n\n    Irp->IoStatus.Status = Status;\n    Irp->IoStatus.Information = 0;\n\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n"
  },
  {
    "path": "src/flushthread.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include \"zstd/lib/common/xxhash.h\"\n#include \"crc32c.h\"\n#include <ata.h>\n#include <ntddscsi.h>\n#include <ntddstor.h>\n\n/* cf. __MAX_CSUM_ITEMS in Linux - it needs sizeof(leaf_node) bytes free\n * so it can do a split. Linux tries to get it so a run will fit in a\n * sector, but the MAX_CSUM_ITEMS logic is wrong... */\n#define MAX_CSUM_SIZE (4096 - sizeof(tree_header) - (2 * sizeof(leaf_node)))\n\n// #define DEBUG_WRITE_LOOPS\n\n#define BATCH_ITEM_LIMIT 1000\n\ntypedef struct {\n    KEVENT Event;\n    IO_STATUS_BLOCK iosb;\n} write_context;\n\ntypedef struct {\n    EXTENT_ITEM_TREE eit;\n    uint8_t type;\n    TREE_BLOCK_REF tbr;\n} EXTENT_ITEM_TREE2;\n\ntypedef struct {\n    EXTENT_ITEM ei;\n    uint8_t type;\n    TREE_BLOCK_REF tbr;\n} EXTENT_ITEM_SKINNY_METADATA;\n\nstatic NTSTATUS create_chunk(device_extension* Vcb, chunk* c, PIRP Irp);\nstatic NTSTATUS update_tree_extents(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback);\n\nstatic NTSTATUS insert_tree_item_batch(LIST_ENTRY* batchlist, device_extension* Vcb, root* r, uint64_t objid,\n                                       uint8_t objtype, uint64_t offset, _In_opt_ _When_(return >= 0, __drv_aliasesMem) void* data,\n                                       uint16_t datalen, enum batch_operation operation);\n\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall write_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {\n    write_context* context = conptr;\n\n    UNUSED(DeviceObject);\n\n    context->iosb = Irp->IoStatus;\n    KeSetEvent(&context->Event, 0, false);\n\n    return STATUS_MORE_PROCESSING_REQUIRED;\n}\n\nNTSTATUS write_data_phys(_In_ PDEVICE_OBJECT device, _In_ PFILE_OBJECT fileobj, _In_ uint64_t address,\n                         _In_reads_bytes_(length) void* data, _In_ uint32_t length) {\n    NTSTATUS Status;\n    LARGE_INTEGER offset;\n    PIRP Irp;\n    PIO_STACK_LOCATION IrpSp;\n    write_context context;\n\n    TRACE(\"(%p, %I64x, %p, %x)\\n\", device, address, data, length);\n\n    RtlZeroMemory(&context, sizeof(write_context));\n\n    KeInitializeEvent(&context.Event, NotificationEvent, false);\n\n    offset.QuadPart = address;\n\n    Irp = IoAllocateIrp(device->StackSize, false);\n\n    if (!Irp) {\n        ERR(\"IoAllocateIrp failed\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    IrpSp = IoGetNextIrpStackLocation(Irp);\n    IrpSp->MajorFunction = IRP_MJ_WRITE;\n    IrpSp->FileObject = fileobj;\n\n    if (device->Flags & DO_BUFFERED_IO) {\n        Irp->AssociatedIrp.SystemBuffer = data;\n\n        Irp->Flags = IRP_BUFFERED_IO;\n    } else if (device->Flags & DO_DIRECT_IO) {\n        Irp->MdlAddress = IoAllocateMdl(data, length, false, false, NULL);\n        if (!Irp->MdlAddress) {\n            DbgPrint(\"IoAllocateMdl failed\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        Status = STATUS_SUCCESS;\n\n        try {\n            MmProbeAndLockPages(Irp->MdlAddress, KernelMode, IoReadAccess);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n            IoFreeMdl(Irp->MdlAddress);\n            goto exit;\n        }\n    } else {\n        Irp->UserBuffer = data;\n    }\n\n    IrpSp->Parameters.Write.Length = length;\n    IrpSp->Parameters.Write.ByteOffset = offset;\n\n    Irp->UserIosb = &context.iosb;\n\n    Irp->UserEvent = &context.Event;\n\n    IoSetCompletionRoutine(Irp, write_completion, &context, true, true, true);\n\n    Status = IoCallDriver(device, Irp);\n\n    if (Status == STATUS_PENDING) {\n        KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n        Status = context.iosb.Status;\n    }\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoCallDriver returned %08lx\\n\", Status);\n    }\n\n    if (device->Flags & DO_DIRECT_IO) {\n        MmUnlockPages(Irp->MdlAddress);\n        IoFreeMdl(Irp->MdlAddress);\n    }\n\nexit:\n    IoFreeIrp(Irp);\n\n    return Status;\n}\n\nstatic void add_trim_entry(device* dev, uint64_t address, uint64_t size) {\n    space* s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);\n    if (!s) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    s->address = address;\n    s->size = size;\n    dev->num_trim_entries++;\n\n    InsertTailList(&dev->trim_list, &s->list_entry);\n}\n\nstatic void clean_space_cache_chunk(device_extension* Vcb, chunk* c) {\n    LIST_ENTRY* le;\n    ULONG type;\n\n    if (c->chunk_item->type & BLOCK_FLAG_DUPLICATE)\n        type = BLOCK_FLAG_DUPLICATE;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID0)\n        type = BLOCK_FLAG_RAID0;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID1)\n        type = BLOCK_FLAG_DUPLICATE;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID10)\n        type = BLOCK_FLAG_RAID10;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID5)\n        type = BLOCK_FLAG_RAID5;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID6)\n        type = BLOCK_FLAG_RAID6;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID1C3)\n        type = BLOCK_FLAG_DUPLICATE;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID1C4)\n        type = BLOCK_FLAG_DUPLICATE;\n    else // SINGLE\n        type = BLOCK_FLAG_DUPLICATE;\n\n    le = c->deleting.Flink;\n    while (le != &c->deleting) {\n        space* s = CONTAINING_RECORD(le, space, list_entry);\n\n        if (!Vcb->options.no_barrier || !(c->chunk_item->type & BLOCK_FLAG_METADATA)) {\n            CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n\n            if (type == BLOCK_FLAG_DUPLICATE) {\n                uint16_t i;\n\n                for (i = 0; i < c->chunk_item->num_stripes; i++) {\n                    if (c->devices[i] && c->devices[i]->devobj && !c->devices[i]->readonly && c->devices[i]->trim)\n                        add_trim_entry(c->devices[i], s->address - c->offset + cis[i].offset, s->size);\n                }\n            } else if (type == BLOCK_FLAG_RAID0) {\n                uint64_t startoff, endoff;\n                uint16_t startoffstripe, endoffstripe, i;\n\n                get_raid0_offset(s->address - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &startoff, &startoffstripe);\n                get_raid0_offset(s->address - c->offset + s->size - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &endoff, &endoffstripe);\n\n                for (i = 0; i < c->chunk_item->num_stripes; i++) {\n                    if (c->devices[i] && c->devices[i]->devobj && !c->devices[i]->readonly && c->devices[i]->trim) {\n                        uint64_t stripestart, stripeend;\n\n                        if (startoffstripe > i)\n                            stripestart = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n                        else if (startoffstripe == i)\n                            stripestart = startoff;\n                        else\n                            stripestart = startoff - (startoff % c->chunk_item->stripe_length);\n\n                        if (endoffstripe > i)\n                            stripeend = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n                        else if (endoffstripe == i)\n                            stripeend = endoff + 1;\n                        else\n                            stripeend = endoff - (endoff % c->chunk_item->stripe_length);\n\n                        if (stripestart != stripeend)\n                            add_trim_entry(c->devices[i], stripestart + cis[i].offset, stripeend - stripestart);\n                    }\n                }\n            } else if (type == BLOCK_FLAG_RAID10) {\n                uint64_t startoff, endoff;\n                uint16_t sub_stripes, startoffstripe, endoffstripe, i;\n\n                sub_stripes = max(1, c->chunk_item->sub_stripes);\n\n                get_raid0_offset(s->address - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes / sub_stripes, &startoff, &startoffstripe);\n                get_raid0_offset(s->address - c->offset + s->size - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes / sub_stripes, &endoff, &endoffstripe);\n\n                startoffstripe *= sub_stripes;\n                endoffstripe *= sub_stripes;\n\n                for (i = 0; i < c->chunk_item->num_stripes; i += sub_stripes) {\n                    ULONG j;\n                    uint64_t stripestart, stripeend;\n\n                    if (startoffstripe > i)\n                        stripestart = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n                    else if (startoffstripe == i)\n                        stripestart = startoff;\n                    else\n                        stripestart = startoff - (startoff % c->chunk_item->stripe_length);\n\n                    if (endoffstripe > i)\n                        stripeend = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n                    else if (endoffstripe == i)\n                        stripeend = endoff + 1;\n                    else\n                        stripeend = endoff - (endoff % c->chunk_item->stripe_length);\n\n                    if (stripestart != stripeend) {\n                        for (j = 0; j < sub_stripes; j++) {\n                            if (c->devices[i+j] && c->devices[i+j]->devobj && !c->devices[i+j]->readonly && c->devices[i+j]->trim)\n                                add_trim_entry(c->devices[i+j], stripestart + cis[i+j].offset, stripeend - stripestart);\n                        }\n                    }\n                }\n            }\n            // FIXME - RAID5(?), RAID6(?)\n        }\n\n        le = le->Flink;\n    }\n}\n\ntypedef struct {\n    DEVICE_MANAGE_DATA_SET_ATTRIBUTES* dmdsa;\n    ATA_PASS_THROUGH_EX apte;\n    PIRP Irp;\n    IO_STATUS_BLOCK iosb;\n#ifdef DEBUG_TRIM_EMULATION\n    PMDL mdl;\n    void* buf;\n#endif\n} ioctl_context_stripe;\n\ntypedef struct {\n    KEVENT Event;\n    LONG left;\n    ioctl_context_stripe* stripes;\n} ioctl_context;\n\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall ioctl_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {\n    ioctl_context* context = (ioctl_context*)conptr;\n    LONG left2 = InterlockedDecrement(&context->left);\n\n    UNUSED(DeviceObject);\n    UNUSED(Irp);\n\n    if (left2 == 0)\n        KeSetEvent(&context->Event, 0, false);\n\n    return STATUS_MORE_PROCESSING_REQUIRED;\n}\n\n#ifdef DEBUG_TRIM_EMULATION\nstatic void trim_emulation(device* dev) {\n    LIST_ENTRY* le;\n    ioctl_context context;\n    unsigned int i = 0, count = 0;\n\n    le = dev->trim_list.Flink;\n    while (le != &dev->trim_list) {\n        count++;\n        le = le->Flink;\n    }\n\n    context.left = count;\n\n    KeInitializeEvent(&context.Event, NotificationEvent, false);\n\n    context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(ioctl_context_stripe) * context.left, ALLOC_TAG);\n    if (!context.stripes) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    RtlZeroMemory(context.stripes, sizeof(ioctl_context_stripe) * context.left);\n\n    i = 0;\n    le = dev->trim_list.Flink;\n    while (le != &dev->trim_list) {\n        ioctl_context_stripe* stripe = &context.stripes[i];\n        space* s = CONTAINING_RECORD(le, space, list_entry);\n\n        WARN(\"(%I64x, %I64x)\\n\", s->address, s->size);\n\n        stripe->Irp = IoAllocateIrp(dev->devobj->StackSize, false);\n\n        if (!stripe->Irp) {\n            ERR(\"IoAllocateIrp failed\\n\");\n        } else {\n            PIO_STACK_LOCATION IrpSp = IoGetNextIrpStackLocation(stripe->Irp);\n            IrpSp->MajorFunction = IRP_MJ_WRITE;\n            IrpSp->FileObject = dev->fileobj;\n\n            stripe->buf = ExAllocatePoolWithTag(NonPagedPool, (uint32_t)s->size, ALLOC_TAG);\n\n            if (!stripe->buf) {\n                ERR(\"out of memory\\n\");\n            } else {\n                RtlZeroMemory(stripe->buf, (uint32_t)s->size); // FIXME - randomize instead?\n\n                stripe->mdl = IoAllocateMdl(stripe->buf, (uint32_t)s->size, false, false, NULL);\n\n                if (!stripe->mdl) {\n                    ERR(\"IoAllocateMdl failed\\n\");\n                } else {\n                    MmBuildMdlForNonPagedPool(stripe->mdl);\n\n                    stripe->Irp->MdlAddress = stripe->mdl;\n\n                    IrpSp->Parameters.Write.ByteOffset.QuadPart = s->address;\n                    IrpSp->Parameters.Write.Length = s->size;\n\n                    stripe->Irp->UserIosb = &stripe->iosb;\n\n                    IoSetCompletionRoutine(stripe->Irp, ioctl_completion, &context, true, true, true);\n\n                    IoCallDriver(dev->devobj, stripe->Irp);\n                }\n            }\n        }\n\n        i++;\n\n        le = le->Flink;\n    }\n\n    KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n\n    for (i = 0; i < count; i++) {\n        ioctl_context_stripe* stripe = &context.stripes[i];\n\n        if (stripe->mdl)\n            IoFreeMdl(stripe->mdl);\n\n        if (stripe->buf)\n            ExFreePool(stripe->buf);\n    }\n\n    ExFreePool(context.stripes);\n}\n#endif\n\nstatic void clean_space_cache(device_extension* Vcb) {\n    LIST_ENTRY* le;\n    chunk* c;\n#ifndef DEBUG_TRIM_EMULATION\n    ULONG num;\n#endif\n\n    TRACE(\"(%p)\\n\", Vcb);\n\n    ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (c->space_changed) {\n            acquire_chunk_lock(c, Vcb);\n\n            if (c->space_changed) {\n                if (Vcb->trim && !Vcb->options.no_trim)\n                    clean_space_cache_chunk(Vcb, c);\n\n                space_list_merge(&c->space, &c->space_size, &c->deleting);\n\n                while (!IsListEmpty(&c->deleting)) {\n                    space* s = CONTAINING_RECORD(RemoveHeadList(&c->deleting), space, list_entry);\n\n                    ExFreePool(s);\n                }\n            }\n\n            c->space_changed = false;\n\n            release_chunk_lock(c, Vcb);\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&Vcb->chunk_lock);\n\n    if (Vcb->trim && !Vcb->options.no_trim) {\n#ifndef DEBUG_TRIM_EMULATION\n        ioctl_context context;\n        ULONG total_num;\n\n        context.left = 0;\n\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n            if (dev->devobj && !dev->readonly && dev->trim && dev->num_trim_entries > 0)\n                context.left++;\n\n            le = le->Flink;\n        }\n\n        if (context.left == 0)\n            return;\n\n        total_num = context.left;\n        num = 0;\n\n        KeInitializeEvent(&context.Event, NotificationEvent, false);\n\n        context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(ioctl_context_stripe) * context.left, ALLOC_TAG);\n        if (!context.stripes) {\n            ERR(\"out of memory\\n\");\n            return;\n        }\n\n        RtlZeroMemory(context.stripes, sizeof(ioctl_context_stripe) * context.left);\n#endif\n\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n            if (dev->devobj && !dev->readonly && dev->trim && dev->num_trim_entries > 0) {\n#ifdef DEBUG_TRIM_EMULATION\n                trim_emulation(dev);\n#else\n                LIST_ENTRY* le2;\n                ioctl_context_stripe* stripe = &context.stripes[num];\n                DEVICE_DATA_SET_RANGE* ranges;\n                ULONG datalen = (ULONG)sector_align(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), sizeof(uint64_t)) + (dev->num_trim_entries * sizeof(DEVICE_DATA_SET_RANGE)), i;\n                PIO_STACK_LOCATION IrpSp;\n\n                stripe->dmdsa = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG);\n                if (!stripe->dmdsa) {\n                    ERR(\"out of memory\\n\");\n                    goto nextdev;\n                }\n\n                stripe->dmdsa->Size = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES);\n                stripe->dmdsa->Action = DeviceDsmAction_Trim;\n                stripe->dmdsa->Flags = DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED;\n                stripe->dmdsa->ParameterBlockOffset = 0;\n                stripe->dmdsa->ParameterBlockLength = 0;\n                stripe->dmdsa->DataSetRangesOffset = (ULONG)sector_align(sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), sizeof(uint64_t));\n                stripe->dmdsa->DataSetRangesLength = dev->num_trim_entries * sizeof(DEVICE_DATA_SET_RANGE);\n\n                ranges = (DEVICE_DATA_SET_RANGE*)((uint8_t*)stripe->dmdsa + stripe->dmdsa->DataSetRangesOffset);\n\n                i = 0;\n\n                le2 = dev->trim_list.Flink;\n                while (le2 != &dev->trim_list) {\n                    space* s = CONTAINING_RECORD(le2, space, list_entry);\n\n                    ranges[i].StartingOffset = s->address;\n                    ranges[i].LengthInBytes = s->size;\n                    i++;\n\n                    le2 = le2->Flink;\n                }\n\n                stripe->Irp = IoAllocateIrp(dev->devobj->StackSize, false);\n\n                if (!stripe->Irp) {\n                    ERR(\"IoAllocateIrp failed\\n\");\n                    goto nextdev;\n                }\n\n                IrpSp = IoGetNextIrpStackLocation(stripe->Irp);\n                IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;\n                IrpSp->FileObject = dev->fileobj;\n\n                IrpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES;\n                IrpSp->Parameters.DeviceIoControl.InputBufferLength = datalen;\n                IrpSp->Parameters.DeviceIoControl.OutputBufferLength = 0;\n\n                stripe->Irp->AssociatedIrp.SystemBuffer = stripe->dmdsa;\n                stripe->Irp->Flags |= IRP_BUFFERED_IO;\n                stripe->Irp->UserBuffer = NULL;\n                stripe->Irp->UserIosb = &stripe->iosb;\n\n                IoSetCompletionRoutine(stripe->Irp, ioctl_completion, &context, true, true, true);\n\n                IoCallDriver(dev->devobj, stripe->Irp);\n\nnextdev:\n#endif\n                while (!IsListEmpty(&dev->trim_list)) {\n                    space* s = CONTAINING_RECORD(RemoveHeadList(&dev->trim_list), space, list_entry);\n                    ExFreePool(s);\n                }\n\n                dev->num_trim_entries = 0;\n\n#ifndef DEBUG_TRIM_EMULATION\n                num++;\n#endif\n            }\n\n            le = le->Flink;\n        }\n\n#ifndef DEBUG_TRIM_EMULATION\n        KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n\n        for (num = 0; num < total_num; num++) {\n            if (context.stripes[num].dmdsa)\n                ExFreePool(context.stripes[num].dmdsa);\n\n            if (context.stripes[num].Irp)\n                IoFreeIrp(context.stripes[num].Irp);\n        }\n\n        ExFreePool(context.stripes);\n#endif\n    }\n}\n\nstatic bool trees_consistent(device_extension* Vcb) {\n    ULONG maxsize = Vcb->superblock.node_size - sizeof(tree_header);\n    LIST_ENTRY* le;\n\n    le = Vcb->trees.Flink;\n    while (le != &Vcb->trees) {\n        tree* t = CONTAINING_RECORD(le, tree, list_entry);\n\n        if (t->write) {\n            if (t->header.num_items == 0 && t->parent) {\n#ifdef DEBUG_WRITE_LOOPS\n                ERR(\"empty tree found, looping again\\n\");\n#endif\n                return false;\n            }\n\n            if (t->size > maxsize) {\n#ifdef DEBUG_WRITE_LOOPS\n                ERR(\"overlarge tree found (%u > %u), looping again\\n\", t->size, maxsize);\n#endif\n                return false;\n            }\n\n            if (!t->has_new_address) {\n#ifdef DEBUG_WRITE_LOOPS\n                ERR(\"tree found without new address, looping again\\n\");\n#endif\n                return false;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    return true;\n}\n\nstatic NTSTATUS add_parents(device_extension* Vcb, PIRP Irp) {\n    ULONG level;\n    LIST_ENTRY* le;\n\n    for (level = 0; level <= 255; level++) {\n        bool nothing_found = true;\n\n        TRACE(\"level = %lu\\n\", level);\n\n        le = Vcb->trees.Flink;\n        while (le != &Vcb->trees) {\n            tree* t = CONTAINING_RECORD(le, tree, list_entry);\n\n            if (t->write && t->header.level == level) {\n                TRACE(\"tree %p: root = %I64x, level = %x, parent = %p\\n\", t, t->header.tree_id, t->header.level, t->parent);\n\n                nothing_found = false;\n\n                if (t->parent) {\n                    if (!t->parent->write)\n                        TRACE(\"adding tree %p (level %x)\\n\", t->parent, t->header.level);\n\n                    t->parent->write = true;\n                } else if (t->root != Vcb->root_root && t->root != Vcb->chunk_root) {\n                    KEY searchkey;\n                    traverse_ptr tp;\n                    NTSTATUS Status;\n\n                    searchkey.obj_id = t->root->id;\n                    searchkey.obj_type = TYPE_ROOT_ITEM;\n                    searchkey.offset = 0xffffffffffffffff;\n\n                    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"error - find_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n                        ERR(\"could not find ROOT_ITEM for tree %I64x\\n\", searchkey.obj_id);\n                        return STATUS_INTERNAL_ERROR;\n                    }\n\n                    if (tp.item->size < sizeof(ROOT_ITEM)) { // if not full length, delete and create new entry\n                        ROOT_ITEM* ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);\n\n                        if (!ri) {\n                            ERR(\"out of memory\\n\");\n                            return STATUS_INSUFFICIENT_RESOURCES;\n                        }\n\n                        RtlCopyMemory(ri, &t->root->root_item, sizeof(ROOT_ITEM));\n\n                        Status = delete_tree_item(Vcb, &tp);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                            ExFreePool(ri);\n                            return Status;\n                        }\n\n                        Status = insert_tree_item(Vcb, Vcb->root_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, ri, sizeof(ROOT_ITEM), NULL, Irp);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                            ExFreePool(ri);\n                            return Status;\n                        }\n                    }\n\n                    tree* t2 = tp.tree;\n                    while (t2) {\n                        t2->write = true;\n\n                        t2 = t2->parent;\n                    }\n                }\n            }\n\n            le = le->Flink;\n        }\n\n        if (nothing_found)\n            break;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic void add_parents_to_cache(tree* t) {\n    while (t->parent) {\n        t = t->parent;\n        t->write = true;\n    }\n}\n\nstatic bool insert_tree_extent_skinny(device_extension* Vcb, uint8_t level, uint64_t root_id, chunk* c, uint64_t address, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    EXTENT_ITEM_SKINNY_METADATA* eism;\n    traverse_ptr insert_tp;\n\n    eism = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM_SKINNY_METADATA), ALLOC_TAG);\n    if (!eism) {\n        ERR(\"out of memory\\n\");\n        return false;\n    }\n\n    eism->ei.refcount = 1;\n    eism->ei.generation = Vcb->superblock.generation;\n    eism->ei.flags = EXTENT_ITEM_TREE_BLOCK;\n    eism->type = TYPE_TREE_BLOCK_REF;\n    eism->tbr.offset = root_id;\n\n    Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, level, eism, sizeof(EXTENT_ITEM_SKINNY_METADATA), &insert_tp, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(eism);\n        return false;\n    }\n\n    acquire_chunk_lock(c, Vcb);\n\n    space_list_subtract(c, address, Vcb->superblock.node_size, rollback);\n\n    release_chunk_lock(c, Vcb);\n\n    add_parents_to_cache(insert_tp.tree);\n\n    return true;\n}\n\nbool find_metadata_address_in_chunk(device_extension* Vcb, chunk* c, uint64_t* address) {\n    LIST_ENTRY* le;\n    space* s;\n\n    TRACE(\"(%p, %I64x, %p)\\n\", Vcb, c->offset, address);\n\n    if (Vcb->superblock.node_size > c->chunk_item->size - c->used)\n        return false;\n\n    if (!c->cache_loaded) {\n        NTSTATUS Status = load_cache_chunk(Vcb, c, NULL);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"load_cache_chunk returned %08lx\\n\", Status);\n            return false;\n        }\n    }\n\n    if (IsListEmpty(&c->space_size))\n        return false;\n\n    if (!c->last_alloc_set) {\n        s = CONTAINING_RECORD(c->space.Blink, space, list_entry);\n\n        c->last_alloc = s->address;\n        c->last_alloc_set = true;\n\n        if (s->size >= Vcb->superblock.node_size) {\n            *address = s->address;\n            c->last_alloc += Vcb->superblock.node_size;\n            return true;\n        }\n    }\n\n    le = c->space.Flink;\n    while (le != &c->space) {\n        s = CONTAINING_RECORD(le, space, list_entry);\n\n        if (s->address <= c->last_alloc && s->address + s->size >= c->last_alloc + Vcb->superblock.node_size) {\n            *address = c->last_alloc;\n            c->last_alloc += Vcb->superblock.node_size;\n            return true;\n        }\n\n        le = le->Flink;\n    }\n\n    le = c->space_size.Flink;\n    while (le != &c->space_size) {\n        s = CONTAINING_RECORD(le, space, list_entry_size);\n\n        if (s->size == Vcb->superblock.node_size) {\n            *address = s->address;\n            c->last_alloc = s->address + Vcb->superblock.node_size;\n            return true;\n        } else if (s->size < Vcb->superblock.node_size) {\n            if (le == c->space_size.Flink)\n                return false;\n\n            s = CONTAINING_RECORD(le->Blink, space, list_entry_size);\n\n            *address = s->address;\n            c->last_alloc = s->address + Vcb->superblock.node_size;\n\n            return true;\n        }\n\n        le = le->Flink;\n    }\n\n    s = CONTAINING_RECORD(c->space_size.Blink, space, list_entry_size);\n\n    if (s->size > Vcb->superblock.node_size) {\n        *address = s->address;\n        c->last_alloc = s->address + Vcb->superblock.node_size;\n        return true;\n    }\n\n    return false;\n}\n\nstatic bool insert_tree_extent(device_extension* Vcb, uint8_t level, uint64_t root_id, chunk* c, uint64_t* new_address, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    uint64_t address;\n    EXTENT_ITEM_TREE2* eit2;\n    traverse_ptr insert_tp;\n\n    TRACE(\"(%p, %x, %I64x, %p, %p, %p, %p)\\n\", Vcb, level, root_id, c, new_address, Irp, rollback);\n\n    if (!find_metadata_address_in_chunk(Vcb, c, &address))\n        return false;\n\n    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) {\n        bool b = insert_tree_extent_skinny(Vcb, level, root_id, c, address, Irp, rollback);\n\n        if (b)\n            *new_address = address;\n\n        return b;\n    }\n\n    eit2 = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_ITEM_TREE2), ALLOC_TAG);\n    if (!eit2) {\n        ERR(\"out of memory\\n\");\n        return false;\n    }\n\n    eit2->eit.extent_item.refcount = 1;\n    eit2->eit.extent_item.generation = Vcb->superblock.generation;\n    eit2->eit.extent_item.flags = EXTENT_ITEM_TREE_BLOCK;\n    eit2->eit.level = level;\n    eit2->type = TYPE_TREE_BLOCK_REF;\n    eit2->tbr.offset = root_id;\n\n    Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_EXTENT_ITEM, Vcb->superblock.node_size, eit2, sizeof(EXTENT_ITEM_TREE2), &insert_tp, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(eit2);\n        return false;\n    }\n\n    acquire_chunk_lock(c, Vcb);\n\n    space_list_subtract(c, address, Vcb->superblock.node_size, rollback);\n\n    release_chunk_lock(c, Vcb);\n\n    add_parents_to_cache(insert_tp.tree);\n\n    *new_address = address;\n\n    return true;\n}\n\nNTSTATUS get_tree_new_address(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    chunk *origchunk = NULL, *c;\n    LIST_ENTRY* le;\n    uint64_t flags, addr;\n\n    if (t->root->id == BTRFS_ROOT_CHUNK)\n        flags = Vcb->system_flags;\n    else\n        flags = Vcb->metadata_flags;\n\n    if (t->has_address) {\n        origchunk = get_chunk_from_address(Vcb, t->header.address);\n\n        if (origchunk && !origchunk->readonly && !origchunk->reloc && origchunk->chunk_item->type == flags &&\n            insert_tree_extent(Vcb, t->header.level, t->root->id, origchunk, &addr, Irp, rollback)) {\n            t->new_address = addr;\n            t->has_new_address = true;\n            return STATUS_SUCCESS;\n        }\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true);\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (!c->readonly && !c->reloc) {\n            acquire_chunk_lock(c, Vcb);\n\n            if (c != origchunk && c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= Vcb->superblock.node_size) {\n                if (insert_tree_extent(Vcb, t->header.level, t->root->id, c, &addr, Irp, rollback)) {\n                    release_chunk_lock(c, Vcb);\n                    ExReleaseResourceLite(&Vcb->chunk_lock);\n                    t->new_address = addr;\n                    t->has_new_address = true;\n                    return STATUS_SUCCESS;\n                }\n            }\n\n            release_chunk_lock(c, Vcb);\n        }\n\n        le = le->Flink;\n    }\n\n    // allocate new chunk if necessary\n\n    Status = alloc_chunk(Vcb, flags, &c, false);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"alloc_chunk returned %08lx\\n\", Status);\n        ExReleaseResourceLite(&Vcb->chunk_lock);\n        return Status;\n    }\n\n    acquire_chunk_lock(c, Vcb);\n\n    if ((c->chunk_item->size - c->used) >= Vcb->superblock.node_size) {\n        if (insert_tree_extent(Vcb, t->header.level, t->root->id, c, &addr, Irp, rollback)) {\n            release_chunk_lock(c, Vcb);\n            ExReleaseResourceLite(&Vcb->chunk_lock);\n            t->new_address = addr;\n            t->has_new_address = true;\n            return STATUS_SUCCESS;\n        }\n    }\n\n    release_chunk_lock(c, Vcb);\n\n    ExReleaseResourceLite(&Vcb->chunk_lock);\n\n    ERR(\"couldn't find any metadata chunks with %x bytes free\\n\", Vcb->superblock.node_size);\n\n    return STATUS_DISK_FULL;\n}\n\nstatic NTSTATUS reduce_tree_extent(device_extension* Vcb, uint64_t address, tree* t, uint64_t parent_root, uint8_t level, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    uint64_t rc, root;\n\n    TRACE(\"(%p, %I64x, %p)\\n\", Vcb, address, t);\n\n    rc = get_extent_refcount(Vcb, address, Vcb->superblock.node_size, Irp);\n    if (rc == 0) {\n        ERR(\"error - refcount for extent %I64x was 0\\n\", address);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (!t || t->parent)\n        root = parent_root;\n    else\n        root = t->header.tree_id;\n\n    Status = decrease_extent_refcount_tree(Vcb, address, Vcb->superblock.node_size, root, level, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"decrease_extent_refcount_tree returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (rc == 1) {\n        chunk* c = get_chunk_from_address(Vcb, address);\n\n        if (c) {\n            acquire_chunk_lock(c, Vcb);\n\n            if (!c->cache_loaded) {\n                Status = load_cache_chunk(Vcb, c, NULL);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"load_cache_chunk returned %08lx\\n\", Status);\n                    release_chunk_lock(c, Vcb);\n                    return Status;\n                }\n            }\n\n            c->used -= Vcb->superblock.node_size;\n\n            space_list_add(c, address, Vcb->superblock.node_size, rollback);\n\n            release_chunk_lock(c, Vcb);\n        } else\n            ERR(\"could not find chunk for address %I64x\\n\", address);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS add_changed_extent_ref_edr(changed_extent* ce, EXTENT_DATA_REF* edr, bool old) {\n    LIST_ENTRY *le2, *list;\n    changed_extent_ref* cer;\n\n    list = old ? &ce->old_refs : &ce->refs;\n\n    le2 = list->Flink;\n    while (le2 != list) {\n        cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);\n\n        if (cer->type == TYPE_EXTENT_DATA_REF && cer->edr.root == edr->root && cer->edr.objid == edr->objid && cer->edr.offset == edr->offset) {\n            cer->edr.count += edr->count;\n            goto end;\n        }\n\n        le2 = le2->Flink;\n    }\n\n    cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG);\n    if (!cer) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    cer->type = TYPE_EXTENT_DATA_REF;\n    RtlCopyMemory(&cer->edr, edr, sizeof(EXTENT_DATA_REF));\n    InsertTailList(list, &cer->list_entry);\n\nend:\n    if (old)\n        ce->old_count += edr->count;\n    else\n        ce->count += edr->count;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS add_changed_extent_ref_sdr(changed_extent* ce, SHARED_DATA_REF* sdr, bool old) {\n    LIST_ENTRY *le2, *list;\n    changed_extent_ref* cer;\n\n    list = old ? &ce->old_refs : &ce->refs;\n\n    le2 = list->Flink;\n    while (le2 != list) {\n        cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);\n\n        if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == sdr->offset) {\n            cer->sdr.count += sdr->count;\n            goto end;\n        }\n\n        le2 = le2->Flink;\n    }\n\n    cer = ExAllocatePoolWithTag(PagedPool, sizeof(changed_extent_ref), ALLOC_TAG);\n    if (!cer) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    cer->type = TYPE_SHARED_DATA_REF;\n    RtlCopyMemory(&cer->sdr, sdr, sizeof(SHARED_DATA_REF));\n    InsertTailList(list, &cer->list_entry);\n\nend:\n    if (old)\n        ce->old_count += sdr->count;\n    else\n        ce->count += sdr->count;\n\n    return STATUS_SUCCESS;\n}\n\nstatic bool shared_tree_is_unique(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n\n    if (!t->updated_extents && t->has_address) {\n        Status = update_tree_extents(Vcb, t, Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"update_tree_extents returned %08lx\\n\", Status);\n            return false;\n        }\n    }\n\n    searchkey.obj_id = t->header.address;\n    searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return false;\n    }\n\n    if (tp.item->key.obj_id == t->header.address && (tp.item->key.obj_type == TYPE_METADATA_ITEM || tp.item->key.obj_type == TYPE_EXTENT_ITEM))\n        return false;\n    else\n        return true;\n}\n\nstatic NTSTATUS update_tree_extents(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    uint64_t rc = get_extent_refcount(Vcb, t->header.address, Vcb->superblock.node_size, Irp);\n    uint64_t flags = get_extent_flags(Vcb, t->header.address, Irp);\n\n    if (rc == 0) {\n        ERR(\"refcount for extent %I64x was 0\\n\", t->header.address);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (flags & EXTENT_ITEM_SHARED_BACKREFS || t->header.flags & HEADER_FLAG_SHARED_BACKREF || !(t->header.flags & HEADER_FLAG_MIXED_BACKREF)) {\n        TREE_BLOCK_REF tbr;\n        bool unique = rc > 1 ? false : (t->parent ? shared_tree_is_unique(Vcb, t->parent, Irp, rollback) : false);\n\n        if (t->header.level == 0) {\n            LIST_ENTRY* le;\n\n            le = t->itemlist.Flink;\n            while (le != &t->itemlist) {\n                tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);\n\n                if (!td->inserted && td->key.obj_type == TYPE_EXTENT_DATA && td->size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {\n                    EXTENT_DATA* ed = (EXTENT_DATA*)td->data;\n\n                    if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {\n                        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n\n                        if (ed2->size > 0) {\n                            EXTENT_DATA_REF edr;\n                            changed_extent* ce = NULL;\n                            chunk* c = get_chunk_from_address(Vcb, ed2->address);\n\n                            if (c) {\n                                LIST_ENTRY* le2;\n\n                                le2 = c->changed_extents.Flink;\n                                while (le2 != &c->changed_extents) {\n                                    changed_extent* ce2 = CONTAINING_RECORD(le2, changed_extent, list_entry);\n\n                                    if (ce2->address == ed2->address) {\n                                        ce = ce2;\n                                        break;\n                                    }\n\n                                    le2 = le2->Flink;\n                                }\n                            }\n\n                            edr.root = t->root->id;\n                            edr.objid = td->key.obj_id;\n                            edr.offset = td->key.offset - ed2->offset;\n                            edr.count = 1;\n\n                            if (ce) {\n                                Status = add_changed_extent_ref_edr(ce, &edr, true);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"add_changed_extent_ref_edr returned %08lx\\n\", Status);\n                                    return Status;\n                                }\n\n                                Status = add_changed_extent_ref_edr(ce, &edr, false);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"add_changed_extent_ref_edr returned %08lx\\n\", Status);\n                                    return Status;\n                                }\n                            }\n\n                            Status = increase_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, Irp);\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"increase_extent_refcount returned %08lx\\n\", Status);\n                                return Status;\n                            }\n\n                            if ((flags & EXTENT_ITEM_SHARED_BACKREFS && unique) || !(t->header.flags & HEADER_FLAG_MIXED_BACKREF)) {\n                                uint64_t sdrrc = find_extent_shared_data_refcount(Vcb, ed2->address, t->header.address, Irp);\n\n                                if (sdrrc > 0) {\n                                    SHARED_DATA_REF sdr;\n\n                                    sdr.offset = t->header.address;\n                                    sdr.count = 1;\n\n                                    Status = decrease_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_SHARED_DATA_REF, &sdr, NULL, 0,\n                                                                      t->header.address, ce ? ce->superseded : false, Irp);\n                                    if (!NT_SUCCESS(Status)) {\n                                        ERR(\"decrease_extent_refcount returned %08lx\\n\", Status);\n                                        return Status;\n                                    }\n\n                                    if (ce) {\n                                        LIST_ENTRY* le2;\n\n                                        le2 = ce->refs.Flink;\n                                        while (le2 != &ce->refs) {\n                                            changed_extent_ref* cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);\n\n                                            if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == sdr.offset) {\n                                                ce->count--;\n                                                cer->sdr.count--;\n                                                break;\n                                            }\n\n                                            le2 = le2->Flink;\n                                        }\n\n                                        le2 = ce->old_refs.Flink;\n                                        while (le2 != &ce->old_refs) {\n                                            changed_extent_ref* cer = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);\n\n                                            if (cer->type == TYPE_SHARED_DATA_REF && cer->sdr.offset == sdr.offset) {\n                                                ce->old_count--;\n\n                                                if (cer->sdr.count > 1)\n                                                    cer->sdr.count--;\n                                                else {\n                                                    RemoveEntryList(&cer->list_entry);\n                                                    ExFreePool(cer);\n                                                }\n\n                                                break;\n                                            }\n\n                                            le2 = le2->Flink;\n                                        }\n                                    }\n                                }\n                            }\n\n                            // FIXME - clear shared flag if unique?\n                        }\n                    }\n                }\n\n                le = le->Flink;\n            }\n        } else {\n            LIST_ENTRY* le;\n\n            le = t->itemlist.Flink;\n            while (le != &t->itemlist) {\n                tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);\n\n                if (!td->inserted) {\n                    tbr.offset = t->root->id;\n\n                    Status = increase_extent_refcount(Vcb, td->treeholder.address, Vcb->superblock.node_size, TYPE_TREE_BLOCK_REF,\n                                                      &tbr, &td->key, t->header.level - 1, Irp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"increase_extent_refcount returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    if (unique || !(t->header.flags & HEADER_FLAG_MIXED_BACKREF)) {\n                        uint64_t sbrrc = find_extent_shared_tree_refcount(Vcb, td->treeholder.address, t->header.address, Irp);\n\n                        if (sbrrc > 0) {\n                            SHARED_BLOCK_REF sbr;\n\n                            sbr.offset = t->header.address;\n\n                            Status = decrease_extent_refcount(Vcb, td->treeholder.address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, NULL, 0,\n                                                              t->header.address, false, Irp);\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"decrease_extent_refcount returned %08lx\\n\", Status);\n                                return Status;\n                            }\n                        }\n                    }\n\n                    // FIXME - clear shared flag if unique?\n                }\n\n                le = le->Flink;\n            }\n        }\n\n        if (unique) {\n            uint64_t sbrrc = find_extent_shared_tree_refcount(Vcb, t->header.address, t->parent->header.address, Irp);\n\n            if (sbrrc == 1) {\n                SHARED_BLOCK_REF sbr;\n\n                sbr.offset = t->parent->header.address;\n\n                Status = decrease_extent_refcount(Vcb, t->header.address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, NULL, 0,\n                                                  t->parent->header.address, false, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"decrease_extent_refcount returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n        }\n\n        if (t->parent)\n            tbr.offset = t->parent->header.tree_id;\n        else\n            tbr.offset = t->header.tree_id;\n\n        Status = increase_extent_refcount(Vcb, t->header.address, Vcb->superblock.node_size, TYPE_TREE_BLOCK_REF, &tbr,\n                                          t->parent ? &t->paritem->key : NULL, t->header.level, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"increase_extent_refcount returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        // FIXME - clear shared flag if unique?\n\n        t->header.flags &= ~HEADER_FLAG_SHARED_BACKREF;\n    }\n\n    if (rc > 1 || t->header.tree_id == t->root->id) {\n        Status = reduce_tree_extent(Vcb, t->header.address, t, t->parent ? t->parent->header.tree_id : t->header.tree_id, t->header.level, Irp, rollback);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"reduce_tree_extent returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    t->has_address = false;\n\n    if ((rc > 1 || t->header.tree_id != t->root->id) && !(flags & EXTENT_ITEM_SHARED_BACKREFS)) {\n        if (t->header.tree_id == t->root->id) {\n            flags |= EXTENT_ITEM_SHARED_BACKREFS;\n            update_extent_flags(Vcb, t->header.address, flags, Irp);\n        }\n\n        if (t->header.level > 0) {\n            LIST_ENTRY* le;\n\n            le = t->itemlist.Flink;\n            while (le != &t->itemlist) {\n                tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);\n\n                if (!td->inserted) {\n                    if (t->header.tree_id == t->root->id) {\n                        SHARED_BLOCK_REF sbr;\n\n                        sbr.offset = t->header.address;\n\n                        Status = increase_extent_refcount(Vcb, td->treeholder.address, Vcb->superblock.node_size, TYPE_SHARED_BLOCK_REF, &sbr, &td->key, t->header.level - 1, Irp);\n                    } else {\n                        TREE_BLOCK_REF tbr;\n\n                        tbr.offset = t->root->id;\n\n                        Status = increase_extent_refcount(Vcb, td->treeholder.address, Vcb->superblock.node_size, TYPE_TREE_BLOCK_REF, &tbr, &td->key, t->header.level - 1, Irp);\n                    }\n\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"increase_extent_refcount returned %08lx\\n\", Status);\n                        return Status;\n                    }\n                }\n\n                le = le->Flink;\n            }\n        } else {\n            LIST_ENTRY* le;\n\n            le = t->itemlist.Flink;\n            while (le != &t->itemlist) {\n                tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);\n\n                if (!td->inserted && td->key.obj_type == TYPE_EXTENT_DATA && td->size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {\n                    EXTENT_DATA* ed = (EXTENT_DATA*)td->data;\n\n                    if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {\n                        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n\n                        if (ed2->size > 0) {\n                            changed_extent* ce = NULL;\n                            chunk* c = get_chunk_from_address(Vcb, ed2->address);\n\n                            if (c) {\n                                LIST_ENTRY* le2;\n\n                                le2 = c->changed_extents.Flink;\n                                while (le2 != &c->changed_extents) {\n                                    changed_extent* ce2 = CONTAINING_RECORD(le2, changed_extent, list_entry);\n\n                                    if (ce2->address == ed2->address) {\n                                        ce = ce2;\n                                        break;\n                                    }\n\n                                    le2 = le2->Flink;\n                                }\n                            }\n\n                            if (t->header.tree_id == t->root->id) {\n                                SHARED_DATA_REF sdr;\n\n                                sdr.offset = t->header.address;\n                                sdr.count = 1;\n\n                                if (ce) {\n                                    Status = add_changed_extent_ref_sdr(ce, &sdr, true);\n                                    if (!NT_SUCCESS(Status)) {\n                                        ERR(\"add_changed_extent_ref_edr returned %08lx\\n\", Status);\n                                        return Status;\n                                    }\n\n                                    Status = add_changed_extent_ref_sdr(ce, &sdr, false);\n                                    if (!NT_SUCCESS(Status)) {\n                                        ERR(\"add_changed_extent_ref_edr returned %08lx\\n\", Status);\n                                        return Status;\n                                    }\n                                }\n\n                                Status = increase_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_SHARED_DATA_REF, &sdr, NULL, 0, Irp);\n                            } else {\n                                EXTENT_DATA_REF edr;\n\n                                edr.root = t->root->id;\n                                edr.objid = td->key.obj_id;\n                                edr.offset = td->key.offset - ed2->offset;\n                                edr.count = 1;\n\n                                if (ce) {\n                                    Status = add_changed_extent_ref_edr(ce, &edr, true);\n                                    if (!NT_SUCCESS(Status)) {\n                                        ERR(\"add_changed_extent_ref_edr returned %08lx\\n\", Status);\n                                        return Status;\n                                    }\n\n                                    Status = add_changed_extent_ref_edr(ce, &edr, false);\n                                    if (!NT_SUCCESS(Status)) {\n                                        ERR(\"add_changed_extent_ref_edr returned %08lx\\n\", Status);\n                                        return Status;\n                                    }\n                                }\n\n                                Status = increase_extent_refcount(Vcb, ed2->address, ed2->size, TYPE_EXTENT_DATA_REF, &edr, NULL, 0, Irp);\n                            }\n\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"increase_extent_refcount returned %08lx\\n\", Status);\n                                return Status;\n                            }\n                        }\n                    }\n                }\n\n                le = le->Flink;\n            }\n        }\n    }\n\n    t->updated_extents = true;\n    t->header.tree_id = t->root->id;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS allocate_tree_extents(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) {\n    LIST_ENTRY* le;\n    NTSTATUS Status;\n    bool changed = false;\n    uint8_t max_level = 0, level;\n\n    TRACE(\"(%p)\\n\", Vcb);\n\n    le = Vcb->trees.Flink;\n    while (le != &Vcb->trees) {\n        tree* t = CONTAINING_RECORD(le, tree, list_entry);\n\n        if (t->write && !t->has_new_address) {\n            chunk* c;\n\n            if (t->has_address) {\n                c = get_chunk_from_address(Vcb, t->header.address);\n\n                if (c) {\n                    if (!c->cache_loaded) {\n                        acquire_chunk_lock(c, Vcb);\n\n                        if (!c->cache_loaded) {\n                            Status = load_cache_chunk(Vcb, c, NULL);\n\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"load_cache_chunk returned %08lx\\n\", Status);\n                                release_chunk_lock(c, Vcb);\n                                return Status;\n                            }\n                        }\n\n                        release_chunk_lock(c, Vcb);\n                    }\n                }\n            }\n\n            Status = get_tree_new_address(Vcb, t, Irp, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"get_tree_new_address returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            TRACE(\"allocated extent %I64x\\n\", t->new_address);\n\n            c = get_chunk_from_address(Vcb, t->new_address);\n\n            if (c)\n                c->used += Vcb->superblock.node_size;\n            else {\n                ERR(\"could not find chunk for address %I64x\\n\", t->new_address);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            changed = true;\n\n            if (t->header.level > max_level)\n                max_level = t->header.level;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!changed)\n        return STATUS_SUCCESS;\n\n    level = max_level;\n    do {\n        le = Vcb->trees.Flink;\n        while (le != &Vcb->trees) {\n            tree* t = CONTAINING_RECORD(le, tree, list_entry);\n\n            if (t->write && !t->updated_extents && t->has_address && t->header.level == level) {\n                Status = update_tree_extents(Vcb, t, Irp, rollback);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"update_tree_extents returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n\n            le = le->Flink;\n        }\n\n        if (level == 0)\n            break;\n\n        level--;\n    } while (true);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS update_root_root(device_extension* Vcb, bool no_cache, PIRP Irp, LIST_ENTRY* rollback) {\n    LIST_ENTRY* le;\n    NTSTATUS Status;\n\n    TRACE(\"(%p)\\n\", Vcb);\n\n    le = Vcb->trees.Flink;\n    while (le != &Vcb->trees) {\n        tree* t = CONTAINING_RECORD(le, tree, list_entry);\n\n        if (t->write && !t->parent) {\n            if (t->root != Vcb->root_root && t->root != Vcb->chunk_root) {\n                KEY searchkey;\n                traverse_ptr tp;\n\n                searchkey.obj_id = t->root->id;\n                searchkey.obj_type = TYPE_ROOT_ITEM;\n                searchkey.offset = 0xffffffffffffffff;\n\n                Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"error - find_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n                    ERR(\"could not find ROOT_ITEM for tree %I64x\\n\", searchkey.obj_id);\n                    return STATUS_INTERNAL_ERROR;\n                }\n\n                TRACE(\"updating the address for root %I64x to %I64x\\n\", searchkey.obj_id, t->new_address);\n\n                t->root->root_item.block_number = t->new_address;\n                t->root->root_item.root_level = t->header.level;\n                t->root->root_item.generation = Vcb->superblock.generation;\n                t->root->root_item.generation2 = Vcb->superblock.generation;\n\n                // item is guaranteed to be at least sizeof(ROOT_ITEM), due to add_parents\n\n                RtlCopyMemory(tp.item->data, &t->root->root_item, sizeof(ROOT_ITEM));\n            }\n\n            t->root->treeholder.address = t->new_address;\n            t->root->treeholder.generation = Vcb->superblock.generation;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!no_cache && !(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE)) {\n        ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);\n        Status = update_chunk_caches(Vcb, Irp, rollback);\n        ExReleaseResourceLite(&Vcb->chunk_lock);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"update_chunk_caches returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS do_tree_writes(device_extension* Vcb, LIST_ENTRY* tree_writes, bool no_free) {\n    chunk* c;\n    LIST_ENTRY* le;\n    tree_write* tw;\n    NTSTATUS Status;\n    ULONG i, num_bits;\n    write_data_context* wtc;\n    ULONG bit_num = 0;\n    bool raid56 = false;\n\n    // merge together runs\n    c = NULL;\n    le = tree_writes->Flink;\n    while (le != tree_writes) {\n        tw = CONTAINING_RECORD(le, tree_write, list_entry);\n\n        if (!c || tw->address < c->offset || tw->address >= c->offset + c->chunk_item->size)\n            c = get_chunk_from_address(Vcb, tw->address);\n        else {\n            tree_write* tw2 = CONTAINING_RECORD(le->Blink, tree_write, list_entry);\n\n            if (tw->address == tw2->address + tw2->length) {\n                uint8_t* data = ExAllocatePoolWithTag(NonPagedPool, tw2->length + tw->length, ALLOC_TAG);\n\n                if (!data) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(data, tw2->data, tw2->length);\n                RtlCopyMemory(&data[tw2->length], tw->data, tw->length);\n\n                if (!no_free || tw2->allocated)\n                    ExFreePool(tw2->data);\n\n                tw2->data = data;\n                tw2->length += tw->length;\n                tw2->allocated = true;\n\n                if (!no_free || tw->allocated)\n                    ExFreePool(tw->data);\n\n                RemoveEntryList(&tw->list_entry);\n                ExFreePool(tw);\n\n                le = tw2->list_entry.Flink;\n                continue;\n            }\n        }\n\n        tw->c = c;\n\n        if (c->chunk_item->type & (BLOCK_FLAG_RAID5 | BLOCK_FLAG_RAID6))\n            raid56 = true;\n\n        le = le->Flink;\n    }\n\n    num_bits = 0;\n\n    le = tree_writes->Flink;\n    while (le != tree_writes) {\n        tw = CONTAINING_RECORD(le, tree_write, list_entry);\n\n        num_bits++;\n\n        le = le->Flink;\n    }\n\n    wtc = ExAllocatePoolWithTag(NonPagedPool, sizeof(write_data_context) * num_bits, ALLOC_TAG);\n    if (!wtc) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    le = tree_writes->Flink;\n\n    while (le != tree_writes) {\n        tw = CONTAINING_RECORD(le, tree_write, list_entry);\n\n        TRACE(\"address: %I64x, size: %x\\n\", tw->address, tw->length);\n\n        KeInitializeEvent(&wtc[bit_num].Event, NotificationEvent, false);\n        InitializeListHead(&wtc[bit_num].stripes);\n        wtc[bit_num].need_wait = false;\n        wtc[bit_num].stripes_left = 0;\n        wtc[bit_num].parity1 = wtc[bit_num].parity2 = wtc[bit_num].scratch = NULL;\n        wtc[bit_num].mdl = wtc[bit_num].parity1_mdl = wtc[bit_num].parity2_mdl = NULL;\n\n        Status = write_data(Vcb, tw->address, tw->data, tw->length, &wtc[bit_num], NULL, NULL, false, 0, HighPagePriority);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"write_data returned %08lx\\n\", Status);\n\n            for (i = 0; i < num_bits; i++) {\n                free_write_data_stripes(&wtc[i]);\n            }\n            ExFreePool(wtc);\n\n            return Status;\n        }\n\n        bit_num++;\n\n        le = le->Flink;\n    }\n\n    for (i = 0; i < num_bits; i++) {\n        if (wtc[i].stripes.Flink != &wtc[i].stripes) {\n            // launch writes and wait\n            le = wtc[i].stripes.Flink;\n            while (le != &wtc[i].stripes) {\n                write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry);\n\n                if (stripe->status != WriteDataStatus_Ignore) {\n                    wtc[i].need_wait = true;\n                    IoCallDriver(stripe->device->devobj, stripe->Irp);\n                }\n\n                le = le->Flink;\n            }\n        }\n    }\n\n    for (i = 0; i < num_bits; i++) {\n        if (wtc[i].need_wait)\n            KeWaitForSingleObject(&wtc[i].Event, Executive, KernelMode, false, NULL);\n    }\n\n    for (i = 0; i < num_bits; i++) {\n        le = wtc[i].stripes.Flink;\n        while (le != &wtc[i].stripes) {\n            write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry);\n\n            if (stripe->status != WriteDataStatus_Ignore && !NT_SUCCESS(stripe->iosb.Status)) {\n                Status = stripe->iosb.Status;\n                log_device_error(Vcb, stripe->device, BTRFS_DEV_STAT_WRITE_ERRORS);\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        free_write_data_stripes(&wtc[i]);\n    }\n\n    ExFreePool(wtc);\n\n    if (raid56) {\n        c = NULL;\n\n        le = tree_writes->Flink;\n        while (le != tree_writes) {\n            tw = CONTAINING_RECORD(le, tree_write, list_entry);\n\n            if (tw->c != c) {\n                c = tw->c;\n\n                ExAcquireResourceExclusiveLite(&c->partial_stripes_lock, true);\n\n                while (!IsListEmpty(&c->partial_stripes)) {\n                    partial_stripe* ps = CONTAINING_RECORD(RemoveHeadList(&c->partial_stripes), partial_stripe, list_entry);\n\n                    Status = flush_partial_stripe(Vcb, c, ps);\n\n                    if (ps->bmparr)\n                        ExFreePool(ps->bmparr);\n\n                    ExFreePool(ps);\n\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"flush_partial_stripe returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&c->partial_stripes_lock);\n                        return Status;\n                    }\n                }\n\n                ExReleaseResourceLite(&c->partial_stripes_lock);\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nvoid calc_tree_checksum(device_extension* Vcb, tree_header* th) {\n    switch (Vcb->superblock.csum_type) {\n        case CSUM_TYPE_CRC32C:\n            *((uint32_t*)th) = ~calc_crc32c(0xffffffff, (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));\n        break;\n\n        case CSUM_TYPE_XXHASH:\n            *((uint64_t*)th) = XXH64((uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum), 0);\n        break;\n\n        case CSUM_TYPE_SHA256:\n            calc_sha256((uint8_t*)th, &th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));\n        break;\n\n        case CSUM_TYPE_BLAKE2:\n            blake2b((uint8_t*)th, BLAKE2_HASH_SIZE, &th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));\n        break;\n    }\n}\n\nstatic NTSTATUS write_trees(device_extension* Vcb, PIRP Irp) {\n    ULONG level;\n    uint8_t *data, *body;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    LIST_ENTRY tree_writes;\n    tree_write* tw;\n\n    TRACE(\"(%p)\\n\", Vcb);\n\n    InitializeListHead(&tree_writes);\n\n    for (level = 0; level <= 255; level++) {\n        bool nothing_found = true;\n\n        TRACE(\"level = %lu\\n\", level);\n\n        le = Vcb->trees.Flink;\n        while (le != &Vcb->trees) {\n            tree* t = CONTAINING_RECORD(le, tree, list_entry);\n\n            if (t->write && t->header.level == level) {\n                KEY firstitem, searchkey;\n                LIST_ENTRY* le2;\n                traverse_ptr tp;\n\n                if (!t->has_new_address) {\n                    ERR(\"error - tried to write tree with no new address\\n\");\n                    return STATUS_INTERNAL_ERROR;\n                }\n\n                le2 = t->itemlist.Flink;\n                while (le2 != &t->itemlist) {\n                    tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);\n                    if (!td->ignore) {\n                        firstitem = td->key;\n                        break;\n                    }\n                    le2 = le2->Flink;\n                }\n\n                if (t->parent) {\n                    t->paritem->key = firstitem;\n                    t->paritem->treeholder.address = t->new_address;\n                    t->paritem->treeholder.generation = Vcb->superblock.generation;\n                }\n\n                if (!(Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA)) {\n                    EXTENT_ITEM_TREE* eit;\n\n                    searchkey.obj_id = t->new_address;\n                    searchkey.obj_type = TYPE_EXTENT_ITEM;\n                    searchkey.offset = Vcb->superblock.node_size;\n\n                    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"error - find_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    if (keycmp(searchkey, tp.item->key)) {\n                        ERR(\"could not find %I64x,%x,%I64x in extent_root (found %I64x,%x,%I64x instead)\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n                        return STATUS_INTERNAL_ERROR;\n                    }\n\n                    if (tp.item->size < sizeof(EXTENT_ITEM_TREE)) {\n                        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM_TREE));\n                        return STATUS_INTERNAL_ERROR;\n                    }\n\n                    eit = (EXTENT_ITEM_TREE*)tp.item->data;\n                    eit->firstitem = firstitem;\n                }\n\n                nothing_found = false;\n            }\n\n            le = le->Flink;\n        }\n\n        if (nothing_found)\n            break;\n    }\n\n    TRACE(\"allocated tree extents\\n\");\n\n    le = Vcb->trees.Flink;\n    while (le != &Vcb->trees) {\n        tree* t = CONTAINING_RECORD(le, tree, list_entry);\n        LIST_ENTRY* le2;\n#ifdef DEBUG_PARANOID\n        uint32_t num_items = 0, size = 0;\n        bool crash = false;\n#endif\n\n        if (t->write) {\n#ifdef DEBUG_PARANOID\n            bool first = true;\n            KEY lastkey;\n\n            le2 = t->itemlist.Flink;\n            while (le2 != &t->itemlist) {\n                tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);\n                if (!td->ignore) {\n                    num_items++;\n\n                    if (!first) {\n                        if (keycmp(td->key, lastkey) == 0) {\n                            ERR(\"(%I64x,%x,%I64x): duplicate key\\n\", td->key.obj_id, td->key.obj_type, td->key.offset);\n                            crash = true;\n                        } else if (keycmp(td->key, lastkey) == -1) {\n                            ERR(\"(%I64x,%x,%I64x): key out of order\\n\", td->key.obj_id, td->key.obj_type, td->key.offset);\n                            crash = true;\n                        }\n                    } else\n                        first = false;\n\n                    lastkey = td->key;\n\n                    if (t->header.level == 0)\n                        size += td->size;\n                }\n                le2 = le2->Flink;\n            }\n\n            if (t->header.level == 0)\n                size += num_items * sizeof(leaf_node);\n            else\n                size += num_items * sizeof(internal_node);\n\n            if (num_items != t->header.num_items) {\n                ERR(\"tree %I64x, level %x: num_items was %x, expected %x\\n\", t->root->id, t->header.level, num_items, t->header.num_items);\n                crash = true;\n            }\n\n            if (size != t->size) {\n                ERR(\"tree %I64x, level %x: size was %x, expected %x\\n\", t->root->id, t->header.level, size, t->size);\n                crash = true;\n            }\n\n            if (t->header.num_items == 0 && t->parent) {\n                ERR(\"tree %I64x, level %x: tried to write empty tree with parent\\n\", t->root->id, t->header.level);\n                crash = true;\n            }\n\n            if (t->size > Vcb->superblock.node_size - sizeof(tree_header)) {\n                ERR(\"tree %I64x, level %x: tried to write overlarge tree (%x > %Ix)\\n\", t->root->id, t->header.level, t->size, Vcb->superblock.node_size - sizeof(tree_header));\n                crash = true;\n            }\n\n            if (crash) {\n                ERR(\"tree %p\\n\", t);\n                le2 = t->itemlist.Flink;\n                while (le2 != &t->itemlist) {\n                    tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);\n                    if (!td->ignore) {\n                        ERR(\"%I64x,%x,%I64x inserted=%u\\n\", td->key.obj_id, td->key.obj_type, td->key.offset, td->inserted);\n                    }\n                    le2 = le2->Flink;\n                }\n                int3;\n            }\n#endif\n            t->header.address = t->new_address;\n            t->header.generation = Vcb->superblock.generation;\n            t->header.tree_id = t->root->id;\n            t->header.flags |= HEADER_FLAG_MIXED_BACKREF;\n            t->header.fs_uuid = Vcb->superblock.metadata_uuid;\n            t->has_address = true;\n\n            data = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG);\n            if (!data) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            body = data + sizeof(tree_header);\n\n            RtlCopyMemory(data, &t->header, sizeof(tree_header));\n            RtlZeroMemory(body, Vcb->superblock.node_size - sizeof(tree_header));\n\n            if (t->header.level == 0) {\n                leaf_node* itemptr = (leaf_node*)body;\n                int i = 0;\n                uint8_t* dataptr = data + Vcb->superblock.node_size;\n\n                le2 = t->itemlist.Flink;\n                while (le2 != &t->itemlist) {\n                    tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);\n                    if (!td->ignore) {\n                        dataptr = dataptr - td->size;\n\n                        itemptr[i].key = td->key;\n                        itemptr[i].offset = (uint32_t)((uint8_t*)dataptr - (uint8_t*)body);\n                        itemptr[i].size = td->size;\n                        i++;\n\n                        if (td->size > 0)\n                            RtlCopyMemory(dataptr, td->data, td->size);\n                    }\n\n                    le2 = le2->Flink;\n                }\n            } else {\n                internal_node* itemptr = (internal_node*)body;\n                int i = 0;\n\n                le2 = t->itemlist.Flink;\n                while (le2 != &t->itemlist) {\n                    tree_data* td = CONTAINING_RECORD(le2, tree_data, list_entry);\n                    if (!td->ignore) {\n                        itemptr[i].key = td->key;\n                        itemptr[i].address = td->treeholder.address;\n                        itemptr[i].generation = td->treeholder.generation;\n                        i++;\n                    }\n\n                    le2 = le2->Flink;\n                }\n            }\n\n            calc_tree_checksum(Vcb, (tree_header*)data);\n\n            tw = ExAllocatePoolWithTag(PagedPool, sizeof(tree_write), ALLOC_TAG);\n            if (!tw) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(data);\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            tw->address = t->new_address;\n            tw->length = Vcb->superblock.node_size;\n            tw->data = data;\n            tw->allocated = false;\n\n            if (IsListEmpty(&tree_writes))\n                InsertTailList(&tree_writes, &tw->list_entry);\n            else {\n                bool inserted = false;\n\n                le2 = tree_writes.Flink;\n                while (le2 != &tree_writes) {\n                    tree_write* tw2 = CONTAINING_RECORD(le2, tree_write, list_entry);\n\n                    if (tw2->address > tw->address) {\n                        InsertHeadList(le2->Blink, &tw->list_entry);\n                        inserted = true;\n                        break;\n                    }\n\n                    le2 = le2->Flink;\n                }\n\n                if (!inserted)\n                    InsertTailList(&tree_writes, &tw->list_entry);\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    Status = do_tree_writes(Vcb, &tree_writes, false);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"do_tree_writes returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    while (!IsListEmpty(&tree_writes)) {\n        le = RemoveHeadList(&tree_writes);\n        tw = CONTAINING_RECORD(le, tree_write, list_entry);\n\n        if (tw->data)\n            ExFreePool(tw->data);\n\n        ExFreePool(tw);\n    }\n\n    return Status;\n}\n\nstatic void update_backup_superblock(device_extension* Vcb, superblock_backup* sb, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n\n    RtlZeroMemory(sb, sizeof(superblock_backup));\n\n    sb->root_tree_addr = Vcb->superblock.root_tree_addr;\n    sb->root_tree_generation = Vcb->superblock.generation;\n    sb->root_level = Vcb->superblock.root_level;\n\n    sb->chunk_tree_addr = Vcb->superblock.chunk_tree_addr;\n    sb->chunk_tree_generation = Vcb->superblock.chunk_root_generation;\n    sb->chunk_root_level = Vcb->superblock.chunk_root_level;\n\n    searchkey.obj_id = BTRFS_ROOT_EXTENT;\n    searchkey.obj_type = TYPE_ROOT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp))) {\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) {\n            ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;\n\n            sb->extent_tree_addr = ri->block_number;\n            sb->extent_tree_generation = ri->generation;\n            sb->extent_root_level = ri->root_level;\n        }\n    }\n\n    searchkey.obj_id = BTRFS_ROOT_FSTREE;\n\n    if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp))) {\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) {\n            ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;\n\n            sb->fs_tree_addr = ri->block_number;\n            sb->fs_tree_generation = ri->generation;\n            sb->fs_root_level = ri->root_level;\n        }\n    }\n\n    searchkey.obj_id = BTRFS_ROOT_DEVTREE;\n\n    if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp))) {\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) {\n            ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;\n\n            sb->dev_root_addr = ri->block_number;\n            sb->dev_root_generation = ri->generation;\n            sb->dev_root_level = ri->root_level;\n        }\n    }\n\n    searchkey.obj_id = BTRFS_ROOT_CHECKSUM;\n\n    if (NT_SUCCESS(find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp))) {\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type && tp.item->size >= sizeof(ROOT_ITEM)) {\n            ROOT_ITEM* ri = (ROOT_ITEM*)tp.item->data;\n\n            sb->csum_root_addr = ri->block_number;\n            sb->csum_root_generation = ri->generation;\n            sb->csum_root_level = ri->root_level;\n        }\n    }\n\n    sb->total_bytes = Vcb->superblock.total_bytes;\n    sb->bytes_used = Vcb->superblock.bytes_used;\n    sb->num_devices = Vcb->superblock.num_devices;\n}\n\ntypedef struct {\n    void* context;\n    uint8_t* buf;\n    PMDL mdl;\n    device* device;\n    NTSTATUS Status;\n    PIRP Irp;\n    LIST_ENTRY list_entry;\n} write_superblocks_stripe;\n\ntypedef struct _write_superblocks_context {\n    KEVENT Event;\n    LIST_ENTRY stripes;\n    LONG left;\n} write_superblocks_context;\n\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall write_superblock_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {\n    write_superblocks_stripe* stripe = conptr;\n    write_superblocks_context* context = stripe->context;\n\n    UNUSED(DeviceObject);\n\n    stripe->Status = Irp->IoStatus.Status;\n\n    if (InterlockedDecrement(&context->left) == 0)\n        KeSetEvent(&context->Event, 0, false);\n\n    return STATUS_MORE_PROCESSING_REQUIRED;\n}\n\nstatic void calc_superblock_checksum(superblock* sb) {\n    switch (sb->csum_type) {\n        case CSUM_TYPE_CRC32C:\n            *(uint32_t*)sb = ~calc_crc32c(0xffffffff, (uint8_t*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum));\n        break;\n\n        case CSUM_TYPE_XXHASH:\n            *(uint64_t*)sb = XXH64(&sb->uuid, sizeof(superblock) - sizeof(sb->checksum), 0);\n        break;\n\n        case CSUM_TYPE_SHA256:\n            calc_sha256((uint8_t*)sb, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum));\n        break;\n\n        case CSUM_TYPE_BLAKE2:\n            blake2b((uint8_t*)sb, BLAKE2_HASH_SIZE, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum));\n        break;\n    }\n}\n\nstatic NTSTATUS write_superblock(device_extension* Vcb, device* device, write_superblocks_context* context) {\n    unsigned int i = 0;\n\n    // All the documentation says that the Linux driver only writes one superblock\n    // if it thinks a disk is an SSD, but this doesn't seem to be the case!\n\n    while (superblock_addrs[i] > 0 && device->devitem.num_bytes >= superblock_addrs[i] + sizeof(superblock)) {\n        ULONG sblen = (ULONG)sector_align(sizeof(superblock), Vcb->superblock.sector_size);\n        superblock* sb;\n        write_superblocks_stripe* stripe;\n        PIO_STACK_LOCATION IrpSp;\n\n        sb = ExAllocatePoolWithTag(NonPagedPool, sblen, ALLOC_TAG);\n        if (!sb) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(sb, &Vcb->superblock, sizeof(superblock));\n\n        if (sblen > sizeof(superblock))\n            RtlZeroMemory((uint8_t*)sb + sizeof(superblock), sblen - sizeof(superblock));\n\n        RtlCopyMemory(&sb->dev_item, &device->devitem, sizeof(DEV_ITEM));\n        sb->sb_phys_addr = superblock_addrs[i];\n\n        calc_superblock_checksum(sb);\n\n        stripe = ExAllocatePoolWithTag(NonPagedPool, sizeof(write_superblocks_stripe), ALLOC_TAG);\n        if (!stripe) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(sb);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        stripe->buf = (uint8_t*)sb;\n\n        stripe->Irp = IoAllocateIrp(device->devobj->StackSize, false);\n        if (!stripe->Irp) {\n            ERR(\"IoAllocateIrp failed\\n\");\n            ExFreePool(stripe);\n            ExFreePool(sb);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        IrpSp = IoGetNextIrpStackLocation(stripe->Irp);\n        IrpSp->MajorFunction = IRP_MJ_WRITE;\n        IrpSp->FileObject = device->fileobj;\n\n        if (i == 0)\n            IrpSp->Flags |= SL_WRITE_THROUGH;\n\n        if (device->devobj->Flags & DO_BUFFERED_IO) {\n            stripe->Irp->AssociatedIrp.SystemBuffer = sb;\n            stripe->mdl = NULL;\n\n            stripe->Irp->Flags = IRP_BUFFERED_IO;\n        } else if (device->devobj->Flags & DO_DIRECT_IO) {\n            stripe->mdl = IoAllocateMdl(sb, sblen, false, false, NULL);\n            if (!stripe->mdl) {\n                ERR(\"IoAllocateMdl failed\\n\");\n                IoFreeIrp(stripe->Irp);\n                ExFreePool(stripe);\n                ExFreePool(sb);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            stripe->Irp->MdlAddress = stripe->mdl;\n\n            MmBuildMdlForNonPagedPool(stripe->mdl);\n        } else {\n            stripe->Irp->UserBuffer = sb;\n            stripe->mdl = NULL;\n        }\n\n        IrpSp->Parameters.Write.Length = sblen;\n        IrpSp->Parameters.Write.ByteOffset.QuadPart = superblock_addrs[i];\n\n        IoSetCompletionRoutine(stripe->Irp, write_superblock_completion, stripe, true, true, true);\n\n        stripe->context = context;\n        stripe->device = device;\n        InsertTailList(&context->stripes, &stripe->list_entry);\n\n        context->left++;\n\n        i++;\n    }\n\n    if (i == 0)\n        ERR(\"no superblocks written!\\n\");\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS write_superblocks(device_extension* Vcb, PIRP Irp) {\n    uint64_t i;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    write_superblocks_context context;\n\n    TRACE(\"(%p)\\n\", Vcb);\n\n    le = Vcb->trees.Flink;\n    while (le != &Vcb->trees) {\n        tree* t = CONTAINING_RECORD(le, tree, list_entry);\n\n        if (t->write && !t->parent) {\n            if (t->root == Vcb->root_root) {\n                Vcb->superblock.root_tree_addr = t->new_address;\n                Vcb->superblock.root_level = t->header.level;\n            } else if (t->root == Vcb->chunk_root) {\n                Vcb->superblock.chunk_tree_addr = t->new_address;\n                Vcb->superblock.chunk_root_generation = t->header.generation;\n                Vcb->superblock.chunk_root_level = t->header.level;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    for (i = 0; i < BTRFS_NUM_BACKUP_ROOTS - 1; i++) {\n        RtlCopyMemory(&Vcb->superblock.backup[i], &Vcb->superblock.backup[i+1], sizeof(superblock_backup));\n    }\n\n    update_backup_superblock(Vcb, &Vcb->superblock.backup[BTRFS_NUM_BACKUP_ROOTS - 1], Irp);\n\n    KeInitializeEvent(&context.Event, NotificationEvent, false);\n    InitializeListHead(&context.stripes);\n    context.left = 0;\n\n    le = Vcb->devices.Flink;\n    while (le != &Vcb->devices) {\n        device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n        if (dev->devobj && !dev->readonly) {\n            Status = write_superblock(Vcb, dev, &context);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"write_superblock returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    if (IsListEmpty(&context.stripes)) {\n        ERR(\"error - not writing any superblocks\\n\");\n        Status = STATUS_INTERNAL_ERROR;\n        goto end;\n    }\n\n    le = context.stripes.Flink;\n    while (le != &context.stripes) {\n        write_superblocks_stripe* stripe = CONTAINING_RECORD(le, write_superblocks_stripe, list_entry);\n\n        IoCallDriver(stripe->device->devobj, stripe->Irp);\n\n        le = le->Flink;\n    }\n\n    KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n\n    le = context.stripes.Flink;\n    while (le != &context.stripes) {\n        write_superblocks_stripe* stripe = CONTAINING_RECORD(le, write_superblocks_stripe, list_entry);\n\n        if (!NT_SUCCESS(stripe->Status)) {\n            ERR(\"device %I64x returned %08lx\\n\", stripe->device->devitem.dev_id, stripe->Status);\n            log_device_error(Vcb, stripe->device, BTRFS_DEV_STAT_WRITE_ERRORS);\n            Status = stripe->Status;\n            goto end;\n        }\n\n        le = le->Flink;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    while (!IsListEmpty(&context.stripes)) {\n        write_superblocks_stripe* stripe = CONTAINING_RECORD(RemoveHeadList(&context.stripes), write_superblocks_stripe, list_entry);\n\n        if (stripe->mdl) {\n            if (stripe->mdl->MdlFlags & MDL_PAGES_LOCKED)\n                MmUnlockPages(stripe->mdl);\n\n            IoFreeMdl(stripe->mdl);\n        }\n\n        if (stripe->Irp)\n            IoFreeIrp(stripe->Irp);\n\n        if (stripe->buf)\n            ExFreePool(stripe->buf);\n\n        ExFreePool(stripe);\n    }\n\n    return Status;\n}\n\nstatic NTSTATUS flush_changed_extent(device_extension* Vcb, chunk* c, changed_extent* ce, PIRP Irp, LIST_ENTRY* rollback) {\n    LIST_ENTRY *le, *le2;\n    NTSTATUS Status;\n    uint64_t old_size;\n\n    if (ce->count == 0 && ce->old_count == 0) {\n        while (!IsListEmpty(&ce->refs)) {\n            changed_extent_ref* cer = CONTAINING_RECORD(RemoveHeadList(&ce->refs), changed_extent_ref, list_entry);\n            ExFreePool(cer);\n        }\n\n        while (!IsListEmpty(&ce->old_refs)) {\n            changed_extent_ref* cer = CONTAINING_RECORD(RemoveHeadList(&ce->old_refs), changed_extent_ref, list_entry);\n            ExFreePool(cer);\n        }\n\n        goto end;\n    }\n\n    le = ce->refs.Flink;\n    while (le != &ce->refs) {\n        changed_extent_ref* cer = CONTAINING_RECORD(le, changed_extent_ref, list_entry);\n        uint32_t old_count = 0;\n\n        if (cer->type == TYPE_EXTENT_DATA_REF) {\n            le2 = ce->old_refs.Flink;\n            while (le2 != &ce->old_refs) {\n                changed_extent_ref* cer2 = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);\n\n                if (cer2->type == TYPE_EXTENT_DATA_REF && cer2->edr.root == cer->edr.root && cer2->edr.objid == cer->edr.objid && cer2->edr.offset == cer->edr.offset) {\n                    old_count = cer2->edr.count;\n                    break;\n                }\n\n                le2 = le2->Flink;\n            }\n\n            old_size = ce->old_count > 0 ? ce->old_size : ce->size;\n\n            if (cer->edr.count > old_count) {\n                Status = increase_extent_refcount_data(Vcb, ce->address, old_size, cer->edr.root, cer->edr.objid, cer->edr.offset, cer->edr.count - old_count, Irp);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"increase_extent_refcount_data returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n        } else if (cer->type == TYPE_SHARED_DATA_REF) {\n            le2 = ce->old_refs.Flink;\n            while (le2 != &ce->old_refs) {\n                changed_extent_ref* cer2 = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);\n\n                if (cer2->type == TYPE_SHARED_DATA_REF && cer2->sdr.offset == cer->sdr.offset) {\n                    RemoveEntryList(&cer2->list_entry);\n                    ExFreePool(cer2);\n                    break;\n                }\n\n                le2 = le2->Flink;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    le = ce->refs.Flink;\n    while (le != &ce->refs) {\n        changed_extent_ref* cer = CONTAINING_RECORD(le, changed_extent_ref, list_entry);\n        LIST_ENTRY* le3 = le->Flink;\n        uint32_t old_count = 0;\n\n        if (cer->type == TYPE_EXTENT_DATA_REF) {\n            le2 = ce->old_refs.Flink;\n            while (le2 != &ce->old_refs) {\n                changed_extent_ref* cer2 = CONTAINING_RECORD(le2, changed_extent_ref, list_entry);\n\n                if (cer2->type == TYPE_EXTENT_DATA_REF && cer2->edr.root == cer->edr.root && cer2->edr.objid == cer->edr.objid && cer2->edr.offset == cer->edr.offset) {\n                    old_count = cer2->edr.count;\n\n                    RemoveEntryList(&cer2->list_entry);\n                    ExFreePool(cer2);\n                    break;\n                }\n\n                le2 = le2->Flink;\n            }\n\n            old_size = ce->old_count > 0 ? ce->old_size : ce->size;\n\n            if (cer->edr.count < old_count) {\n                Status = decrease_extent_refcount_data(Vcb, ce->address, old_size, cer->edr.root, cer->edr.objid, cer->edr.offset,\n                                                       old_count - cer->edr.count, ce->superseded, Irp);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"decrease_extent_refcount_data returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n\n            if (ce->size != ce->old_size && ce->old_count > 0) {\n                KEY searchkey;\n                traverse_ptr tp;\n                void* data;\n\n                searchkey.obj_id = ce->address;\n                searchkey.obj_type = TYPE_EXTENT_ITEM;\n                searchkey.offset = ce->old_size;\n\n                Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"error - find_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                if (keycmp(searchkey, tp.item->key)) {\n                    ERR(\"could not find (%I64x,%x,%I64x) in extent tree\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n                    return STATUS_INTERNAL_ERROR;\n                }\n\n                if (tp.item->size > 0) {\n                    data = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);\n\n                    if (!data) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    RtlCopyMemory(data, tp.item->data, tp.item->size);\n                } else\n                    data = NULL;\n\n                Status = insert_tree_item(Vcb, Vcb->extent_root, ce->address, TYPE_EXTENT_ITEM, ce->size, data, tp.item->size, NULL, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                    if (data) ExFreePool(data);\n                    return Status;\n                }\n\n                Status = delete_tree_item(Vcb, &tp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n        }\n\n        RemoveEntryList(&cer->list_entry);\n        ExFreePool(cer);\n\n        le = le3;\n    }\n\n#ifdef DEBUG_PARANOID\n    if (!IsListEmpty(&ce->old_refs))\n        WARN(\"old_refs not empty\\n\");\n#endif\n\nend:\n    if (ce->count == 0 && !ce->superseded) {\n        c->used -= ce->size;\n        space_list_add(c, ce->address, ce->size, rollback);\n    }\n\n    RemoveEntryList(&ce->list_entry);\n    ExFreePool(ce);\n\n    return STATUS_SUCCESS;\n}\n\nvoid add_checksum_entry(device_extension* Vcb, uint64_t address, ULONG length, void* csum, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp, next_tp;\n    NTSTATUS Status;\n    uint64_t startaddr, endaddr;\n    ULONG len;\n    RTL_BITMAP bmp;\n    ULONG* bmparr;\n    ULONG runlength, index;\n\n    TRACE(\"(%p, %I64x, %lx, %p, %p)\\n\", Vcb, address, length, csum, Irp);\n\n    searchkey.obj_id = EXTENT_CSUM_ID;\n    searchkey.obj_type = TYPE_EXTENT_CSUM;\n    searchkey.offset = address;\n\n    // FIXME - create checksum_root if it doesn't exist at all\n\n    Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, Irp);\n    if (Status == STATUS_NOT_FOUND) { // tree is completely empty\n        if (csum) { // not deleted\n            ULONG length2 = length;\n            uint64_t off = address;\n            void* data = csum;\n\n            do {\n                uint16_t il = (uint16_t)min(length2, MAX_CSUM_SIZE / Vcb->csum_size);\n\n                void* checksums = ExAllocatePoolWithTag(PagedPool, il * Vcb->csum_size, ALLOC_TAG);\n                if (!checksums) {\n                    ERR(\"out of memory\\n\");\n                    return;\n                }\n\n                RtlCopyMemory(checksums, data, il * Vcb->csum_size);\n\n                Status = insert_tree_item(Vcb, Vcb->checksum_root, EXTENT_CSUM_ID, TYPE_EXTENT_CSUM, off, checksums,\n                                          il * Vcb->csum_size, NULL, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                    ExFreePool(checksums);\n                    return;\n                }\n\n                length2 -= il;\n\n                if (length2 > 0) {\n                    off += (uint64_t)il << Vcb->sector_shift;\n                    data = (uint8_t*)data + (il * Vcb->csum_size);\n                }\n            } while (length2 > 0);\n        }\n    } else if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return;\n    } else {\n        uint32_t tplen;\n        void* checksums;\n\n        // FIXME - check entry is TYPE_EXTENT_CSUM?\n\n        if (tp.item->key.offset < address && tp.item->key.offset + (((uint64_t)tp.item->size << Vcb->sector_shift) / Vcb->csum_size) >= address)\n            startaddr = tp.item->key.offset;\n        else\n            startaddr = address;\n\n        searchkey.obj_id = EXTENT_CSUM_ID;\n        searchkey.obj_type = TYPE_EXTENT_CSUM;\n        searchkey.offset = address + (length << Vcb->sector_shift);\n\n        Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            return;\n        }\n\n        tplen = tp.item->size / Vcb->csum_size;\n\n        if (tp.item->key.offset + (tplen << Vcb->sector_shift) >= address + (length << Vcb->sector_shift))\n            endaddr = tp.item->key.offset + (tplen << Vcb->sector_shift);\n        else\n            endaddr = address + (length << Vcb->sector_shift);\n\n        TRACE(\"cs starts at %I64x (%lx sectors)\\n\", address, length);\n        TRACE(\"startaddr = %I64x\\n\", startaddr);\n        TRACE(\"endaddr = %I64x\\n\", endaddr);\n\n        len = (ULONG)((endaddr - startaddr) >> Vcb->sector_shift);\n\n        checksums = ExAllocatePoolWithTag(PagedPool, Vcb->csum_size * len, ALLOC_TAG);\n        if (!checksums) {\n            ERR(\"out of memory\\n\");\n            return;\n        }\n\n        bmparr = ExAllocatePoolWithTag(PagedPool, sizeof(ULONG) * ((len/8)+1), ALLOC_TAG);\n        if (!bmparr) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(checksums);\n            return;\n        }\n\n        RtlInitializeBitMap(&bmp, bmparr, len);\n        RtlSetAllBits(&bmp);\n\n        searchkey.obj_id = EXTENT_CSUM_ID;\n        searchkey.obj_type = TYPE_EXTENT_CSUM;\n        searchkey.offset = address;\n\n        Status = find_item(Vcb, Vcb->checksum_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            ExFreePool(checksums);\n            ExFreePool(bmparr);\n            return;\n        }\n\n        // set bit = free space, cleared bit = allocated sector\n\n        while (tp.item->key.offset < endaddr) {\n            if (tp.item->key.offset >= startaddr) {\n                if (tp.item->size > 0) {\n                    ULONG itemlen = (ULONG)min((len - ((tp.item->key.offset - startaddr) >> Vcb->sector_shift)) * Vcb->csum_size, tp.item->size);\n\n                    RtlCopyMemory((uint8_t*)checksums + (((tp.item->key.offset - startaddr) * Vcb->csum_size) >> Vcb->sector_shift),\n                                  tp.item->data, itemlen);\n                    RtlClearBits(&bmp, (ULONG)((tp.item->key.offset - startaddr) >> Vcb->sector_shift), itemlen / Vcb->csum_size);\n                }\n\n                Status = delete_tree_item(Vcb, &tp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                    ExFreePool(checksums);\n                    ExFreePool(bmparr);\n                    return;\n                }\n            }\n\n            if (find_next_item(Vcb, &tp, &next_tp, false, Irp)) {\n                tp = next_tp;\n            } else\n                break;\n        }\n\n        if (!csum) { // deleted\n            RtlSetBits(&bmp, (ULONG)((address - startaddr) >> Vcb->sector_shift), length);\n        } else {\n            RtlCopyMemory((uint8_t*)checksums + (((address - startaddr) * Vcb->csum_size) >> Vcb->sector_shift),\n                          csum, length * Vcb->csum_size);\n            RtlClearBits(&bmp, (ULONG)((address - startaddr) >> Vcb->sector_shift), length);\n        }\n\n        runlength = RtlFindFirstRunClear(&bmp, &index);\n\n        while (runlength != 0) {\n            if (index >= len)\n                break;\n\n            if (index + runlength >= len) {\n                runlength = len - index;\n\n                if (runlength == 0)\n                    break;\n            }\n\n            do {\n                uint16_t rl;\n                uint64_t off;\n                void* data;\n\n                if (runlength * Vcb->csum_size > MAX_CSUM_SIZE)\n                    rl = (uint16_t)(MAX_CSUM_SIZE / Vcb->csum_size);\n                else\n                    rl = (uint16_t)runlength;\n\n                data = ExAllocatePoolWithTag(PagedPool, Vcb->csum_size * rl, ALLOC_TAG);\n                if (!data) {\n                    ERR(\"out of memory\\n\");\n                    ExFreePool(bmparr);\n                    ExFreePool(checksums);\n                    return;\n                }\n\n                RtlCopyMemory(data, (uint8_t*)checksums + (Vcb->csum_size * index), Vcb->csum_size * rl);\n\n                off = startaddr + ((uint64_t)index << Vcb->sector_shift);\n\n                Status = insert_tree_item(Vcb, Vcb->checksum_root, EXTENT_CSUM_ID, TYPE_EXTENT_CSUM, off, data, Vcb->csum_size * rl, NULL, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                    ExFreePool(data);\n                    ExFreePool(bmparr);\n                    ExFreePool(checksums);\n                    return;\n                }\n\n                runlength -= rl;\n                index += rl;\n            } while (runlength > 0);\n\n            runlength = RtlFindNextForwardRunClear(&bmp, index, &index);\n        }\n\n        ExFreePool(bmparr);\n        ExFreePool(checksums);\n    }\n}\n\nstatic NTSTATUS update_chunk_usage(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) {\n    LIST_ENTRY *le = Vcb->chunks.Flink, *le2;\n    chunk* c;\n    KEY searchkey;\n    traverse_ptr tp;\n    BLOCK_GROUP_ITEM* bgi;\n    NTSTATUS Status;\n\n    TRACE(\"(%p)\\n\", Vcb);\n\n    ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);\n\n    while (le != &Vcb->chunks) {\n        c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        acquire_chunk_lock(c, Vcb);\n\n        if (!c->cache_loaded && (!IsListEmpty(&c->changed_extents) || c->used != c->oldused)) {\n            Status = load_cache_chunk(Vcb, c, NULL);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"load_cache_chunk returned %08lx\\n\", Status);\n                release_chunk_lock(c, Vcb);\n                goto end;\n            }\n        }\n\n        le2 = c->changed_extents.Flink;\n        while (le2 != &c->changed_extents) {\n            LIST_ENTRY* le3 = le2->Flink;\n            changed_extent* ce = CONTAINING_RECORD(le2, changed_extent, list_entry);\n\n            Status = flush_changed_extent(Vcb, c, ce, Irp, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"flush_changed_extent returned %08lx\\n\", Status);\n                release_chunk_lock(c, Vcb);\n                goto end;\n            }\n\n            le2 = le3;\n        }\n\n        // This is usually done by update_chunks, but we have to check again in case any new chunks\n        // have been allocated since.\n        if (c->created) {\n            Status = create_chunk(Vcb, c, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"create_chunk returned %08lx\\n\", Status);\n                release_chunk_lock(c, Vcb);\n                goto end;\n            }\n        }\n\n        if (c->old_cache) {\n            if (c->old_cache->dirty) {\n                LIST_ENTRY batchlist;\n\n                InitializeListHead(&batchlist);\n\n                Status = flush_fcb(c->old_cache, false, &batchlist, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"flush_fcb returned %08lx\\n\", Status);\n                    release_chunk_lock(c, Vcb);\n                    clear_batch_list(Vcb, &batchlist);\n                    goto end;\n                }\n\n                Status = commit_batch_list(Vcb, &batchlist, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"commit_batch_list returned %08lx\\n\", Status);\n                    release_chunk_lock(c, Vcb);\n                    goto end;\n                }\n            }\n\n            free_fcb(c->old_cache);\n\n            if (c->old_cache->refcount == 0)\n                reap_fcb(c->old_cache);\n\n            c->old_cache = NULL;\n        }\n\n        if (c->used != c->oldused) {\n            root* r = Vcb->block_group_root ? Vcb->block_group_root : Vcb->extent_root;\n\n            searchkey.obj_id = c->offset;\n            searchkey.obj_type = TYPE_BLOCK_GROUP_ITEM;\n            searchkey.offset = c->chunk_item->size;\n\n            Status = find_item(Vcb, r, &tp, &searchkey, false, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"error - find_item returned %08lx\\n\", Status);\n                release_chunk_lock(c, Vcb);\n                goto end;\n            }\n\n            if (keycmp(searchkey, tp.item->key)) {\n                ERR(\"could not find (%I64x;%I64x,%x,%I64x)\\n\", r->id, searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n                Status = STATUS_INTERNAL_ERROR;\n                release_chunk_lock(c, Vcb);\n                goto end;\n            }\n\n            if (tp.item->size < sizeof(BLOCK_GROUP_ITEM)) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(BLOCK_GROUP_ITEM));\n                Status = STATUS_INTERNAL_ERROR;\n                release_chunk_lock(c, Vcb);\n                goto end;\n            }\n\n            bgi = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);\n            if (!bgi) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                release_chunk_lock(c, Vcb);\n                goto end;\n            }\n\n            RtlCopyMemory(bgi, tp.item->data, tp.item->size);\n            bgi->used = c->used;\n\n#ifdef DEBUG_PARANOID\n            if (bgi->used & 0x8000000000000000) {\n                ERR(\"refusing to write BLOCK_GROUP_ITEM with negative usage value (%I64x)\\n\", bgi->used);\n                int3;\n            }\n#endif\n\n            TRACE(\"adjusting usage of chunk %I64x to %I64x\\n\", c->offset, c->used);\n\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                ExFreePool(bgi);\n                release_chunk_lock(c, Vcb);\n                goto end;\n            }\n\n            Status = insert_tree_item(Vcb, r, searchkey.obj_id, searchkey.obj_type, searchkey.offset, bgi, tp.item->size, NULL, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                ExFreePool(bgi);\n                release_chunk_lock(c, Vcb);\n                goto end;\n            }\n\n            Vcb->superblock.bytes_used += c->used - c->oldused;\n            c->oldused = c->used;\n        }\n\n        release_chunk_lock(c, Vcb);\n\n        le = le->Flink;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&Vcb->chunk_lock);\n\n    return Status;\n}\n\nstatic void get_first_item(tree* t, KEY* key) {\n    LIST_ENTRY* le;\n\n    le = t->itemlist.Flink;\n    while (le != &t->itemlist) {\n        tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);\n\n        *key = td->key;\n        return;\n    }\n}\n\nstatic NTSTATUS split_tree_at(device_extension* Vcb, tree* t, tree_data* newfirstitem, uint32_t numitems, uint32_t size) {\n    tree *nt, *pt;\n    tree_data* td;\n    tree_data* oldlastitem;\n\n    TRACE(\"splitting tree in %I64x at (%I64x,%x,%I64x)\\n\", t->root->id, newfirstitem->key.obj_id, newfirstitem->key.obj_type, newfirstitem->key.offset);\n\n    nt = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG);\n    if (!nt) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    if (t->header.level > 0) {\n        nt->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(tree_nonpaged), ALLOC_TAG);\n        if (!nt->nonpaged) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(nt);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        ExInitializeFastMutex(&nt->nonpaged->mutex);\n    } else\n        nt->nonpaged = NULL;\n\n    RtlCopyMemory(&nt->header, &t->header, sizeof(tree_header));\n    nt->header.address = 0;\n    nt->header.generation = Vcb->superblock.generation;\n    nt->header.num_items = t->header.num_items - numitems;\n    nt->header.flags = HEADER_FLAG_MIXED_BACKREF | HEADER_FLAG_WRITTEN;\n\n    nt->has_address = false;\n    nt->Vcb = Vcb;\n    nt->parent = t->parent;\n\n#ifdef DEBUG_PARANOID\n    if (nt->parent && nt->parent->header.level <= nt->header.level) int3;\n#endif\n\n    nt->root = t->root;\n    nt->new_address = 0;\n    nt->has_new_address = false;\n    nt->updated_extents = false;\n    nt->uniqueness_determined = true;\n    nt->is_unique = true;\n    nt->list_entry_hash.Flink = NULL;\n    nt->buf = NULL;\n    InitializeListHead(&nt->itemlist);\n\n    oldlastitem = CONTAINING_RECORD(newfirstitem->list_entry.Blink, tree_data, list_entry);\n\n    nt->itemlist.Flink = &newfirstitem->list_entry;\n    nt->itemlist.Blink = t->itemlist.Blink;\n    nt->itemlist.Flink->Blink = &nt->itemlist;\n    nt->itemlist.Blink->Flink = &nt->itemlist;\n\n    t->itemlist.Blink = &oldlastitem->list_entry;\n    t->itemlist.Blink->Flink = &t->itemlist;\n\n    nt->size = t->size - size;\n    t->size = size;\n    t->header.num_items = numitems;\n    nt->write = true;\n\n    InsertTailList(&Vcb->trees, &nt->list_entry);\n\n    if (nt->header.level > 0) {\n        LIST_ENTRY* le = nt->itemlist.Flink;\n\n        while (le != &nt->itemlist) {\n            tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry);\n\n            if (td2->treeholder.tree) {\n                td2->treeholder.tree->parent = nt;\n#ifdef DEBUG_PARANOID\n                if (td2->treeholder.tree->parent && td2->treeholder.tree->parent->header.level <= td2->treeholder.tree->header.level) int3;\n#endif\n            }\n\n            le = le->Flink;\n        }\n    } else {\n        LIST_ENTRY* le = nt->itemlist.Flink;\n\n        while (le != &nt->itemlist) {\n            tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry);\n\n            if (!td2->inserted && td2->data) {\n                uint8_t* data = ExAllocatePoolWithTag(PagedPool, td2->size, ALLOC_TAG);\n\n                if (!data) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(data, td2->data, td2->size);\n                td2->data = data;\n                td2->inserted = true;\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    if (nt->parent) {\n        td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);\n        if (!td) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        td->key = newfirstitem->key;\n\n        InsertHeadList(&t->paritem->list_entry, &td->list_entry);\n\n        td->ignore = false;\n        td->inserted = true;\n        td->treeholder.tree = nt;\n        nt->paritem = td;\n\n        nt->parent->header.num_items++;\n        nt->parent->size += sizeof(internal_node);\n\n        goto end;\n    }\n\n    TRACE(\"adding new tree parent\\n\");\n\n    if (nt->header.level == 255) {\n        ERR(\"cannot add parent to tree at level 255\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    pt = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG);\n    if (!pt) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    pt->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(tree_nonpaged), ALLOC_TAG);\n    if (!pt->nonpaged) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(pt);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    ExInitializeFastMutex(&pt->nonpaged->mutex);\n\n    RtlCopyMemory(&pt->header, &nt->header, sizeof(tree_header));\n    pt->header.address = 0;\n    pt->header.num_items = 2;\n    pt->header.level = nt->header.level + 1;\n    pt->header.flags = HEADER_FLAG_MIXED_BACKREF | HEADER_FLAG_WRITTEN;\n\n    pt->has_address = false;\n    pt->Vcb = Vcb;\n    pt->parent = NULL;\n    pt->paritem = NULL;\n    pt->root = t->root;\n    pt->new_address = 0;\n    pt->has_new_address = false;\n    pt->updated_extents = false;\n    pt->size = pt->header.num_items * sizeof(internal_node);\n    pt->uniqueness_determined = true;\n    pt->is_unique = true;\n    pt->list_entry_hash.Flink = NULL;\n    pt->buf = NULL;\n    InitializeListHead(&pt->itemlist);\n\n    InsertTailList(&Vcb->trees, &pt->list_entry);\n\n    td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);\n    if (!td) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    get_first_item(t, &td->key);\n    td->ignore = false;\n    td->inserted = false;\n    td->treeholder.address = 0;\n    td->treeholder.generation = Vcb->superblock.generation;\n    td->treeholder.tree = t;\n    InsertTailList(&pt->itemlist, &td->list_entry);\n    t->paritem = td;\n\n    td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);\n    if (!td) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    td->key = newfirstitem->key;\n    td->ignore = false;\n    td->inserted = false;\n    td->treeholder.address = 0;\n    td->treeholder.generation = Vcb->superblock.generation;\n    td->treeholder.tree = nt;\n    InsertTailList(&pt->itemlist, &td->list_entry);\n    nt->paritem = td;\n\n    pt->write = true;\n\n    t->root->treeholder.tree = pt;\n\n    t->parent = pt;\n    nt->parent = pt;\n\n#ifdef DEBUG_PARANOID\n    if (t->parent && t->parent->header.level <= t->header.level) int3;\n    if (nt->parent && nt->parent->header.level <= nt->header.level) int3;\n#endif\n\nend:\n    t->root->root_item.bytes_used += Vcb->superblock.node_size;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS split_tree(device_extension* Vcb, tree* t) {\n    LIST_ENTRY* le;\n    uint32_t size, ds, numitems;\n\n    size = 0;\n    numitems = 0;\n\n    // FIXME - naïve implementation: maximizes number of filled trees\n\n    le = t->itemlist.Flink;\n    while (le != &t->itemlist) {\n        tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);\n\n        if (!td->ignore) {\n            if (t->header.level == 0)\n                ds = sizeof(leaf_node) + td->size;\n            else\n                ds = sizeof(internal_node);\n\n            if (numitems == 0 && ds > Vcb->superblock.node_size - sizeof(tree_header)) {\n                ERR(\"(%I64x,%x,%I64x) in tree %I64x is too large (%x > %Ix)\\n\",\n                    td->key.obj_id, td->key.obj_type, td->key.offset, t->root->id,\n                    ds, Vcb->superblock.node_size - sizeof(tree_header));\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            // FIXME - move back if previous item was deleted item with same key\n            if (size + ds > Vcb->superblock.node_size - sizeof(tree_header))\n                return split_tree_at(Vcb, t, td, numitems, size);\n\n            size += ds;\n            numitems++;\n        }\n\n        le = le->Flink;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nbool is_tree_unique(device_extension* Vcb, tree* t, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n    bool ret = false;\n    EXTENT_ITEM* ei;\n    uint8_t* type;\n\n    if (t->uniqueness_determined)\n        return t->is_unique;\n\n    if (t->parent && !is_tree_unique(Vcb, t->parent, Irp))\n        goto end;\n\n    if (t->has_address) {\n        searchkey.obj_id = t->header.address;\n        searchkey.obj_type = Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA ? TYPE_METADATA_ITEM : TYPE_EXTENT_ITEM;\n        searchkey.offset = 0xffffffffffffffff;\n\n        Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        if (tp.item->key.obj_id != t->header.address || (tp.item->key.obj_type != TYPE_METADATA_ITEM && tp.item->key.obj_type != TYPE_EXTENT_ITEM))\n            goto end;\n\n        if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->size == sizeof(EXTENT_ITEM_V0))\n            goto end;\n\n        if (tp.item->size < sizeof(EXTENT_ITEM))\n            goto end;\n\n        ei = (EXTENT_ITEM*)tp.item->data;\n\n        if (ei->refcount > 1)\n            goto end;\n\n        if (tp.item->key.obj_type == TYPE_EXTENT_ITEM && ei->flags & EXTENT_ITEM_TREE_BLOCK) {\n            EXTENT_ITEM2* ei2;\n\n            if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2))\n                goto end;\n\n            ei2 = (EXTENT_ITEM2*)&ei[1];\n            type = (uint8_t*)&ei2[1];\n        } else\n            type = (uint8_t*)&ei[1];\n\n        if (type >= tp.item->data + tp.item->size || *type != TYPE_TREE_BLOCK_REF)\n            goto end;\n    }\n\n    ret = true;\n\nend:\n    t->is_unique = ret;\n    t->uniqueness_determined = true;\n\n    return ret;\n}\n\nstatic NTSTATUS try_tree_amalgamate(device_extension* Vcb, tree* t, bool* done, bool* done_deletions, PIRP Irp, LIST_ENTRY* rollback) {\n    LIST_ENTRY* le;\n    tree_data* nextparitem = NULL;\n    NTSTATUS Status;\n    tree *next_tree, *par;\n\n    *done = false;\n\n    TRACE(\"trying to amalgamate tree in root %I64x, level %x (size %u)\\n\", t->root->id, t->header.level, t->size);\n\n    // FIXME - doesn't capture everything, as it doesn't ascend\n    le = t->paritem->list_entry.Flink;\n    while (le != &t->parent->itemlist) {\n        tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);\n\n        if (!td->ignore) {\n            nextparitem = td;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!nextparitem)\n        return STATUS_SUCCESS;\n\n    TRACE(\"nextparitem: key = %I64x,%x,%I64x\\n\", nextparitem->key.obj_id, nextparitem->key.obj_type, nextparitem->key.offset);\n\n    if (!nextparitem->treeholder.tree) {\n        Status = do_load_tree(Vcb, &nextparitem->treeholder, t->root, t->parent, nextparitem, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"do_load_tree returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    if (!is_tree_unique(Vcb, nextparitem->treeholder.tree, Irp))\n        return STATUS_SUCCESS;\n\n    next_tree = nextparitem->treeholder.tree;\n\n    if (!next_tree->updated_extents && next_tree->has_address) {\n        Status = update_tree_extents(Vcb, next_tree, Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"update_tree_extents returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    if (t->size + next_tree->size <= Vcb->superblock.node_size - sizeof(tree_header)) {\n        // merge two trees into one\n\n        t->header.num_items += next_tree->header.num_items;\n        t->size += next_tree->size;\n\n        if (next_tree->header.level > 0) {\n            le = next_tree->itemlist.Flink;\n\n            while (le != &next_tree->itemlist) {\n                tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry);\n\n                if (td2->treeholder.tree) {\n                    td2->treeholder.tree->parent = t;\n#ifdef DEBUG_PARANOID\n                    if (td2->treeholder.tree->parent && td2->treeholder.tree->parent->header.level <= td2->treeholder.tree->header.level) int3;\n#endif\n                }\n\n                td2->inserted = true;\n                le = le->Flink;\n            }\n        } else {\n            le = next_tree->itemlist.Flink;\n\n            while (le != &next_tree->itemlist) {\n                tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry);\n\n                if (!td2->inserted && td2->data) {\n                    uint8_t* data = ExAllocatePoolWithTag(PagedPool, td2->size, ALLOC_TAG);\n\n                    if (!data) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    RtlCopyMemory(data, td2->data, td2->size);\n                    td2->data = data;\n                    td2->inserted = true;\n                }\n\n                le = le->Flink;\n            }\n        }\n\n        t->itemlist.Blink->Flink = next_tree->itemlist.Flink;\n        t->itemlist.Blink->Flink->Blink = t->itemlist.Blink;\n        t->itemlist.Blink = next_tree->itemlist.Blink;\n        t->itemlist.Blink->Flink = &t->itemlist;\n\n        next_tree->itemlist.Flink = next_tree->itemlist.Blink = &next_tree->itemlist;\n\n        next_tree->header.num_items = 0;\n        next_tree->size = 0;\n\n        if (next_tree->has_new_address) { // delete associated EXTENT_ITEM\n            Status = reduce_tree_extent(Vcb, next_tree->new_address, next_tree, next_tree->parent->header.tree_id, next_tree->header.level, Irp, rollback);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"reduce_tree_extent returned %08lx\\n\", Status);\n                return Status;\n            }\n        } else if (next_tree->has_address) {\n            Status = reduce_tree_extent(Vcb, next_tree->header.address, next_tree, next_tree->parent->header.tree_id, next_tree->header.level, Irp, rollback);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"reduce_tree_extent returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        if (!nextparitem->ignore) {\n            nextparitem->ignore = true;\n            next_tree->parent->header.num_items--;\n            next_tree->parent->size -= sizeof(internal_node);\n\n            *done_deletions = true;\n        }\n\n        par = next_tree->parent;\n        while (par) {\n            par->write = true;\n            par = par->parent;\n        }\n\n        RemoveEntryList(&nextparitem->list_entry);\n        ExFreePool(next_tree->paritem);\n        next_tree->paritem = NULL;\n\n        next_tree->root->root_item.bytes_used -= Vcb->superblock.node_size;\n\n        free_tree(next_tree);\n\n        *done = true;\n    } else {\n        // rebalance by moving items from second tree into first\n        ULONG avg_size = (t->size + next_tree->size) / 2;\n        KEY firstitem = {0, 0, 0};\n        bool changed = false;\n\n        TRACE(\"attempting rebalance\\n\");\n\n        le = next_tree->itemlist.Flink;\n        while (le != &next_tree->itemlist && t->size < avg_size && next_tree->header.num_items > 1) {\n            tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);\n            ULONG size;\n\n            if (!td->ignore) {\n                if (next_tree->header.level == 0)\n                    size = sizeof(leaf_node) + td->size;\n                else\n                    size = sizeof(internal_node);\n            } else\n                size = 0;\n\n            if (t->size + size < Vcb->superblock.node_size - sizeof(tree_header)) {\n                RemoveEntryList(&td->list_entry);\n                InsertTailList(&t->itemlist, &td->list_entry);\n\n                if (next_tree->header.level > 0 && td->treeholder.tree) {\n                    td->treeholder.tree->parent = t;\n#ifdef DEBUG_PARANOID\n                    if (td->treeholder.tree->parent && td->treeholder.tree->parent->header.level <= td->treeholder.tree->header.level) int3;\n#endif\n                } else if (next_tree->header.level == 0 && !td->inserted && td->size > 0) {\n                    uint8_t* data = ExAllocatePoolWithTag(PagedPool, td->size, ALLOC_TAG);\n\n                    if (!data) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    RtlCopyMemory(data, td->data, td->size);\n                    td->data = data;\n                }\n\n                td->inserted = true;\n\n                if (!td->ignore) {\n                    next_tree->size -= size;\n                    t->size += size;\n                    next_tree->header.num_items--;\n                    t->header.num_items++;\n                }\n\n                changed = true;\n            } else\n                break;\n\n            le = next_tree->itemlist.Flink;\n        }\n\n        le = next_tree->itemlist.Flink;\n        while (le != &next_tree->itemlist) {\n            tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);\n\n            if (!td->ignore) {\n                firstitem = td->key;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        // FIXME - once ascension is working, make this work with parent's parent, etc.\n        if (next_tree->paritem)\n            next_tree->paritem->key = firstitem;\n\n        par = next_tree;\n        while (par) {\n            par->write = true;\n            par = par->parent;\n        }\n\n        if (changed)\n            *done = true;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS update_extent_level(device_extension* Vcb, uint64_t address, tree* t, uint8_t level, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n\n    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA) {\n        searchkey.obj_id = address;\n        searchkey.obj_type = TYPE_METADATA_ITEM;\n        searchkey.offset = t->header.level;\n\n        Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (!keycmp(tp.item->key, searchkey)) {\n            EXTENT_ITEM_SKINNY_METADATA* eism;\n\n            if (tp.item->size > 0) {\n                eism = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);\n\n                if (!eism) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(eism, tp.item->data, tp.item->size);\n            } else\n                eism = NULL;\n\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                if (eism) ExFreePool(eism);\n                return Status;\n            }\n\n            Status = insert_tree_item(Vcb, Vcb->extent_root, address, TYPE_METADATA_ITEM, level, eism, tp.item->size, NULL, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                if (eism) ExFreePool(eism);\n                return Status;\n            }\n\n            return STATUS_SUCCESS;\n        }\n    }\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = TYPE_EXTENT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n        EXTENT_ITEM_TREE* eit;\n\n        if (tp.item->size < sizeof(EXTENT_ITEM_TREE)) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM_TREE));\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        eit = ExAllocatePoolWithTag(PagedPool, tp.item->size, ALLOC_TAG);\n\n        if (!eit) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(eit, tp.item->data, tp.item->size);\n\n        Status = delete_tree_item(Vcb, &tp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            ExFreePool(eit);\n            return Status;\n        }\n\n        eit->level = level;\n\n        Status = insert_tree_item(Vcb, Vcb->extent_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, eit, tp.item->size, NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            ExFreePool(eit);\n            return Status;\n        }\n\n        return STATUS_SUCCESS;\n    }\n\n    ERR(\"could not find EXTENT_ITEM for address %I64x\\n\", address);\n\n    return STATUS_INTERNAL_ERROR;\n}\n\nstatic NTSTATUS update_tree_extents_recursive(device_extension* Vcb, tree* t, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n\n    if (t->parent && !t->parent->updated_extents && t->parent->has_address) {\n        Status = update_tree_extents_recursive(Vcb, t->parent, Irp, rollback);\n        if (!NT_SUCCESS(Status))\n            return Status;\n    }\n\n    Status = update_tree_extents(Vcb, t, Irp, rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"update_tree_extents returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS do_splits(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) {\n    ULONG level, max_level;\n    uint32_t min_size, min_size_fst;\n    bool empty, done_deletions = false;\n    NTSTATUS Status;\n    tree* t;\n\n    TRACE(\"(%p)\\n\", Vcb);\n\n    max_level = 0;\n\n    for (level = 0; level <= 255; level++) {\n        LIST_ENTRY *le, *nextle;\n\n        empty = true;\n\n        TRACE(\"doing level %lu\\n\", level);\n\n        le = Vcb->trees.Flink;\n\n        while (le != &Vcb->trees) {\n            t = CONTAINING_RECORD(le, tree, list_entry);\n\n            nextle = le->Flink;\n\n            if (t->write && t->header.level == level) {\n                empty = false;\n\n                if (t->header.num_items == 0) {\n                    if (t->parent) {\n                        done_deletions = true;\n\n                        TRACE(\"deleting tree in root %I64x\\n\", t->root->id);\n\n                        t->root->root_item.bytes_used -= Vcb->superblock.node_size;\n\n                        if (t->has_new_address) { // delete associated EXTENT_ITEM\n                            Status = reduce_tree_extent(Vcb, t->new_address, t, t->parent->header.tree_id, t->header.level, Irp, rollback);\n\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"reduce_tree_extent returned %08lx\\n\", Status);\n                                return Status;\n                            }\n\n                            t->has_new_address = false;\n                        } else if (t->has_address) {\n                            Status = reduce_tree_extent(Vcb,t->header.address, t, t->parent->header.tree_id, t->header.level, Irp, rollback);\n\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"reduce_tree_extent returned %08lx\\n\", Status);\n                                return Status;\n                            }\n\n                            t->has_address = false;\n                        }\n\n                        if (!t->paritem->ignore) {\n                            t->paritem->ignore = true;\n                            t->parent->header.num_items--;\n                            t->parent->size -= sizeof(internal_node);\n                        }\n\n                        RemoveEntryList(&t->paritem->list_entry);\n                        ExFreePool(t->paritem);\n                        t->paritem = NULL;\n\n                        free_tree(t);\n                    } else if (t->header.level != 0) {\n                        if (t->has_new_address) {\n                            Status = update_extent_level(Vcb, t->new_address, t, 0, Irp);\n\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"update_extent_level returned %08lx\\n\", Status);\n                                return Status;\n                            }\n                        }\n\n                        t->header.level = 0;\n                    }\n                } else if (t->size > Vcb->superblock.node_size - sizeof(tree_header)) {\n                    TRACE(\"splitting overlarge tree (%x > %Ix)\\n\", t->size, Vcb->superblock.node_size - sizeof(tree_header));\n\n                    if (!t->updated_extents && t->has_address) {\n                        Status = update_tree_extents_recursive(Vcb, t, Irp, rollback);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"update_tree_extents_recursive returned %08lx\\n\", Status);\n                            return Status;\n                        }\n                    }\n\n                    Status = split_tree(Vcb, t);\n\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"split_tree returned %08lx\\n\", Status);\n                        return Status;\n                    }\n                }\n            }\n\n            le = nextle;\n        }\n\n        if (!empty) {\n            max_level = level;\n        } else {\n            TRACE(\"nothing found for level %lu\\n\", level);\n            break;\n        }\n    }\n\n    min_size = (Vcb->superblock.node_size - sizeof(tree_header)) / 2;\n    min_size_fst = (Vcb->superblock.node_size - sizeof(tree_header)) / 4;\n\n    for (level = 0; level <= max_level; level++) {\n        LIST_ENTRY* le;\n\n        le = Vcb->trees.Flink;\n\n        while (le != &Vcb->trees) {\n            t = CONTAINING_RECORD(le, tree, list_entry);\n\n            if (t->write && t->header.level == level && t->header.num_items > 0 && t->parent &&\n                ((t->size < min_size && t->root->id != BTRFS_ROOT_FREE_SPACE) || (t->size < min_size_fst && t->root->id == BTRFS_ROOT_FREE_SPACE)) &&\n                is_tree_unique(Vcb, t, Irp)) {\n                bool done;\n\n                do {\n                    Status = try_tree_amalgamate(Vcb, t, &done, &done_deletions, Irp, rollback);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"try_tree_amalgamate returned %08lx\\n\", Status);\n                        return Status;\n                    }\n                } while (done && t->size < min_size);\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    // simplify trees if top tree only has one entry\n\n    if (done_deletions) {\n        for (level = max_level; level > 0; level--) {\n            LIST_ENTRY *le, *nextle;\n\n            le = Vcb->trees.Flink;\n            while (le != &Vcb->trees) {\n                nextle = le->Flink;\n                t = CONTAINING_RECORD(le, tree, list_entry);\n\n                if (t->write && t->header.level == level) {\n                    if (!t->parent && t->header.num_items == 1) {\n                        LIST_ENTRY* le2 = t->itemlist.Flink;\n                        tree_data* td = NULL;\n                        tree* child_tree = NULL;\n\n                        while (le2 != &t->itemlist) {\n                            td = CONTAINING_RECORD(le2, tree_data, list_entry);\n                            if (!td->ignore)\n                                break;\n                            le2 = le2->Flink;\n                        }\n\n                        TRACE(\"deleting top-level tree in root %I64x with one item\\n\", t->root->id);\n\n                        if (t->has_new_address) { // delete associated EXTENT_ITEM\n                            Status = reduce_tree_extent(Vcb, t->new_address, t, t->header.tree_id, t->header.level, Irp, rollback);\n\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"reduce_tree_extent returned %08lx\\n\", Status);\n                                return Status;\n                            }\n\n                            t->has_new_address = false;\n                        } else if (t->has_address) {\n                            Status = reduce_tree_extent(Vcb,t->header.address, t, t->header.tree_id, t->header.level, Irp, rollback);\n\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"reduce_tree_extent returned %08lx\\n\", Status);\n                                return Status;\n                            }\n\n                            t->has_address = false;\n                        }\n\n                        if (!td->treeholder.tree) { // load first item if not already loaded\n                            KEY searchkey = {0,0,0};\n                            traverse_ptr tp;\n\n                            Status = find_item(Vcb, t->root, &tp, &searchkey, false, Irp);\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"error - find_item returned %08lx\\n\", Status);\n                                return Status;\n                            }\n                        }\n\n                        child_tree = td->treeholder.tree;\n\n                        if (child_tree) {\n                            child_tree->parent = NULL;\n                            child_tree->paritem = NULL;\n                        }\n\n                        t->root->root_item.bytes_used -= Vcb->superblock.node_size;\n\n                        free_tree(t);\n\n                        if (child_tree)\n                            child_tree->root->treeholder.tree = child_tree;\n                    }\n                }\n\n                le = nextle;\n            }\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS remove_root_extents(device_extension* Vcb, root* r, tree_holder* th, uint8_t level, tree* parent, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n\n    if (!th->tree) {\n        uint8_t* buf;\n        chunk* c;\n\n        buf = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG);\n        if (!buf) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        Status = read_data(Vcb, th->address, Vcb->superblock.node_size, NULL, true, buf, NULL,\n                           &c, Irp, th->generation, false, NormalPagePriority);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"read_data returned 0x%08lx\\n\", Status);\n            ExFreePool(buf);\n            return Status;\n        }\n\n        Status = load_tree(Vcb, th->address, buf, r, &th->tree);\n\n        if (!th->tree || th->tree->buf != buf)\n            ExFreePool(buf);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"load_tree(%I64x) returned %08lx\\n\", th->address, Status);\n            return Status;\n        }\n    }\n\n    if (level > 0) {\n        LIST_ENTRY* le = th->tree->itemlist.Flink;\n\n        while (le != &th->tree->itemlist) {\n            tree_data* td = CONTAINING_RECORD(le, tree_data, list_entry);\n\n            if (!td->ignore) {\n                Status = remove_root_extents(Vcb, r, &td->treeholder, th->tree->header.level - 1, th->tree, Irp, rollback);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"remove_root_extents returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    if (th->tree && !th->tree->updated_extents && th->tree->has_address) {\n        Status = update_tree_extents(Vcb, th->tree, Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"update_tree_extents returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    if (!th->tree || th->tree->has_address) {\n        Status = reduce_tree_extent(Vcb, th->address, NULL, parent ? parent->header.tree_id : r->id, level, Irp, rollback);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"reduce_tree_extent(%I64x) returned %08lx\\n\", th->address, Status);\n            return Status;\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS drop_root(device_extension* Vcb, root* r, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n\n    Status = remove_root_extents(Vcb, r, &r->treeholder, r->root_item.root_level, NULL, Irp, rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"remove_root_extents returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    // remove entries in uuid root (tree 9)\n    if (Vcb->uuid_root) {\n        RtlCopyMemory(&searchkey.obj_id, &r->root_item.uuid.uuid[0], sizeof(uint64_t));\n        searchkey.obj_type = TYPE_SUBVOL_UUID;\n        RtlCopyMemory(&searchkey.offset, &r->root_item.uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t));\n\n        if (searchkey.obj_id != 0 || searchkey.offset != 0) {\n            Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp);\n            if (!NT_SUCCESS(Status)) {\n                WARN(\"find_item returned %08lx\\n\", Status);\n            } else {\n                if (!keycmp(tp.item->key, searchkey)) {\n                    Status = delete_tree_item(Vcb, &tp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n                } else\n                    WARN(\"could not find (%I64x,%x,%I64x) in uuid tree\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n            }\n        }\n\n        if (r->root_item.rtransid > 0) {\n            RtlCopyMemory(&searchkey.obj_id, &r->root_item.received_uuid.uuid[0], sizeof(uint64_t));\n            searchkey.obj_type = TYPE_SUBVOL_REC_UUID;\n            RtlCopyMemory(&searchkey.offset, &r->root_item.received_uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t));\n\n            Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp);\n            if (!NT_SUCCESS(Status))\n                WARN(\"find_item returned %08lx\\n\", Status);\n            else {\n                if (!keycmp(tp.item->key, searchkey)) {\n                    if (tp.item->size == sizeof(uint64_t)) {\n                        uint64_t* id = (uint64_t*)tp.item->data;\n\n                        if (*id == r->id) {\n                            Status = delete_tree_item(Vcb, &tp);\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                                return Status;\n                            }\n                        }\n                    } else if (tp.item->size > sizeof(uint64_t)) {\n                        ULONG i;\n                        uint64_t* ids = (uint64_t*)tp.item->data;\n\n                        for (i = 0; i < tp.item->size / sizeof(uint64_t); i++) {\n                            if (ids[i] == r->id) {\n                                uint64_t* ne;\n\n                                ne = ExAllocatePoolWithTag(PagedPool, tp.item->size - sizeof(uint64_t), ALLOC_TAG);\n                                if (!ne) {\n                                    ERR(\"out of memory\\n\");\n                                    return STATUS_INSUFFICIENT_RESOURCES;\n                                }\n\n                                if (i > 0)\n                                    RtlCopyMemory(ne, ids, sizeof(uint64_t) * i);\n\n                                if ((i + 1) * sizeof(uint64_t) < tp.item->size)\n                                    RtlCopyMemory(&ne[i], &ids[i + 1], tp.item->size - ((i + 1) * sizeof(uint64_t)));\n\n                                Status = delete_tree_item(Vcb, &tp);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                                    ExFreePool(ne);\n                                    return Status;\n                                }\n\n                                Status = insert_tree_item(Vcb, Vcb->uuid_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n                                                          ne, tp.item->size - sizeof(uint64_t), NULL, Irp);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                                    ExFreePool(ne);\n                                    return Status;\n                                }\n\n                                break;\n                            }\n                        }\n                    }\n                } else\n                    WARN(\"could not find (%I64x,%x,%I64x) in uuid tree\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n            }\n        }\n    }\n\n    // delete ROOT_ITEM\n\n    searchkey.obj_id = r->id;\n    searchkey.obj_type = TYPE_ROOT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n        Status = delete_tree_item(Vcb, &tp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n    } else\n        WARN(\"could not find (%I64x,%x,%I64x) in root_root\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n\n    // delete items in tree cache\n\n    free_trees_root(Vcb, r);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS drop_roots(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) {\n    LIST_ENTRY *le = Vcb->drop_roots.Flink, *le2;\n    NTSTATUS Status;\n\n    while (le != &Vcb->drop_roots) {\n        root* r = CONTAINING_RECORD(le, root, list_entry);\n\n        le2 = le->Flink;\n\n        Status = drop_root(Vcb, r, Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"drop_root(%I64x) returned %08lx\\n\", r->id, Status);\n            return Status;\n        }\n\n        le = le2;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS update_dev_item(device_extension* Vcb, device* device, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    DEV_ITEM* di;\n    NTSTATUS Status;\n\n    searchkey.obj_id = 1;\n    searchkey.obj_type = TYPE_DEV_ITEM;\n    searchkey.offset = device->devitem.dev_id;\n\n    Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (keycmp(tp.item->key, searchkey)) {\n        ERR(\"error - could not find DEV_ITEM for device %I64x\\n\", device->devitem.dev_id);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    Status = delete_tree_item(Vcb, &tp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    di = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_ITEM), ALLOC_TAG);\n    if (!di) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(di, &device->devitem, sizeof(DEV_ITEM));\n\n    Status = insert_tree_item(Vcb, Vcb->chunk_root, 1, TYPE_DEV_ITEM, device->devitem.dev_id, di, sizeof(DEV_ITEM), NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(di);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic void regen_bootstrap(device_extension* Vcb) {\n    sys_chunk* sc2;\n    USHORT i = 0;\n    LIST_ENTRY* le;\n\n    i = 0;\n    le = Vcb->sys_chunks.Flink;\n    while (le != &Vcb->sys_chunks) {\n        sc2 = CONTAINING_RECORD(le, sys_chunk, list_entry);\n\n        TRACE(\"%I64x,%x,%I64x\\n\", sc2->key.obj_id, sc2->key.obj_type, sc2->key.offset);\n\n        RtlCopyMemory(&Vcb->superblock.sys_chunk_array[i], &sc2->key, sizeof(KEY));\n        i += sizeof(KEY);\n\n        RtlCopyMemory(&Vcb->superblock.sys_chunk_array[i], sc2->data, sc2->size);\n        i += sc2->size;\n\n        le = le->Flink;\n    }\n}\n\nstatic NTSTATUS add_to_bootstrap(device_extension* Vcb, uint64_t obj_id, uint8_t obj_type, uint64_t offset, void* data, uint16_t size) {\n    sys_chunk* sc;\n    LIST_ENTRY* le;\n\n    if (Vcb->superblock.n + sizeof(KEY) + size > SYS_CHUNK_ARRAY_SIZE) {\n        ERR(\"error - bootstrap is full\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    sc = ExAllocatePoolWithTag(PagedPool, sizeof(sys_chunk), ALLOC_TAG);\n    if (!sc) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    sc->key.obj_id = obj_id;\n    sc->key.obj_type = obj_type;\n    sc->key.offset = offset;\n    sc->size = size;\n    sc->data = ExAllocatePoolWithTag(PagedPool, sc->size, ALLOC_TAG);\n    if (!sc->data) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(sc);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(sc->data, data, sc->size);\n\n    le = Vcb->sys_chunks.Flink;\n    while (le != &Vcb->sys_chunks) {\n        sys_chunk* sc2 = CONTAINING_RECORD(le, sys_chunk, list_entry);\n\n        if (keycmp(sc2->key, sc->key) == 1)\n            break;\n\n        le = le->Flink;\n    }\n    InsertTailList(le, &sc->list_entry);\n\n    Vcb->superblock.n += sizeof(KEY) + size;\n\n    regen_bootstrap(Vcb);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS create_chunk(device_extension* Vcb, chunk* c, PIRP Irp) {\n    CHUNK_ITEM* ci;\n    CHUNK_ITEM_STRIPE* cis;\n    BLOCK_GROUP_ITEM* bgi;\n    uint16_t i, factor;\n    NTSTATUS Status;\n\n    ci = ExAllocatePoolWithTag(PagedPool, c->size, ALLOC_TAG);\n    if (!ci) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(ci, c->chunk_item, c->size);\n\n    Status = insert_tree_item(Vcb, Vcb->chunk_root, 0x100, TYPE_CHUNK_ITEM, c->offset, ci, c->size, NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item failed\\n\");\n        ExFreePool(ci);\n        return Status;\n    }\n\n    if (c->chunk_item->type & BLOCK_FLAG_SYSTEM) {\n        Status = add_to_bootstrap(Vcb, 0x100, TYPE_CHUNK_ITEM, c->offset, ci, c->size);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_to_bootstrap returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    // add BLOCK_GROUP_ITEM to tree 2\n\n    bgi = ExAllocatePoolWithTag(PagedPool, sizeof(BLOCK_GROUP_ITEM), ALLOC_TAG);\n    if (!bgi) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    bgi->used = c->used;\n    bgi->chunk_tree = 0x100;\n    bgi->flags = c->chunk_item->type;\n\n    Status = insert_tree_item(Vcb, Vcb->block_group_root ? Vcb->block_group_root : Vcb->extent_root, c->offset,\n                              TYPE_BLOCK_GROUP_ITEM, c->chunk_item->size, bgi, sizeof(BLOCK_GROUP_ITEM), NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item failed\\n\");\n        ExFreePool(bgi);\n        return Status;\n    }\n\n    if (c->chunk_item->type & BLOCK_FLAG_RAID0)\n        factor = c->chunk_item->num_stripes;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID10)\n        factor = c->chunk_item->num_stripes / c->chunk_item->sub_stripes;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID5)\n        factor = c->chunk_item->num_stripes - 1;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID6)\n        factor = c->chunk_item->num_stripes - 2;\n    else // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4\n        factor = 1;\n\n    // add DEV_EXTENTs to tree 4\n\n    cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        DEV_EXTENT* de;\n\n        de = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_EXTENT), ALLOC_TAG);\n        if (!de) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        de->chunktree = Vcb->chunk_root->id;\n        de->objid = 0x100;\n        de->address = c->offset;\n        de->length = c->chunk_item->size / factor;\n        de->chunktree_uuid = Vcb->chunk_root->treeholder.tree->header.chunk_tree_uuid;\n\n        Status = insert_tree_item(Vcb, Vcb->dev_root, c->devices[i]->devitem.dev_id, TYPE_DEV_EXTENT, cis[i].offset, de, sizeof(DEV_EXTENT), NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            ExFreePool(de);\n            return Status;\n        }\n\n        // FIXME - no point in calling this twice for the same device\n        Status = update_dev_item(Vcb, c->devices[i], Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"update_dev_item returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    c->created = false;\n    c->oldused = c->used;\n\n    Vcb->superblock.bytes_used += c->used;\n\n    return STATUS_SUCCESS;\n}\n\nstatic void remove_from_bootstrap(device_extension* Vcb, uint64_t obj_id, uint8_t obj_type, uint64_t offset) {\n    sys_chunk* sc2;\n    LIST_ENTRY* le;\n\n    le = Vcb->sys_chunks.Flink;\n    while (le != &Vcb->sys_chunks) {\n        sc2 = CONTAINING_RECORD(le, sys_chunk, list_entry);\n\n        if (sc2->key.obj_id == obj_id && sc2->key.obj_type == obj_type && sc2->key.offset == offset) {\n            RemoveEntryList(&sc2->list_entry);\n\n            Vcb->superblock.n -= sizeof(KEY) + sc2->size;\n\n            ExFreePool(sc2->data);\n            ExFreePool(sc2);\n            regen_bootstrap(Vcb);\n            return;\n        }\n\n        le = le->Flink;\n    }\n}\n\nstatic NTSTATUS set_xattr(device_extension* Vcb, LIST_ENTRY* batchlist, root* subvol, uint64_t inode, char* name, uint16_t namelen,\n                          uint32_t crc32, uint8_t* data, uint16_t datalen) {\n    NTSTATUS Status;\n    uint16_t xasize;\n    DIR_ITEM* xa;\n\n    TRACE(\"(%p, %I64x, %I64x, %.*s, %08x, %p, %u)\\n\", Vcb, subvol->id, inode, namelen, name, crc32, data, datalen);\n\n    xasize = (uint16_t)offsetof(DIR_ITEM, name[0]) + namelen + datalen;\n\n    xa = ExAllocatePoolWithTag(PagedPool, xasize, ALLOC_TAG);\n    if (!xa) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    xa->key.obj_id = 0;\n    xa->key.obj_type = 0;\n    xa->key.offset = 0;\n    xa->transid = Vcb->superblock.generation;\n    xa->m = datalen;\n    xa->n = namelen;\n    xa->type = BTRFS_TYPE_EA;\n    RtlCopyMemory(xa->name, name, namelen);\n    RtlCopyMemory(xa->name + namelen, data, datalen);\n\n    Status = insert_tree_item_batch(batchlist, Vcb, subvol, inode, TYPE_XATTR_ITEM, crc32, xa, xasize, Batch_SetXattr);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n        ExFreePool(xa);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS delete_xattr(device_extension* Vcb, LIST_ENTRY* batchlist, root* subvol, uint64_t inode, char* name,\n                             uint16_t namelen, uint32_t crc32) {\n    NTSTATUS Status;\n    uint16_t xasize;\n    DIR_ITEM* xa;\n\n    TRACE(\"(%p, %I64x, %I64x, %.*s, %08x)\\n\", Vcb, subvol->id, inode, namelen, name, crc32);\n\n    xasize = (uint16_t)offsetof(DIR_ITEM, name[0]) + namelen;\n\n    xa = ExAllocatePoolWithTag(PagedPool, xasize, ALLOC_TAG);\n    if (!xa) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    xa->key.obj_id = 0;\n    xa->key.obj_type = 0;\n    xa->key.offset = 0;\n    xa->transid = Vcb->superblock.generation;\n    xa->m = 0;\n    xa->n = namelen;\n    xa->type = BTRFS_TYPE_EA;\n    RtlCopyMemory(xa->name, name, namelen);\n\n    Status = insert_tree_item_batch(batchlist, Vcb, subvol, inode, TYPE_XATTR_ITEM, crc32, xa, xasize, Batch_DeleteXattr);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n        ExFreePool(xa);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS insert_sparse_extent(fcb* fcb, LIST_ENTRY* batchlist, uint64_t start, uint64_t length) {\n    NTSTATUS Status;\n    EXTENT_DATA* ed;\n    EXTENT_DATA2* ed2;\n\n    TRACE(\"((%I64x, %I64x), %I64x, %I64x)\\n\", fcb->subvol->id, fcb->inode, start, length);\n\n    ed = ExAllocatePoolWithTag(PagedPool, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG);\n    if (!ed) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    ed->generation = fcb->Vcb->superblock.generation;\n    ed->decoded_size = length;\n    ed->compression = BTRFS_COMPRESSION_NONE;\n    ed->encryption = BTRFS_ENCRYPTION_NONE;\n    ed->encoding = BTRFS_ENCODING_NONE;\n    ed->type = EXTENT_TYPE_REGULAR;\n\n    ed2 = (EXTENT_DATA2*)ed->data;\n    ed2->address = 0;\n    ed2->size = 0;\n    ed2->offset = 0;\n    ed2->num_bytes = length;\n\n    Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, start, ed, sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), Batch_Insert);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n        ExFreePool(ed);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS split_batch_item_list(batch_item_ind* bii) {\n    LIST_ENTRY* le;\n    unsigned int i = 0;\n    LIST_ENTRY* midpoint = NULL;\n    batch_item_ind* bii2;\n    batch_item* midpoint_item;\n    LIST_ENTRY* before_midpoint;\n\n    le = bii->items.Flink;\n    while (le != &bii->items) {\n        if (i >= bii->num_items / 2) {\n            midpoint = le;\n            break;\n        }\n\n        i++;\n\n        le = le->Flink;\n    }\n\n    if (!midpoint)\n        return STATUS_SUCCESS;\n\n    // make sure items on either side of split don't have same key\n\n    while (midpoint->Blink != &bii->items) {\n        batch_item* item = CONTAINING_RECORD(midpoint, batch_item, list_entry);\n        batch_item* prev = CONTAINING_RECORD(midpoint->Blink, batch_item, list_entry);\n\n        if (item->key.obj_id != prev->key.obj_id)\n            break;\n\n        if (item->key.obj_type != prev->key.obj_type)\n            break;\n\n        if (item->key.offset != prev->key.offset)\n            break;\n\n        midpoint = midpoint->Blink;\n        i--;\n    }\n\n    if (midpoint->Blink == &bii->items)\n        return STATUS_SUCCESS;\n\n    bii2 = ExAllocatePoolWithTag(PagedPool, sizeof(batch_item_ind), ALLOC_TAG);\n    if (!bii2) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    midpoint_item = CONTAINING_RECORD(midpoint, batch_item, list_entry);\n\n    bii2->key.obj_id = midpoint_item->key.obj_id;\n    bii2->key.obj_type = midpoint_item->key.obj_type;\n    bii2->key.offset = midpoint_item->key.offset;\n\n    bii2->num_items = bii->num_items - i;\n    bii->num_items = i;\n\n    before_midpoint = midpoint->Blink;\n\n    bii2->items.Flink = midpoint;\n    midpoint->Blink = &bii2->items;\n    bii2->items.Blink = bii->items.Blink;\n    bii->items.Blink->Flink = &bii2->items;\n\n    bii->items.Blink = before_midpoint;\n    before_midpoint->Flink = &bii->items;\n\n    InsertHeadList(&bii->list_entry, &bii2->list_entry);\n\n    return STATUS_SUCCESS;\n}\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(suppress: 28194)\n#endif\nstatic NTSTATUS insert_tree_item_batch(LIST_ENTRY* batchlist, device_extension* Vcb, root* r, uint64_t objid,\n                                       uint8_t objtype, uint64_t offset, _In_opt_ _When_(return >= 0, __drv_aliasesMem) void* data,\n                                       uint16_t datalen, enum batch_operation operation) {\n    LIST_ENTRY* le;\n    batch_root* br = NULL;\n    batch_item* bi;\n\n    le = batchlist->Flink;\n    while (le != batchlist) {\n        batch_root* br2 = CONTAINING_RECORD(le, batch_root, list_entry);\n\n        if (br2->r == r) {\n            br = br2;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!br) {\n        br = ExAllocatePoolWithTag(PagedPool, sizeof(batch_root), ALLOC_TAG);\n        if (!br) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        br->r = r;\n        InitializeListHead(&br->items_ind);\n        InsertTailList(batchlist, &br->list_entry);\n    }\n\n    if (IsListEmpty(&br->items_ind)) {\n        batch_item_ind* bii;\n\n        bii = ExAllocatePoolWithTag(PagedPool, sizeof(batch_item_ind), ALLOC_TAG);\n        if (!bii) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        bii->key.obj_id = 0;\n        bii->key.obj_type = 0;\n        bii->key.offset = 0;\n        InitializeListHead(&bii->items);\n        bii->num_items = 0;\n        InsertTailList(&br->items_ind, &bii->list_entry);\n    }\n\n    bi = ExAllocateFromPagedLookasideList(&Vcb->batch_item_lookaside);\n    if (!bi) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    bi->key.obj_id = objid;\n    bi->key.obj_type = objtype;\n    bi->key.offset = offset;\n    bi->data = data;\n    bi->datalen = datalen;\n    bi->operation = operation;\n\n    le = br->items_ind.Blink;\n    while (le != &br->items_ind) {\n        LIST_ENTRY* le2;\n        batch_item_ind* bii = CONTAINING_RECORD(le, batch_item_ind, list_entry);\n\n        if (keycmp(bii->key, bi->key) == 1) {\n            le = le->Blink;\n            continue;\n        }\n\n        le2 = bii->items.Blink;\n        while (le2 != &bii->items) {\n            batch_item* bi2 = CONTAINING_RECORD(le2, batch_item, list_entry);\n            int cmp = keycmp(bi2->key, bi->key);\n\n            if (cmp == -1 || (cmp == 0 && bi->operation >= bi2->operation)) {\n                InsertHeadList(&bi2->list_entry, &bi->list_entry);\n                bii->num_items++;\n                goto end;\n            }\n\n            le2 = le2->Blink;\n        }\n\n        InsertHeadList(&bii->items, &bi->list_entry);\n        bii->num_items++;\n\nend:\n        if (bii->num_items > BATCH_ITEM_LIMIT)\n            return split_batch_item_list(bii);\n\n        return STATUS_SUCCESS;\n    }\n\n    return STATUS_INTERNAL_ERROR;\n}\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\ntypedef struct {\n    uint64_t address;\n    uint64_t length;\n    uint64_t offset;\n    bool changed;\n    chunk* chunk;\n    uint64_t skip_start;\n    uint64_t skip_end;\n    LIST_ENTRY list_entry;\n} extent_range;\n\nstatic void rationalize_extents(fcb* fcb, PIRP Irp) {\n    LIST_ENTRY* le;\n    LIST_ENTRY extent_ranges;\n    extent_range* er;\n    bool changed = false, truncating = false;\n    uint32_t num_extents = 0;\n\n    InitializeListHead(&extent_ranges);\n\n    le = fcb->extents.Flink;\n    while (le != &fcb->extents) {\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->unique) {\n            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n            if (ed2->size != 0) {\n                LIST_ENTRY* le2;\n\n                le2 = extent_ranges.Flink;\n                while (le2 != &extent_ranges) {\n                    extent_range* er2 = CONTAINING_RECORD(le2, extent_range, list_entry);\n\n                    if (er2->address == ed2->address) {\n                        er2->skip_start = min(er2->skip_start, ed2->offset);\n                        er2->skip_end = min(er2->skip_end, ed2->size - ed2->offset - ed2->num_bytes);\n                        goto cont;\n                    } else if (er2->address > ed2->address)\n                        break;\n\n                    le2 = le2->Flink;\n                }\n\n                er = ExAllocatePoolWithTag(PagedPool, sizeof(extent_range), ALLOC_TAG); // FIXME - should be from lookaside?\n                if (!er) {\n                    ERR(\"out of memory\\n\");\n                    goto end;\n                }\n\n                er->address = ed2->address;\n                er->length = ed2->size;\n                er->offset = ext->offset - ed2->offset;\n                er->changed = false;\n                er->chunk = NULL;\n                er->skip_start = ed2->offset;\n                er->skip_end = ed2->size - ed2->offset - ed2->num_bytes;\n\n                if (er->skip_start != 0 || er->skip_end != 0)\n                    truncating = true;\n\n                InsertHeadList(le2->Blink, &er->list_entry);\n                num_extents++;\n            }\n        }\n\ncont:\n        le = le->Flink;\n    }\n\n    if (num_extents == 0 || (num_extents == 1 && !truncating))\n        goto end;\n\n    le = extent_ranges.Flink;\n    while (le != &extent_ranges) {\n        er = CONTAINING_RECORD(le, extent_range, list_entry);\n\n        if (!er->chunk) {\n            LIST_ENTRY* le2;\n\n            er->chunk = get_chunk_from_address(fcb->Vcb, er->address);\n\n            if (!er->chunk) {\n                ERR(\"get_chunk_from_address(%I64x) failed\\n\", er->address);\n                goto end;\n            }\n\n            le2 = le->Flink;\n            while (le2 != &extent_ranges) {\n                extent_range* er2 = CONTAINING_RECORD(le2, extent_range, list_entry);\n\n                if (!er2->chunk && er2->address >= er->chunk->offset && er2->address < er->chunk->offset + er->chunk->chunk_item->size)\n                    er2->chunk = er->chunk;\n\n                le2 = le2->Flink;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    if (truncating) {\n        // truncate beginning or end of extent if unused\n\n        le = extent_ranges.Flink;\n        while (le != &extent_ranges) {\n            er = CONTAINING_RECORD(le, extent_range, list_entry);\n\n            if (er->skip_start > 0) {\n                LIST_ENTRY* le2 = fcb->extents.Flink;\n                while (le2 != &fcb->extents) {\n                    extent* ext = CONTAINING_RECORD(le2, extent, list_entry);\n\n                    if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->unique) {\n                        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n                        if (ed2->size != 0 && ed2->address == er->address) {\n                            NTSTATUS Status;\n\n                            Status = update_changed_extent_ref(fcb->Vcb, er->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset,\n                                                               -1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, true, Irp);\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                                goto end;\n                            }\n\n                            ext->extent_data.decoded_size -= er->skip_start;\n                            ed2->size -= er->skip_start;\n                            ed2->address += er->skip_start;\n                            ed2->offset -= er->skip_start;\n\n                            add_changed_extent_ref(er->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset,\n                                                   1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM);\n                        }\n                    }\n\n                    le2 = le2->Flink;\n                }\n\n                if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM))\n                    add_checksum_entry(fcb->Vcb, er->address, (ULONG)(er->skip_start >> fcb->Vcb->sector_shift), NULL, NULL);\n\n                acquire_chunk_lock(er->chunk, fcb->Vcb);\n\n                if (!er->chunk->cache_loaded) {\n                    NTSTATUS Status = load_cache_chunk(fcb->Vcb, er->chunk, NULL);\n\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"load_cache_chunk returned %08lx\\n\", Status);\n                        release_chunk_lock(er->chunk, fcb->Vcb);\n                        goto end;\n                    }\n                }\n\n                er->chunk->used -= er->skip_start;\n\n                space_list_add(er->chunk, er->address, er->skip_start, NULL);\n\n                release_chunk_lock(er->chunk, fcb->Vcb);\n\n                er->address += er->skip_start;\n                er->length -= er->skip_start;\n            }\n\n            if (er->skip_end > 0) {\n                LIST_ENTRY* le2 = fcb->extents.Flink;\n                while (le2 != &fcb->extents) {\n                    extent* ext = CONTAINING_RECORD(le2, extent, list_entry);\n\n                    if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->unique) {\n                        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n                        if (ed2->size != 0 && ed2->address == er->address) {\n                            NTSTATUS Status;\n\n                            Status = update_changed_extent_ref(fcb->Vcb, er->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset,\n                                                               -1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, true, Irp);\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                                goto end;\n                            }\n\n                            ext->extent_data.decoded_size -= er->skip_end;\n                            ed2->size -= er->skip_end;\n\n                            add_changed_extent_ref(er->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset,\n                                                   1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM);\n                        }\n                    }\n\n                    le2 = le2->Flink;\n                }\n\n                if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM))\n                    add_checksum_entry(fcb->Vcb, er->address + er->length - er->skip_end, (ULONG)(er->skip_end >> fcb->Vcb->sector_shift), NULL, NULL);\n\n                acquire_chunk_lock(er->chunk, fcb->Vcb);\n\n                if (!er->chunk->cache_loaded) {\n                    NTSTATUS Status = load_cache_chunk(fcb->Vcb, er->chunk, NULL);\n\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"load_cache_chunk returned %08lx\\n\", Status);\n                        release_chunk_lock(er->chunk, fcb->Vcb);\n                        goto end;\n                    }\n                }\n\n                er->chunk->used -= er->skip_end;\n\n                space_list_add(er->chunk, er->address + er->length - er->skip_end, er->skip_end, NULL);\n\n                release_chunk_lock(er->chunk, fcb->Vcb);\n\n                er->length -= er->skip_end;\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    if (num_extents < 2)\n        goto end;\n\n    // merge together adjacent extents\n    le = extent_ranges.Flink;\n    while (le != &extent_ranges) {\n        er = CONTAINING_RECORD(le, extent_range, list_entry);\n\n        if (le->Flink != &extent_ranges && er->length < MAX_EXTENT_SIZE) {\n            extent_range* er2 = CONTAINING_RECORD(le->Flink, extent_range, list_entry);\n\n            if (er->chunk == er2->chunk) {\n                if (er2->address == er->address + er->length && er2->offset >= er->offset + er->length) {\n                    if (er->length + er2->length <= MAX_EXTENT_SIZE) {\n                        er->length += er2->length;\n                        er->changed = true;\n\n                        RemoveEntryList(&er2->list_entry);\n                        ExFreePool(er2);\n\n                        changed = true;\n                        continue;\n                    }\n                }\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    if (!changed)\n        goto end;\n\n    le = fcb->extents.Flink;\n    while (le != &fcb->extents) {\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->unique) {\n            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n            if (ed2->size != 0) {\n                LIST_ENTRY* le2;\n\n                le2 = extent_ranges.Flink;\n                while (le2 != &extent_ranges) {\n                    extent_range* er2 = CONTAINING_RECORD(le2, extent_range, list_entry);\n\n                    if (ed2->address >= er2->address && ed2->address + ed2->size <= er2->address + er2->length && er2->changed) {\n                        NTSTATUS Status;\n\n                        Status = update_changed_extent_ref(fcb->Vcb, er2->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset,\n                                                           -1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, true, Irp);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                            goto end;\n                        }\n\n                        ed2->offset += ed2->address - er2->address;\n                        ed2->address = er2->address;\n                        ed2->size = er2->length;\n                        ext->extent_data.decoded_size = ed2->size;\n\n                        add_changed_extent_ref(er2->chunk, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset,\n                                               1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM);\n\n                        break;\n                    }\n\n                    le2 = le2->Flink;\n                }\n            }\n        }\n\n        le = le->Flink;\n    }\n\nend:\n    while (!IsListEmpty(&extent_ranges)) {\n        le = RemoveHeadList(&extent_ranges);\n        er = CONTAINING_RECORD(le, extent_range, list_entry);\n\n        ExFreePool(er);\n    }\n}\n\nNTSTATUS flush_fcb(fcb* fcb, bool cache, LIST_ENTRY* batchlist, PIRP Irp) {\n    traverse_ptr tp;\n    KEY searchkey;\n    NTSTATUS Status;\n    INODE_ITEM* ii;\n    uint64_t ii_offset;\n#ifdef DEBUG_PARANOID\n    uint64_t old_size = 0;\n    bool extents_changed;\n#endif\n\n    if (fcb->ads) {\n        if (fcb->deleted) {\n            Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adsxattr.Length, fcb->adshash);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        } else {\n            Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, fcb->adsxattr.Buffer, fcb->adsxattr.Length,\n                               fcb->adshash, (uint8_t*)fcb->adsdata.Buffer, fcb->adsdata.Length);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"set_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    if (fcb->deleted) {\n        Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0xffffffffffffffff, NULL, 0, Batch_DeleteInode);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        if (fcb->marked_as_orphan) {\n            Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, BTRFS_ORPHAN_INODE_OBJID, TYPE_ORPHAN_INODE,\n                                            fcb->inode, NULL, 0, Batch_Delete);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n#ifdef DEBUG_PARANOID\n    extents_changed = fcb->extents_changed;\n#endif\n\n    if (fcb->extents_changed) {\n        LIST_ENTRY* le;\n        bool prealloc = false, extents_inline = false;\n        uint64_t last_end;\n\n        // delete ignored extent items\n        le = fcb->extents.Flink;\n        while (le != &fcb->extents) {\n            LIST_ENTRY* le2 = le->Flink;\n            extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n            if (ext->ignore) {\n                RemoveEntryList(&ext->list_entry);\n\n                if (ext->csum)\n                    ExFreePool(ext->csum);\n\n                ExFreePool(ext);\n            }\n\n            le = le2;\n        }\n\n        le = fcb->extents.Flink;\n        while (le != &fcb->extents) {\n            extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n            if (ext->inserted && ext->csum && ext->extent_data.type == EXTENT_TYPE_REGULAR) {\n                EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n                if (ed2->size > 0) { // not sparse\n                    if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE)\n                        add_checksum_entry(fcb->Vcb, ed2->address + ed2->offset, (ULONG)(ed2->num_bytes >> fcb->Vcb->sector_shift), ext->csum, Irp);\n                    else\n                        add_checksum_entry(fcb->Vcb, ed2->address, (ULONG)(ed2->size >> fcb->Vcb->sector_shift), ext->csum, Irp);\n                }\n            }\n\n            le = le->Flink;\n        }\n\n        if (!IsListEmpty(&fcb->extents)) {\n            rationalize_extents(fcb, Irp);\n\n            // merge together adjacent EXTENT_DATAs pointing to same extent\n\n            le = fcb->extents.Flink;\n            while (le != &fcb->extents) {\n                LIST_ENTRY* le2 = le->Flink;\n                extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n                if ((ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) && le->Flink != &fcb->extents) {\n                    extent* nextext = CONTAINING_RECORD(le->Flink, extent, list_entry);\n\n                    if (ext->extent_data.type == nextext->extent_data.type) {\n                        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n                        EXTENT_DATA2* ned2 = (EXTENT_DATA2*)nextext->extent_data.data;\n\n                        if (ed2->size != 0 && ed2->address == ned2->address && ed2->size == ned2->size &&\n                            nextext->offset == ext->offset + ed2->num_bytes && ned2->offset == ed2->offset + ed2->num_bytes) {\n                            chunk* c;\n\n                            if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE && ext->csum) {\n                                ULONG len = (ULONG)((ed2->num_bytes + ned2->num_bytes) >> fcb->Vcb->sector_shift);\n                                void* csum;\n\n                                csum = ExAllocatePoolWithTag(NonPagedPool, len * fcb->Vcb->csum_size, ALLOC_TAG);\n                                if (!csum) {\n                                    ERR(\"out of memory\\n\");\n                                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                                    goto end;\n                                }\n\n                                RtlCopyMemory(csum, ext->csum, (ULONG)((ed2->num_bytes * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift));\n                                RtlCopyMemory((uint8_t*)csum + ((ed2->num_bytes * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift), nextext->csum,\n                                              (ULONG)((ned2->num_bytes * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift));\n\n                                ExFreePool(ext->csum);\n                                ext->csum = csum;\n                            }\n\n                            ext->extent_data.generation = fcb->Vcb->superblock.generation;\n                            ed2->num_bytes += ned2->num_bytes;\n\n                            RemoveEntryList(&nextext->list_entry);\n\n                            if (nextext->csum)\n                                ExFreePool(nextext->csum);\n\n                            ExFreePool(nextext);\n\n                            c = get_chunk_from_address(fcb->Vcb, ed2->address);\n\n                            if (!c) {\n                                ERR(\"get_chunk_from_address(%I64x) failed\\n\", ed2->address);\n                            } else {\n                                Status = update_changed_extent_ref(fcb->Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, -1,\n                                                                fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                                    goto end;\n                                }\n                            }\n\n                            le2 = le;\n                        }\n                    }\n                }\n\n                le = le2;\n            }\n        }\n\n        if (!fcb->created) {\n            // delete existing EXTENT_DATA items\n\n            Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, 0, NULL, 0, Batch_DeleteExtentData);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        // add new EXTENT_DATAs\n\n        last_end = 0;\n\n        le = fcb->extents.Flink;\n        while (le != &fcb->extents) {\n            extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n            EXTENT_DATA* ed;\n\n            ext->inserted = false;\n\n            if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_NO_HOLES) && ext->offset > last_end) {\n                Status = insert_sparse_extent(fcb, batchlist, last_end, ext->offset - last_end);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_sparse_extent returned %08lx\\n\", Status);\n                    goto end;\n                }\n            }\n\n            ed = ExAllocatePoolWithTag(PagedPool, ext->datalen, ALLOC_TAG);\n            if (!ed) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            RtlCopyMemory(ed, &ext->extent_data, ext->datalen);\n\n            Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_EXTENT_DATA, ext->offset,\n                                            ed, ext->datalen, Batch_Insert);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n                goto end;\n            }\n\n            if (ed->type == EXTENT_TYPE_PREALLOC)\n                prealloc = true;\n\n            if (ed->type == EXTENT_TYPE_INLINE)\n                extents_inline = true;\n\n            if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_NO_HOLES)) {\n                if (ed->type == EXTENT_TYPE_INLINE)\n                    last_end = ext->offset + ed->decoded_size;\n                else {\n                    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n\n                    last_end = ext->offset + ed2->num_bytes;\n                }\n            }\n\n            le = le->Flink;\n        }\n\n        if (!(fcb->Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_NO_HOLES) && !extents_inline &&\n            sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size) > last_end) {\n            Status = insert_sparse_extent(fcb, batchlist, last_end, sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size) - last_end);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_sparse_extent returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        // update prealloc flag in INODE_ITEM\n\n        if (!prealloc)\n            fcb->inode_item.flags &= ~BTRFS_INODE_PREALLOC;\n        else\n            fcb->inode_item.flags |= BTRFS_INODE_PREALLOC;\n\n        fcb->inode_item_changed = true;\n\n        fcb->extents_changed = false;\n    }\n\n    if ((!fcb->created && fcb->inode_item_changed) || cache) {\n        searchkey.obj_id = fcb->inode;\n        searchkey.obj_type = TYPE_INODE_ITEM;\n        searchkey.offset = 0xffffffffffffffff;\n\n        Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n            if (cache) {\n                ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);\n                if (!ii) {\n                    ERR(\"out of memory\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto end;\n                }\n\n                RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));\n\n                Status = insert_tree_item(fcb->Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                    goto end;\n                }\n\n                ii_offset = 0;\n            } else {\n                ERR(\"could not find INODE_ITEM for inode %I64x in subvol %I64x\\n\", fcb->inode, fcb->subvol->id);\n                Status = STATUS_INTERNAL_ERROR;\n                goto end;\n            }\n        } else {\n#ifdef DEBUG_PARANOID\n            INODE_ITEM* ii2 = (INODE_ITEM*)tp.item->data;\n\n            old_size = ii2->st_size;\n#endif\n\n            ii_offset = tp.item->key.offset;\n        }\n\n        if (!cache) {\n            Status = delete_tree_item(fcb->Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                goto end;\n            }\n        } else {\n            searchkey.obj_id = fcb->inode;\n            searchkey.obj_type = TYPE_INODE_ITEM;\n            searchkey.offset = ii_offset;\n\n            Status = find_item(fcb->Vcb, fcb->subvol, &tp, &searchkey, false, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"error - find_item returned %08lx\\n\", Status);\n                goto end;\n            }\n\n            if (keycmp(tp.item->key, searchkey)) {\n                ERR(\"could not find INODE_ITEM for inode %I64x in subvol %I64x\\n\", fcb->inode, fcb->subvol->id);\n                Status = STATUS_INTERNAL_ERROR;\n                goto end;\n            } else\n                RtlCopyMemory(tp.item->data, &fcb->inode_item, min(tp.item->size, sizeof(INODE_ITEM)));\n        }\n\n#ifdef DEBUG_PARANOID\n        if (!extents_changed && fcb->type != BTRFS_TYPE_DIRECTORY && old_size != fcb->inode_item.st_size) {\n            ERR(\"error - size has changed but extents not marked as changed\\n\");\n            int3;\n        }\n#endif\n    } else\n        ii_offset = 0;\n\n    fcb->created = false;\n\n    if (!cache && fcb->inode_item_changed) {\n        ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);\n        if (!ii) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        RtlCopyMemory(ii, &fcb->inode_item, sizeof(INODE_ITEM));\n\n        Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, fcb->inode, TYPE_INODE_ITEM, ii_offset, ii, sizeof(INODE_ITEM),\n                                        Batch_Insert);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        fcb->inode_item_changed = false;\n    }\n\n    if (fcb->sd_dirty) {\n        if (!fcb->sd_deleted) {\n            Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_NTACL, sizeof(EA_NTACL) - 1,\n                               EA_NTACL_HASH, (uint8_t*)fcb->sd, (uint16_t)RtlLengthSecurityDescriptor(fcb->sd));\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"set_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        } else {\n            Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_NTACL, sizeof(EA_NTACL) - 1, EA_NTACL_HASH);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        fcb->sd_deleted = false;\n        fcb->sd_dirty = false;\n    }\n\n    if (fcb->atts_changed) {\n        if (!fcb->atts_deleted) {\n            uint8_t val[16], *val2;\n            ULONG atts = fcb->atts;\n\n            TRACE(\"inserting new DOSATTRIB xattr\\n\");\n\n            if (fcb->inode == SUBVOL_ROOT_INODE)\n                atts &= ~FILE_ATTRIBUTE_READONLY;\n\n            val2 = &val[sizeof(val) - 1];\n\n            do {\n                uint8_t c = atts % 16;\n                *val2 = c <= 9 ? (c + '0') : (c - 0xa + 'a');\n\n                val2--;\n                atts >>= 4;\n            } while (atts != 0);\n\n            *val2 = 'x';\n            val2--;\n            *val2 = '0';\n\n            Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_DOSATTRIB, sizeof(EA_DOSATTRIB) - 1,\n                               EA_DOSATTRIB_HASH, val2, (uint16_t)(val + sizeof(val) - val2));\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"set_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        } else {\n            Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_DOSATTRIB, sizeof(EA_DOSATTRIB) - 1, EA_DOSATTRIB_HASH);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        fcb->atts_changed = false;\n        fcb->atts_deleted = false;\n    }\n\n    if (fcb->reparse_xattr_changed) {\n        if (fcb->reparse_xattr.Buffer && fcb->reparse_xattr.Length > 0) {\n            Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_REPARSE, sizeof(EA_REPARSE) - 1,\n                               EA_REPARSE_HASH, (uint8_t*)fcb->reparse_xattr.Buffer, (uint16_t)fcb->reparse_xattr.Length);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"set_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        } else {\n            Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_REPARSE, sizeof(EA_REPARSE) - 1, EA_REPARSE_HASH);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        fcb->reparse_xattr_changed = false;\n    }\n\n    if (fcb->ea_changed) {\n        if (fcb->ea_xattr.Buffer && fcb->ea_xattr.Length > 0) {\n            Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_EA, sizeof(EA_EA) - 1,\n                               EA_EA_HASH, (uint8_t*)fcb->ea_xattr.Buffer, (uint16_t)fcb->ea_xattr.Length);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"set_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        } else {\n            Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_EA, sizeof(EA_EA) - 1, EA_EA_HASH);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        fcb->ea_changed = false;\n    }\n\n    if (fcb->prop_compression_changed) {\n        if (fcb->prop_compression == PropCompression_None) {\n            Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1, EA_PROP_COMPRESSION_HASH);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        } else if (fcb->prop_compression == PropCompression_Zlib) {\n            static const char zlib[] = \"zlib\";\n\n            Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1,\n                               EA_PROP_COMPRESSION_HASH, (uint8_t*)zlib, sizeof(zlib) - 1);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"set_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        } else if (fcb->prop_compression == PropCompression_LZO) {\n            static const char lzo[] = \"lzo\";\n\n            Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1,\n                               EA_PROP_COMPRESSION_HASH, (uint8_t*)lzo, sizeof(lzo) - 1);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"set_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        } else if (fcb->prop_compression == PropCompression_ZSTD) {\n            static const char zstd[] = \"zstd\";\n\n            Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1,\n                               EA_PROP_COMPRESSION_HASH, (uint8_t*)zstd, sizeof(zstd) - 1);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"set_xattr returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n        fcb->prop_compression_changed = false;\n    }\n\n    if (fcb->xattrs_changed) {\n        LIST_ENTRY* le;\n\n        le = fcb->xattrs.Flink;\n        while (le != &fcb->xattrs) {\n            xattr* xa = CONTAINING_RECORD(le, xattr, list_entry);\n            LIST_ENTRY* le2 = le->Flink;\n\n            if (xa->dirty) {\n                uint32_t hash = calc_crc32c(0xfffffffe, (uint8_t*)xa->data, xa->namelen);\n\n                if (xa->valuelen == 0) {\n                    Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, xa->data, xa->namelen, hash);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_xattr returned %08lx\\n\", Status);\n                        goto end;\n                    }\n\n                    RemoveEntryList(&xa->list_entry);\n                    ExFreePool(xa);\n                } else {\n                    Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, xa->data, xa->namelen,\n                                       hash, (uint8_t*)&xa->data[xa->namelen], xa->valuelen);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"set_xattr returned %08lx\\n\", Status);\n                        goto end;\n                    }\n\n                    xa->dirty = false;\n                }\n            }\n\n            le = le2;\n        }\n\n        fcb->xattrs_changed = false;\n    }\n\n    if ((fcb->case_sensitive_set && !fcb->case_sensitive)) {\n        Status = delete_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_CASE_SENSITIVE,\n                              sizeof(EA_CASE_SENSITIVE) - 1, EA_CASE_SENSITIVE_HASH);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_xattr returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        fcb->case_sensitive_set = false;\n    } else if ((!fcb->case_sensitive_set && fcb->case_sensitive)) {\n        Status = set_xattr(fcb->Vcb, batchlist, fcb->subvol, fcb->inode, EA_CASE_SENSITIVE,\n                           sizeof(EA_CASE_SENSITIVE) - 1, EA_CASE_SENSITIVE_HASH, (uint8_t*)\"1\", 1);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"set_xattr returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        fcb->case_sensitive_set = true;\n    }\n\n    if (fcb->inode_item.st_nlink == 0 && !fcb->marked_as_orphan) { // mark as orphan\n        Status = insert_tree_item_batch(batchlist, fcb->Vcb, fcb->subvol, BTRFS_ORPHAN_INODE_OBJID, TYPE_ORPHAN_INODE,\n                                        fcb->inode, NULL, 0, Batch_Insert);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        fcb->marked_as_orphan = true;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (fcb->dirty) {\n        bool lock = false;\n\n        fcb->dirty = false;\n\n        if (!ExIsResourceAcquiredExclusiveLite(&fcb->Vcb->dirty_fcbs_lock)) {\n            ExAcquireResourceExclusiveLite(&fcb->Vcb->dirty_fcbs_lock, true);\n            lock = true;\n        }\n\n        RemoveEntryList(&fcb->list_entry_dirty);\n\n        if (lock)\n            ExReleaseResourceLite(&fcb->Vcb->dirty_fcbs_lock);\n    }\n\n    return Status;\n}\n\nvoid add_trim_entry_avoid_sb(device_extension* Vcb, device* dev, uint64_t address, uint64_t size) {\n    int i;\n    ULONG sblen = (ULONG)sector_align(sizeof(superblock), Vcb->superblock.sector_size);\n\n    i = 0;\n    while (superblock_addrs[i] != 0) {\n        if (superblock_addrs[i] + sblen >= address && superblock_addrs[i] < address + size) {\n            if (superblock_addrs[i] > address)\n                add_trim_entry(dev, address, superblock_addrs[i] - address);\n\n            if (size <= superblock_addrs[i] + sblen - address)\n                return;\n\n            size -= superblock_addrs[i] + sblen - address;\n            address = superblock_addrs[i] + sblen;\n        } else if (superblock_addrs[i] > address + size)\n            break;\n\n        i++;\n    }\n\n    add_trim_entry(dev, address, size);\n}\n\nstatic NTSTATUS drop_chunk(device_extension* Vcb, chunk* c, LIST_ENTRY* batchlist, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    uint64_t i, factor;\n    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];;\n\n    TRACE(\"dropping chunk %I64x\\n\", c->offset);\n\n    if (c->chunk_item->type & BLOCK_FLAG_RAID0)\n        factor = c->chunk_item->num_stripes;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID10)\n        factor = c->chunk_item->num_stripes / c->chunk_item->sub_stripes;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID5)\n        factor = c->chunk_item->num_stripes - 1;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID6)\n        factor = c->chunk_item->num_stripes - 2;\n    else // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4\n        factor = 1;\n\n    // do TRIM\n    if (Vcb->trim && !Vcb->options.no_trim) {\n        uint64_t len = c->chunk_item->size / factor;\n\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            if (c->devices[i] && c->devices[i]->devobj && !c->devices[i]->readonly && c->devices[i]->trim)\n                add_trim_entry_avoid_sb(Vcb, c->devices[i], cis[i].offset, len);\n        }\n    }\n\n    if (!c->cache) {\n        Status = load_stored_free_space_cache(Vcb, c, true, Irp);\n\n        if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND)\n            WARN(\"load_stored_free_space_cache returned %08lx\\n\", Status);\n    }\n\n    // remove free space cache\n    if (c->cache) {\n        c->cache->deleted = true;\n\n        Status = excise_extents(Vcb, c->cache, 0, c->cache->inode_item.st_size, Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"excise_extents returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        Status = flush_fcb(c->cache, true, batchlist, Irp);\n\n        free_fcb(c->cache);\n\n        if (c->cache->refcount == 0)\n            reap_fcb(c->cache);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"flush_fcb returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        searchkey.obj_id = FREE_SPACE_CACHE_ID;\n        searchkey.obj_type = 0;\n        searchkey.offset = c->offset;\n\n        Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (!keycmp(tp.item->key, searchkey)) {\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n    }\n\n    if (Vcb->space_root) {\n        Status = insert_tree_item_batch(batchlist, Vcb, Vcb->space_root, c->offset, TYPE_FREE_SPACE_INFO, c->chunk_item->size,\n                                        NULL, 0, Batch_DeleteFreeSpace);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (!c->created) {\n            // remove DEV_EXTENTs from tree 4\n            searchkey.obj_id = cis[i].dev_id;\n            searchkey.obj_type = TYPE_DEV_EXTENT;\n            searchkey.offset = cis[i].offset;\n\n            Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"error - find_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (!keycmp(tp.item->key, searchkey)) {\n                Status = delete_tree_item(Vcb, &tp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                if (tp.item->size >= sizeof(DEV_EXTENT)) {\n                    DEV_EXTENT* de = (DEV_EXTENT*)tp.item->data;\n\n                    c->devices[i]->devitem.bytes_used -= de->length;\n\n                    if (Vcb->balance.thread && Vcb->balance.shrinking && Vcb->balance.opts[0].devid == c->devices[i]->devitem.dev_id) {\n                        if (cis[i].offset < Vcb->balance.opts[0].drange_start && cis[i].offset + de->length > Vcb->balance.opts[0].drange_start)\n                            space_list_add2(&c->devices[i]->space, NULL, cis[i].offset, Vcb->balance.opts[0].drange_start - cis[i].offset, NULL, rollback);\n                    } else\n                        space_list_add2(&c->devices[i]->space, NULL, cis[i].offset, de->length, NULL, rollback);\n                }\n            } else\n                WARN(\"could not find (%I64x,%x,%I64x) in dev tree\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n        } else {\n            uint64_t len = c->chunk_item->size / factor;\n\n            c->devices[i]->devitem.bytes_used -= len;\n\n            if (Vcb->balance.thread && Vcb->balance.shrinking && Vcb->balance.opts[0].devid == c->devices[i]->devitem.dev_id) {\n                if (cis[i].offset < Vcb->balance.opts[0].drange_start && cis[i].offset + len > Vcb->balance.opts[0].drange_start)\n                    space_list_add2(&c->devices[i]->space, NULL, cis[i].offset, Vcb->balance.opts[0].drange_start - cis[i].offset, NULL, rollback);\n            } else\n                space_list_add2(&c->devices[i]->space, NULL, cis[i].offset, len, NULL, rollback);\n        }\n    }\n\n    // modify DEV_ITEMs in chunk tree\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (c->devices[i]) {\n            uint64_t j;\n            DEV_ITEM* di;\n\n            searchkey.obj_id = 1;\n            searchkey.obj_type = TYPE_DEV_ITEM;\n            searchkey.offset = c->devices[i]->devitem.dev_id;\n\n            Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"error - find_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (!keycmp(tp.item->key, searchkey)) {\n                Status = delete_tree_item(Vcb, &tp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                di = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_ITEM), ALLOC_TAG);\n                if (!di) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(di, &c->devices[i]->devitem, sizeof(DEV_ITEM));\n\n                Status = insert_tree_item(Vcb, Vcb->chunk_root, 1, TYPE_DEV_ITEM, c->devices[i]->devitem.dev_id, di, sizeof(DEV_ITEM), NULL, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n\n            for (j = i + 1; j < c->chunk_item->num_stripes; j++) {\n                if (c->devices[j] == c->devices[i])\n                    c->devices[j] = NULL;\n            }\n        }\n    }\n\n    if (!c->created) {\n        // remove CHUNK_ITEM from chunk tree\n        searchkey.obj_id = 0x100;\n        searchkey.obj_type = TYPE_CHUNK_ITEM;\n        searchkey.offset = c->offset;\n\n        Status = find_item(Vcb, Vcb->chunk_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (!keycmp(tp.item->key, searchkey)) {\n            Status = delete_tree_item(Vcb, &tp);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n        } else\n            WARN(\"could not find CHUNK_ITEM for chunk %I64x\\n\", c->offset);\n\n        // remove BLOCK_GROUP_ITEM from extent tree\n        searchkey.obj_id = c->offset;\n        searchkey.obj_type = TYPE_BLOCK_GROUP_ITEM;\n        searchkey.offset = 0xffffffffffffffff;\n\n        Status = find_item(Vcb, Vcb->block_group_root ? Vcb->block_group_root : Vcb->extent_root,\n                           &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n            Status = delete_tree_item(Vcb, &tp);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n        } else\n            WARN(\"could not find BLOCK_GROUP_ITEM for chunk %I64x\\n\", c->offset);\n    }\n\n    if (c->chunk_item->type & BLOCK_FLAG_SYSTEM)\n        remove_from_bootstrap(Vcb, 0x100, TYPE_CHUNK_ITEM, c->offset);\n\n    RemoveEntryList(&c->list_entry);\n\n    // clear raid56 incompat flag if dropping last RAID5/6 chunk\n\n    if (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6) {\n        LIST_ENTRY* le;\n        bool clear_flag = true;\n\n        le = Vcb->chunks.Flink;\n        while (le != &Vcb->chunks) {\n            chunk* c2 = CONTAINING_RECORD(le, chunk, list_entry);\n\n            if (c2->chunk_item->type & BLOCK_FLAG_RAID5 || c2->chunk_item->type & BLOCK_FLAG_RAID6) {\n                clear_flag = false;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        if (clear_flag)\n            Vcb->superblock.incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_RAID56;\n    }\n\n    // clear raid1c34 incompat flag if dropping last RAID5/6 chunk\n\n    if (c->chunk_item->type & BLOCK_FLAG_RAID1C3 || c->chunk_item->type & BLOCK_FLAG_RAID1C4) {\n        LIST_ENTRY* le;\n        bool clear_flag = true;\n\n        le = Vcb->chunks.Flink;\n        while (le != &Vcb->chunks) {\n            chunk* c2 = CONTAINING_RECORD(le, chunk, list_entry);\n\n            if (c2->chunk_item->type & BLOCK_FLAG_RAID1C3 || c2->chunk_item->type & BLOCK_FLAG_RAID1C4) {\n                clear_flag = false;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        if (clear_flag)\n            Vcb->superblock.incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_RAID1C34;\n    }\n\n    Vcb->superblock.bytes_used -= c->oldused;\n\n    ExFreePool(c->chunk_item);\n    ExFreePool(c->devices);\n\n    while (!IsListEmpty(&c->space)) {\n        space* s = CONTAINING_RECORD(c->space.Flink, space, list_entry);\n\n        RemoveEntryList(&s->list_entry);\n        ExFreePool(s);\n    }\n\n    while (!IsListEmpty(&c->deleting)) {\n        space* s = CONTAINING_RECORD(c->deleting.Flink, space, list_entry);\n\n        RemoveEntryList(&s->list_entry);\n        ExFreePool(s);\n    }\n\n    release_chunk_lock(c, Vcb);\n\n    ExDeleteResourceLite(&c->partial_stripes_lock);\n    ExDeleteResourceLite(&c->range_locks_lock);\n    ExDeleteResourceLite(&c->lock);\n    ExDeleteResourceLite(&c->changed_extents_lock);\n\n    ExFreePool(c);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS partial_stripe_read(device_extension* Vcb, chunk* c, partial_stripe* ps, uint64_t startoff, uint16_t parity, ULONG offset, ULONG len) {\n    NTSTATUS Status;\n    ULONG sl = (ULONG)(c->chunk_item->stripe_length >> Vcb->sector_shift);\n    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n\n    while (len > 0) {\n        ULONG readlen = min(offset + len, offset + (sl - (offset % sl))) - offset;\n        uint16_t stripe;\n\n        stripe = (parity + (offset / sl) + 1) % c->chunk_item->num_stripes;\n\n        if (c->devices[stripe]->devobj) {\n            Status = sync_read_phys(c->devices[stripe]->devobj, c->devices[stripe]->fileobj, cis[stripe].offset + startoff + ((offset % sl) << Vcb->sector_shift),\n                                    readlen << Vcb->sector_shift, ps->data + (offset << Vcb->sector_shift), false);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"sync_read_phys returned %08lx\\n\", Status);\n                return Status;\n            }\n        } else if (c->chunk_item->type & BLOCK_FLAG_RAID5) {\n            uint16_t i;\n            uint8_t* scratch;\n\n            scratch = ExAllocatePoolWithTag(NonPagedPool, readlen << Vcb->sector_shift, ALLOC_TAG);\n            if (!scratch) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            for (i = 0; i < c->chunk_item->num_stripes; i++) {\n                if (i != stripe) {\n                    if (!c->devices[i]->devobj) {\n                        ExFreePool(scratch);\n                        return STATUS_UNEXPECTED_IO_ERROR;\n                    }\n\n                    if (i == 0 || (stripe == 0 && i == 1)) {\n                        Status = sync_read_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + startoff + ((offset % sl) << Vcb->sector_shift),\n                                                readlen << Vcb->sector_shift, ps->data + (offset << Vcb->sector_shift), false);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"sync_read_phys returned %08lx\\n\", Status);\n                            ExFreePool(scratch);\n                            return Status;\n                        }\n                    } else {\n                        Status = sync_read_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + startoff + ((offset % sl) << Vcb->sector_shift),\n                                                readlen << Vcb->sector_shift, scratch, false);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"sync_read_phys returned %08lx\\n\", Status);\n                            ExFreePool(scratch);\n                            return Status;\n                        }\n\n                        do_xor(ps->data + (offset << Vcb->sector_shift), scratch, readlen << Vcb->sector_shift);\n                    }\n                }\n            }\n\n            ExFreePool(scratch);\n        } else {\n            uint8_t* scratch;\n            uint16_t k, i, logstripe, error_stripe, num_errors = 0;\n\n            scratch = ExAllocatePoolWithTag(NonPagedPool, (c->chunk_item->num_stripes + 2) * readlen << Vcb->sector_shift, ALLOC_TAG);\n            if (!scratch) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            i = (parity + 1) % c->chunk_item->num_stripes;\n            logstripe = (c->chunk_item->num_stripes + c->chunk_item->num_stripes - 1 - parity + stripe) % c->chunk_item->num_stripes;\n\n            for (k = 0; k < c->chunk_item->num_stripes; k++) {\n                if (i != stripe) {\n                    if (c->devices[i]->devobj) {\n                        Status = sync_read_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + startoff + ((offset % sl) << Vcb->sector_shift),\n                                                readlen << Vcb->sector_shift, scratch + (k * readlen << Vcb->sector_shift), false);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"sync_read_phys returned %08lx\\n\", Status);\n                            num_errors++;\n                            error_stripe = k;\n                        }\n                    } else {\n                        num_errors++;\n                        error_stripe = k;\n                    }\n\n                    if (num_errors > 1) {\n                        ExFreePool(scratch);\n                        return STATUS_UNEXPECTED_IO_ERROR;\n                    }\n                }\n\n                i = (i + 1) % c->chunk_item->num_stripes;\n            }\n\n            if (num_errors == 0 || error_stripe == c->chunk_item->num_stripes - 1) {\n                for (k = 0; k < c->chunk_item->num_stripes - 1; k++) {\n                    if (k != logstripe) {\n                        if (k == 0 || (k == 1 && logstripe == 0)) {\n                            RtlCopyMemory(ps->data + (offset << Vcb->sector_shift), scratch + (k * readlen << Vcb->sector_shift),\n                                          readlen << Vcb->sector_shift);\n                        } else {\n                            do_xor(ps->data + (offset << Vcb->sector_shift), scratch + (k * readlen << Vcb->sector_shift),\n                                   readlen << Vcb->sector_shift);\n                        }\n                    }\n                }\n            } else {\n                raid6_recover2(scratch, c->chunk_item->num_stripes, readlen << Vcb->sector_shift, logstripe,\n                               error_stripe, scratch + (c->chunk_item->num_stripes * readlen << Vcb->sector_shift));\n\n                RtlCopyMemory(ps->data + (offset << Vcb->sector_shift), scratch + (c->chunk_item->num_stripes * readlen << Vcb->sector_shift),\n                              readlen << Vcb->sector_shift);\n            }\n\n            ExFreePool(scratch);\n        }\n\n        offset += readlen;\n        len -= readlen;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS flush_partial_stripe(device_extension* Vcb, chunk* c, partial_stripe* ps) {\n    NTSTATUS Status;\n    uint16_t parity2, stripe, startoffstripe;\n    uint8_t* data;\n    uint64_t startoff;\n    ULONG runlength, index, last1;\n    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n    LIST_ENTRY* le;\n    uint16_t k, num_data_stripes = c->chunk_item->num_stripes - (c->chunk_item->type & BLOCK_FLAG_RAID5 ? 1 : 2);\n    uint64_t ps_length = num_data_stripes * c->chunk_item->stripe_length;\n    ULONG stripe_length = (ULONG)c->chunk_item->stripe_length;\n\n    // FIXME - do writes asynchronously?\n\n    get_raid0_offset(ps->address - c->offset, stripe_length, num_data_stripes, &startoff, &startoffstripe);\n\n    parity2 = (((ps->address - c->offset) / ps_length) + c->chunk_item->num_stripes - 1) % c->chunk_item->num_stripes;\n\n    // read data (or reconstruct if degraded)\n\n    runlength = RtlFindFirstRunClear(&ps->bmp, &index);\n    last1 = 0;\n\n    while (runlength != 0) {\n        if (index >= ps->bmplen)\n            break;\n\n        if (index + runlength >= ps->bmplen) {\n            runlength = ps->bmplen - index;\n\n            if (runlength == 0)\n                break;\n        }\n\n        if (index > last1) {\n            Status = partial_stripe_read(Vcb, c, ps, startoff, parity2, last1, index - last1);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"partial_stripe_read returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        last1 = index + runlength;\n\n        runlength = RtlFindNextForwardRunClear(&ps->bmp, index + runlength, &index);\n    }\n\n    if (last1 < ps_length >> Vcb->sector_shift) {\n        Status = partial_stripe_read(Vcb, c, ps, startoff, parity2, last1, (ULONG)((ps_length >> Vcb->sector_shift) - last1));\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"partial_stripe_read returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    // set unallocated data to 0\n    le = c->space.Flink;\n    while (le != &c->space) {\n        space* s = CONTAINING_RECORD(le, space, list_entry);\n\n        if (s->address + s->size > ps->address && s->address < ps->address + ps_length) {\n            uint64_t start = max(ps->address, s->address);\n            uint64_t end = min(ps->address + ps_length, s->address + s->size);\n\n            RtlZeroMemory(ps->data + start - ps->address, (ULONG)(end - start));\n        } else if (s->address >= ps->address + ps_length)\n            break;\n\n        le = le->Flink;\n    }\n\n    le = c->deleting.Flink;\n    while (le != &c->deleting) {\n        space* s = CONTAINING_RECORD(le, space, list_entry);\n\n        if (s->address + s->size > ps->address && s->address < ps->address + ps_length) {\n            uint64_t start = max(ps->address, s->address);\n            uint64_t end = min(ps->address + ps_length, s->address + s->size);\n\n            RtlZeroMemory(ps->data + start - ps->address, (ULONG)(end - start));\n        } else if (s->address >= ps->address + ps_length)\n            break;\n\n        le = le->Flink;\n    }\n\n    stripe = (parity2 + 1) % c->chunk_item->num_stripes;\n\n    data = ps->data;\n    for (k = 0; k < num_data_stripes; k++) {\n        if (c->devices[stripe]->devobj) {\n            Status = write_data_phys(c->devices[stripe]->devobj, c->devices[stripe]->fileobj, cis[stripe].offset + startoff, data, stripe_length);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"write_data_phys returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        data += stripe_length;\n        stripe = (stripe + 1) % c->chunk_item->num_stripes;\n    }\n\n    // write parity\n    if (c->chunk_item->type & BLOCK_FLAG_RAID5) {\n        if (c->devices[parity2]->devobj) {\n            uint16_t i;\n\n            for (i = 1; i < c->chunk_item->num_stripes - 1; i++) {\n                do_xor(ps->data, ps->data + (i * stripe_length), stripe_length);\n            }\n\n            Status = write_data_phys(c->devices[parity2]->devobj, c->devices[parity2]->fileobj, cis[parity2].offset + startoff, ps->data, stripe_length);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"write_data_phys returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n    } else {\n        uint16_t parity1 = (parity2 + c->chunk_item->num_stripes - 1) % c->chunk_item->num_stripes;\n\n        if (c->devices[parity1]->devobj || c->devices[parity2]->devobj) {\n            uint8_t* scratch;\n            uint16_t i;\n\n            scratch = ExAllocatePoolWithTag(NonPagedPool, stripe_length * 2, ALLOC_TAG);\n            if (!scratch) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            i = c->chunk_item->num_stripes - 3;\n\n            while (true) {\n                if (i == c->chunk_item->num_stripes - 3) {\n                    RtlCopyMemory(scratch, ps->data + (i * stripe_length), stripe_length);\n                    RtlCopyMemory(scratch + stripe_length, ps->data + (i * stripe_length), stripe_length);\n                } else {\n                    do_xor(scratch, ps->data + (i * stripe_length), stripe_length);\n\n                    galois_double(scratch + stripe_length, stripe_length);\n                    do_xor(scratch + stripe_length, ps->data + (i * stripe_length), stripe_length);\n                }\n\n                if (i == 0)\n                    break;\n\n                i--;\n            }\n\n            if (c->devices[parity1]->devobj) {\n                Status = write_data_phys(c->devices[parity1]->devobj, c->devices[parity1]->fileobj, cis[parity1].offset + startoff, scratch, stripe_length);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"write_data_phys returned %08lx\\n\", Status);\n                    ExFreePool(scratch);\n                    return Status;\n                }\n            }\n\n            if (c->devices[parity2]->devobj) {\n                Status = write_data_phys(c->devices[parity2]->devobj, c->devices[parity2]->fileobj, cis[parity2].offset + startoff,\n                                         scratch + stripe_length, stripe_length);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"write_data_phys returned %08lx\\n\", Status);\n                    ExFreePool(scratch);\n                    return Status;\n                }\n            }\n\n            ExFreePool(scratch);\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS update_chunks(device_extension* Vcb, LIST_ENTRY* batchlist, PIRP Irp, LIST_ENTRY* rollback) {\n    LIST_ENTRY *le, *le2;\n    NTSTATUS Status;\n    uint64_t used_minus_cache;\n\n    ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true);\n\n    // FIXME - do tree chunks before data chunks\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        le2 = le->Flink;\n\n        if (c->changed) {\n            acquire_chunk_lock(c, Vcb);\n\n            // flush partial stripes\n            if (!Vcb->readonly && (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6)) {\n                ExAcquireResourceExclusiveLite(&c->partial_stripes_lock, true);\n\n                while (!IsListEmpty(&c->partial_stripes)) {\n                    partial_stripe* ps = CONTAINING_RECORD(RemoveHeadList(&c->partial_stripes), partial_stripe, list_entry);\n\n                    Status = flush_partial_stripe(Vcb, c, ps);\n\n                    if (ps->bmparr)\n                        ExFreePool(ps->bmparr);\n\n                    ExFreePool(ps);\n\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"flush_partial_stripe returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&c->partial_stripes_lock);\n                        release_chunk_lock(c, Vcb);\n                        ExReleaseResourceLite(&Vcb->chunk_lock);\n                        return Status;\n                    }\n                }\n\n                ExReleaseResourceLite(&c->partial_stripes_lock);\n            }\n\n            if (c->list_entry_balance.Flink) {\n                release_chunk_lock(c, Vcb);\n                le = le2;\n                continue;\n            }\n\n            if (c->space_changed || c->created) {\n                bool created = c->created;\n\n                used_minus_cache = c->used;\n\n                // subtract self-hosted cache\n                if (used_minus_cache > 0 && c->chunk_item->type & BLOCK_FLAG_DATA && c->cache && c->cache->inode_item.st_size == c->used) {\n                    LIST_ENTRY* le3;\n\n                    le3 = c->cache->extents.Flink;\n                    while (le3 != &c->cache->extents) {\n                        extent* ext = CONTAINING_RECORD(le3, extent, list_entry);\n                        EXTENT_DATA* ed = &ext->extent_data;\n\n                        if (!ext->ignore) {\n                            if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) {\n                                EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n\n                                if (ed2->size != 0 && ed2->address >= c->offset && ed2->address + ed2->size <= c->offset + c->chunk_item->size)\n                                    used_minus_cache -= ed2->size;\n                            }\n                        }\n\n                        le3 = le3->Flink;\n                    }\n                }\n\n                if (used_minus_cache == 0) {\n                    Status = drop_chunk(Vcb, c, batchlist, Irp, rollback);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"drop_chunk returned %08lx\\n\", Status);\n                        release_chunk_lock(c, Vcb);\n                        ExReleaseResourceLite(&Vcb->chunk_lock);\n                        return Status;\n                    }\n\n                    // c is now freed, so avoid releasing non-existent lock\n                    le = le2;\n                    continue;\n                } else if (c->created) {\n                    Status = create_chunk(Vcb, c, Irp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"create_chunk returned %08lx\\n\", Status);\n                        release_chunk_lock(c, Vcb);\n                        ExReleaseResourceLite(&Vcb->chunk_lock);\n                        return Status;\n                    }\n                }\n\n                if (used_minus_cache > 0 || created)\n                    release_chunk_lock(c, Vcb);\n            } else\n                release_chunk_lock(c, Vcb);\n        }\n\n        le = le2;\n    }\n\n    ExReleaseResourceLite(&Vcb->chunk_lock);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS delete_root_ref(device_extension* Vcb, uint64_t subvolid, uint64_t parsubvolid, uint64_t parinode, PANSI_STRING utf8, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n\n    searchkey.obj_id = parsubvolid;\n    searchkey.obj_type = TYPE_ROOT_REF;\n    searchkey.offset = subvolid;\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (!keycmp(searchkey, tp.item->key)) {\n        if (tp.item->size < sizeof(ROOT_REF)) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(ROOT_REF));\n            return STATUS_INTERNAL_ERROR;\n        } else {\n            ROOT_REF* rr;\n            ULONG len;\n\n            rr = (ROOT_REF*)tp.item->data;\n            len = tp.item->size;\n\n            do {\n                uint16_t itemlen;\n\n                if (len < sizeof(ROOT_REF) || len < offsetof(ROOT_REF, name[0]) + rr->n) {\n                    ERR(\"(%I64x,%x,%I64x) was truncated\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n                    break;\n                }\n\n                itemlen = (uint16_t)offsetof(ROOT_REF, name[0]) + rr->n;\n\n                if (rr->dir == parinode && rr->n == utf8->Length && RtlCompareMemory(rr->name, utf8->Buffer, rr->n) == rr->n) {\n                    uint16_t newlen = tp.item->size - itemlen;\n\n                    Status = delete_tree_item(Vcb, &tp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    if (newlen == 0) {\n                        TRACE(\"deleting (%I64x,%x,%I64x)\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n                    } else {\n                        uint8_t *newrr = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *rroff;\n\n                        if (!newrr) {\n                            ERR(\"out of memory\\n\");\n                            return STATUS_INSUFFICIENT_RESOURCES;\n                        }\n\n                        TRACE(\"modifying (%I64x,%x,%I64x)\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n\n                        if ((uint8_t*)rr > tp.item->data) {\n                            RtlCopyMemory(newrr, tp.item->data, (uint8_t*)rr - tp.item->data);\n                            rroff = newrr + ((uint8_t*)rr - tp.item->data);\n                        } else {\n                            rroff = newrr;\n                        }\n\n                        if ((uint8_t*)&rr->name[rr->n] < tp.item->data + tp.item->size)\n                            RtlCopyMemory(rroff, &rr->name[rr->n], tp.item->size - ((uint8_t*)&rr->name[rr->n] - tp.item->data));\n\n                        Status = insert_tree_item(Vcb, Vcb->root_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, newrr, newlen, NULL, Irp);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                            ExFreePool(newrr);\n                            return Status;\n                        }\n                    }\n\n                    break;\n                }\n\n                if (len > itemlen) {\n                    len -= itemlen;\n                    rr = (ROOT_REF*)&rr->name[rr->n];\n                } else\n                    break;\n            } while (len > 0);\n        }\n    } else {\n        WARN(\"could not find ROOT_REF entry for subvol %I64x in %I64x\\n\", searchkey.offset, searchkey.obj_id);\n        return STATUS_NOT_FOUND;\n    }\n\n    return STATUS_SUCCESS;\n}\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(suppress: 28194)\n#endif\nstatic NTSTATUS add_root_ref(_In_ device_extension* Vcb, _In_ uint64_t subvolid, _In_ uint64_t parsubvolid, _In_ __drv_aliasesMem ROOT_REF* rr, _In_opt_ PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n\n    searchkey.obj_id = parsubvolid;\n    searchkey.obj_type = TYPE_ROOT_REF;\n    searchkey.offset = subvolid;\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (!keycmp(searchkey, tp.item->key)) {\n        uint16_t rrsize = tp.item->size + (uint16_t)offsetof(ROOT_REF, name[0]) + rr->n;\n        uint8_t* rr2;\n\n        rr2 = ExAllocatePoolWithTag(PagedPool, rrsize, ALLOC_TAG);\n        if (!rr2) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        if (tp.item->size > 0)\n            RtlCopyMemory(rr2, tp.item->data, tp.item->size);\n\n        RtlCopyMemory(rr2 + tp.item->size, rr, offsetof(ROOT_REF, name[0]) + rr->n);\n        ExFreePool(rr);\n\n        Status = delete_tree_item(Vcb, &tp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            ExFreePool(rr2);\n            return Status;\n        }\n\n        Status = insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, rr2, rrsize, NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            ExFreePool(rr2);\n            return Status;\n        }\n    } else {\n        Status = insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, rr, (uint16_t)offsetof(ROOT_REF, name[0]) + rr->n, NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            ExFreePool(rr);\n            return Status;\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\nstatic NTSTATUS update_root_backref(device_extension* Vcb, uint64_t subvolid, uint64_t parsubvolid, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    uint8_t* data;\n    uint16_t datalen;\n    NTSTATUS Status;\n\n    searchkey.obj_id = parsubvolid;\n    searchkey.obj_type = TYPE_ROOT_REF;\n    searchkey.offset = subvolid;\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (!keycmp(tp.item->key, searchkey) && tp.item->size > 0) {\n        datalen = tp.item->size;\n\n        data = ExAllocatePoolWithTag(PagedPool, datalen, ALLOC_TAG);\n        if (!data) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(data, tp.item->data, datalen);\n    } else {\n        datalen = 0;\n        data = NULL;\n    }\n\n    searchkey.obj_id = subvolid;\n    searchkey.obj_type = TYPE_ROOT_BACKREF;\n    searchkey.offset = parsubvolid;\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n\n        if (datalen > 0)\n            ExFreePool(data);\n\n        return Status;\n    }\n\n    if (!keycmp(tp.item->key, searchkey)) {\n        Status = delete_tree_item(Vcb, &tp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n\n            if (datalen > 0)\n                ExFreePool(data);\n\n            return Status;\n        }\n    }\n\n    if (datalen > 0) {\n        Status = insert_tree_item(Vcb, Vcb->root_root, subvolid, TYPE_ROOT_BACKREF, parsubvolid, data, datalen, NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            ExFreePool(data);\n            return Status;\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS add_root_item_to_cache(device_extension* Vcb, uint64_t root, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n\n    searchkey.obj_id = root;\n    searchkey.obj_type = TYPE_ROOT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n        ERR(\"could not find ROOT_ITEM for tree %I64x\\n\", searchkey.obj_id);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (tp.item->size < sizeof(ROOT_ITEM)) { // if not full length, create new entry with new bits zeroed\n        ROOT_ITEM* ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);\n        if (!ri) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        if (tp.item->size > 0)\n            RtlCopyMemory(ri, tp.item->data, tp.item->size);\n\n        RtlZeroMemory(((uint8_t*)ri) + tp.item->size, sizeof(ROOT_ITEM) - tp.item->size);\n\n        Status = delete_tree_item(Vcb, &tp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            ExFreePool(ri);\n            return Status;\n        }\n\n        Status = insert_tree_item(Vcb, Vcb->root_root, searchkey.obj_id, searchkey.obj_type, tp.item->key.offset, ri, sizeof(ROOT_ITEM), NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            ExFreePool(ri);\n            return Status;\n        }\n    } else {\n        tp.tree->write = true;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS flush_fileref(file_ref* fileref, LIST_ENTRY* batchlist, PIRP Irp) {\n    NTSTATUS Status;\n\n    // if fileref created and then immediately deleted, do nothing\n    if (fileref->created && fileref->deleted) {\n        fileref->dirty = false;\n        return STATUS_SUCCESS;\n    }\n\n    if (fileref->fcb->ads) {\n        fileref->dirty = false;\n        return STATUS_SUCCESS;\n    }\n\n    if (fileref->created) {\n        uint16_t disize;\n        DIR_ITEM *di, *di2;\n        uint32_t crc32;\n\n        crc32 = calc_crc32c(0xfffffffe, (uint8_t*)fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);\n\n        disize = (uint16_t)(offsetof(DIR_ITEM, name[0]) + fileref->dc->utf8.Length);\n        di = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);\n        if (!di) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        if (fileref->parent->fcb->subvol == fileref->fcb->subvol) {\n            di->key.obj_id = fileref->fcb->inode;\n            di->key.obj_type = TYPE_INODE_ITEM;\n            di->key.offset = 0;\n        } else { // subvolume\n            di->key.obj_id = fileref->fcb->subvol->id;\n            di->key.obj_type = TYPE_ROOT_ITEM;\n            di->key.offset = 0xffffffffffffffff;\n        }\n\n        di->transid = fileref->fcb->Vcb->superblock.generation;\n        di->m = 0;\n        di->n = (uint16_t)fileref->dc->utf8.Length;\n        di->type = fileref->fcb->type;\n        RtlCopyMemory(di->name, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);\n\n        di2 = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);\n        if (!di2) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(di2, di, disize);\n\n        Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_INDEX,\n                                        fileref->dc->index, di, disize, Batch_Insert);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_ITEM, crc32,\n                                        di2, disize, Batch_DirItem);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (fileref->parent->fcb->subvol == fileref->fcb->subvol) {\n            INODE_REF* ir;\n\n            ir = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + fileref->dc->utf8.Length, ALLOC_TAG);\n            if (!ir) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            ir->index = fileref->dc->index;\n            ir->n = fileref->dc->utf8.Length;\n            RtlCopyMemory(ir->name, fileref->dc->utf8.Buffer, ir->n);\n\n            Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->fcb->subvol, fileref->fcb->inode, TYPE_INODE_REF, fileref->parent->fcb->inode,\n                                            ir, sizeof(INODE_REF) - 1 + ir->n, Batch_InodeRef);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n                return Status;\n            }\n        } else if (fileref->fcb != fileref->fcb->Vcb->dummy_fcb) {\n            ULONG rrlen;\n            ROOT_REF* rr;\n\n            rrlen = sizeof(ROOT_REF) - 1 + fileref->dc->utf8.Length;\n\n            rr = ExAllocatePoolWithTag(PagedPool, rrlen, ALLOC_TAG);\n            if (!rr) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            rr->dir = fileref->parent->fcb->inode;\n            rr->index = fileref->dc->index;\n            rr->n = fileref->dc->utf8.Length;\n            RtlCopyMemory(rr->name, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);\n\n            Status = add_root_ref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, rr, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"add_root_ref returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            Status = update_root_backref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"update_root_backref returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        fileref->created = false;\n    } else if (fileref->deleted) {\n        uint32_t crc32;\n        ANSI_STRING* name;\n        DIR_ITEM* di;\n\n        name = &fileref->oldutf8;\n\n        crc32 = calc_crc32c(0xfffffffe, (uint8_t*)name->Buffer, name->Length);\n\n        di = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + name->Length, ALLOC_TAG);\n        if (!di) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        di->m = 0;\n        di->n = name->Length;\n        RtlCopyMemory(di->name, name->Buffer, name->Length);\n\n        // delete DIR_ITEM (0x54)\n\n        Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_ITEM,\n                                        crc32, di, sizeof(DIR_ITEM) - 1 + name->Length, Batch_DeleteDirItem);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (fileref->parent->fcb->subvol == fileref->fcb->subvol) {\n            INODE_REF* ir;\n\n            // delete INODE_REF (0xc)\n\n            ir = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + name->Length, ALLOC_TAG);\n            if (!ir) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            ir->index = fileref->oldindex;\n            ir->n = name->Length;\n            RtlCopyMemory(ir->name, name->Buffer, name->Length);\n\n            Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->fcb->inode, TYPE_INODE_REF,\n                                            fileref->parent->fcb->inode, ir, sizeof(INODE_REF) - 1 + name->Length, Batch_DeleteInodeRef);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n                return Status;\n            }\n        } else if (fileref->fcb != fileref->fcb->Vcb->dummy_fcb) { // subvolume\n            Status = delete_root_ref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, fileref->parent->fcb->inode, name, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_root_ref returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            Status = update_root_backref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"update_root_backref returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        // delete DIR_INDEX (0x60)\n\n        Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_INDEX,\n                                        fileref->oldindex, NULL, 0, Batch_Delete);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (fileref->oldutf8.Buffer) {\n            ExFreePool(fileref->oldutf8.Buffer);\n            fileref->oldutf8.Buffer = NULL;\n        }\n    } else { // rename or change type\n        PANSI_STRING oldutf8 = fileref->oldutf8.Buffer ? &fileref->oldutf8 : &fileref->dc->utf8;\n        uint32_t crc32, oldcrc32;\n        uint16_t disize;\n        DIR_ITEM *olddi, *di, *di2;\n\n        crc32 = calc_crc32c(0xfffffffe, (uint8_t*)fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);\n\n        if (!fileref->oldutf8.Buffer)\n            oldcrc32 = crc32;\n        else\n            oldcrc32 = calc_crc32c(0xfffffffe, (uint8_t*)fileref->oldutf8.Buffer, fileref->oldutf8.Length);\n\n        olddi = ExAllocatePoolWithTag(PagedPool, sizeof(DIR_ITEM) - 1 + oldutf8->Length, ALLOC_TAG);\n        if (!olddi) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        olddi->m = 0;\n        olddi->n = (uint16_t)oldutf8->Length;\n        RtlCopyMemory(olddi->name, oldutf8->Buffer, oldutf8->Length);\n\n        // delete DIR_ITEM (0x54)\n\n        Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_ITEM,\n                                        oldcrc32, olddi, sizeof(DIR_ITEM) - 1 + oldutf8->Length, Batch_DeleteDirItem);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n            ExFreePool(olddi);\n            return Status;\n        }\n\n        // add DIR_ITEM (0x54)\n\n        disize = (uint16_t)(offsetof(DIR_ITEM, name[0]) + fileref->dc->utf8.Length);\n        di = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);\n        if (!di) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        di2 = ExAllocatePoolWithTag(PagedPool, disize, ALLOC_TAG);\n        if (!di2) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(di);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        if (fileref->dc)\n            di->key = fileref->dc->key;\n        else if (fileref->parent->fcb->subvol == fileref->fcb->subvol) {\n            di->key.obj_id = fileref->fcb->inode;\n            di->key.obj_type = TYPE_INODE_ITEM;\n            di->key.offset = 0;\n        } else { // subvolume\n            di->key.obj_id = fileref->fcb->subvol->id;\n            di->key.obj_type = TYPE_ROOT_ITEM;\n            di->key.offset = 0xffffffffffffffff;\n        }\n\n        di->transid = fileref->fcb->Vcb->superblock.generation;\n        di->m = 0;\n        di->n = (uint16_t)fileref->dc->utf8.Length;\n        di->type = fileref->fcb->type;\n        RtlCopyMemory(di->name, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);\n\n        RtlCopyMemory(di2, di, disize);\n\n        Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_ITEM, crc32,\n                                        di, disize, Batch_DirItem);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n            ExFreePool(di2);\n            ExFreePool(di);\n            return Status;\n        }\n\n        if (fileref->parent->fcb->subvol == fileref->fcb->subvol) {\n            INODE_REF *ir, *ir2;\n\n            // delete INODE_REF (0xc)\n\n            ir = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + oldutf8->Length, ALLOC_TAG);\n            if (!ir) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(di2);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            ir->index = fileref->dc->index;\n            ir->n = oldutf8->Length;\n            RtlCopyMemory(ir->name, oldutf8->Buffer, ir->n);\n\n            Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->fcb->subvol, fileref->fcb->inode, TYPE_INODE_REF, fileref->parent->fcb->inode,\n                                            ir, sizeof(INODE_REF) - 1 + ir->n, Batch_DeleteInodeRef);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n                ExFreePool(ir);\n                ExFreePool(di2);\n                return Status;\n            }\n\n            // add INODE_REF (0xc)\n\n            ir2 = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_REF) - 1 + fileref->dc->utf8.Length, ALLOC_TAG);\n            if (!ir2) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(di2);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            ir2->index = fileref->dc->index;\n            ir2->n = fileref->dc->utf8.Length;\n            RtlCopyMemory(ir2->name, fileref->dc->utf8.Buffer, ir2->n);\n\n            Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->fcb->subvol, fileref->fcb->inode, TYPE_INODE_REF, fileref->parent->fcb->inode,\n                                            ir2, sizeof(INODE_REF) - 1 + ir2->n, Batch_InodeRef);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n                ExFreePool(ir2);\n                ExFreePool(di2);\n                return Status;\n            }\n        } else if (fileref->fcb != fileref->fcb->Vcb->dummy_fcb) { // subvolume\n            ULONG rrlen;\n            ROOT_REF* rr;\n\n            Status = delete_root_ref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, fileref->parent->fcb->inode, oldutf8, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_root_ref returned %08lx\\n\", Status);\n                ExFreePool(di2);\n                return Status;\n            }\n\n            rrlen = sizeof(ROOT_REF) - 1 + fileref->dc->utf8.Length;\n\n            rr = ExAllocatePoolWithTag(PagedPool, rrlen, ALLOC_TAG);\n            if (!rr) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(di2);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            rr->dir = fileref->parent->fcb->inode;\n            rr->index = fileref->dc->index;\n            rr->n = fileref->dc->utf8.Length;\n            RtlCopyMemory(rr->name, fileref->dc->utf8.Buffer, fileref->dc->utf8.Length);\n\n            Status = add_root_ref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, rr, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"add_root_ref returned %08lx\\n\", Status);\n                ExFreePool(di2);\n                return Status;\n            }\n\n            Status = update_root_backref(fileref->fcb->Vcb, fileref->fcb->subvol->id, fileref->parent->fcb->subvol->id, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"update_root_backref returned %08lx\\n\", Status);\n                ExFreePool(di2);\n                return Status;\n            }\n        }\n\n        // delete DIR_INDEX (0x60)\n\n        Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_INDEX,\n                                        fileref->dc->index, NULL, 0, Batch_Delete);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n            ExFreePool(di2);\n            return Status;\n        }\n\n        // add DIR_INDEX (0x60)\n\n       Status = insert_tree_item_batch(batchlist, fileref->fcb->Vcb, fileref->parent->fcb->subvol, fileref->parent->fcb->inode, TYPE_DIR_INDEX,\n                                       fileref->dc->index, di2, disize, Batch_Insert);\n       if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item_batch returned %08lx\\n\", Status);\n            ExFreePool(di2);\n            return Status;\n        }\n\n        if (fileref->oldutf8.Buffer) {\n            ExFreePool(fileref->oldutf8.Buffer);\n            fileref->oldutf8.Buffer = NULL;\n        }\n    }\n\n    fileref->dirty = false;\n\n    return STATUS_SUCCESS;\n}\n\nstatic void flush_disk_caches(device_extension* Vcb) {\n    LIST_ENTRY* le;\n    ioctl_context context;\n    ULONG num;\n\n    context.left = 0;\n\n    le = Vcb->devices.Flink;\n\n    while (le != &Vcb->devices) {\n        device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n        if (dev->devobj && !dev->readonly && dev->can_flush)\n            context.left++;\n\n        le = le->Flink;\n    }\n\n    if (context.left == 0)\n        return;\n\n    num = 0;\n\n    KeInitializeEvent(&context.Event, NotificationEvent, false);\n\n    context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(ioctl_context_stripe) * context.left, ALLOC_TAG);\n    if (!context.stripes) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    RtlZeroMemory(context.stripes, sizeof(ioctl_context_stripe) * context.left);\n\n    le = Vcb->devices.Flink;\n\n    while (le != &Vcb->devices) {\n        device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n        if (dev->devobj && !dev->readonly && dev->can_flush) {\n            PIO_STACK_LOCATION IrpSp;\n            ioctl_context_stripe* stripe = &context.stripes[num];\n\n            RtlZeroMemory(&stripe->apte, sizeof(ATA_PASS_THROUGH_EX));\n\n            stripe->apte.Length = sizeof(ATA_PASS_THROUGH_EX);\n            stripe->apte.TimeOutValue = 5;\n            stripe->apte.CurrentTaskFile[6] = IDE_COMMAND_FLUSH_CACHE;\n\n            stripe->Irp = IoAllocateIrp(dev->devobj->StackSize, false);\n\n            if (!stripe->Irp) {\n                ERR(\"IoAllocateIrp failed\\n\");\n                goto nextdev;\n            }\n\n            IrpSp = IoGetNextIrpStackLocation(stripe->Irp);\n            IrpSp->MajorFunction = IRP_MJ_DEVICE_CONTROL;\n            IrpSp->FileObject = dev->fileobj;\n\n            IrpSp->Parameters.DeviceIoControl.IoControlCode = IOCTL_ATA_PASS_THROUGH;\n            IrpSp->Parameters.DeviceIoControl.InputBufferLength = sizeof(ATA_PASS_THROUGH_EX);\n            IrpSp->Parameters.DeviceIoControl.OutputBufferLength = sizeof(ATA_PASS_THROUGH_EX);\n\n            stripe->Irp->AssociatedIrp.SystemBuffer = &stripe->apte;\n            stripe->Irp->Flags |= IRP_BUFFERED_IO | IRP_INPUT_OPERATION;\n            stripe->Irp->UserBuffer = &stripe->apte;\n            stripe->Irp->UserIosb = &stripe->iosb;\n\n            IoSetCompletionRoutine(stripe->Irp, ioctl_completion, &context, true, true, true);\n\n            IoCallDriver(dev->devobj, stripe->Irp);\n\nnextdev:\n            num++;\n        }\n\n        le = le->Flink;\n    }\n\n    KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n\n    for (unsigned int i = 0; i < num; i++) {\n        if (context.stripes[i].Irp)\n            IoFreeIrp(context.stripes[i].Irp);\n    }\n\n    ExFreePool(context.stripes);\n}\n\nstatic NTSTATUS flush_changed_dev_stats(device_extension* Vcb, device* dev, PIRP Irp) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    uint16_t statslen;\n    uint64_t* stats;\n\n    searchkey.obj_id = 0;\n    searchkey.obj_type = TYPE_DEV_STATS;\n    searchkey.offset = dev->devitem.dev_id;\n\n    Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (!keycmp(tp.item->key, searchkey)) {\n        Status = delete_tree_item(Vcb, &tp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    statslen = sizeof(uint64_t) * 5;\n    stats = ExAllocatePoolWithTag(PagedPool, statslen, ALLOC_TAG);\n    if (!stats) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(stats, dev->stats, statslen);\n\n    Status = insert_tree_item(Vcb, Vcb->dev_root, 0, TYPE_DEV_STATS, dev->devitem.dev_id, stats, statslen, NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(stats);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS flush_subvol(device_extension* Vcb, root* r, PIRP Irp) {\n    NTSTATUS Status;\n\n    if (r != Vcb->root_root && r != Vcb->chunk_root) {\n        KEY searchkey;\n        traverse_ptr tp;\n        ROOT_ITEM* ri;\n\n        searchkey.obj_id = r->id;\n        searchkey.obj_type = TYPE_ROOT_ITEM;\n        searchkey.offset = 0xffffffffffffffff;\n\n        Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n            ERR(\"could not find ROOT_ITEM for tree %I64x\\n\", searchkey.obj_id);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        ri = ExAllocatePoolWithTag(PagedPool, sizeof(ROOT_ITEM), ALLOC_TAG);\n        if (!ri) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(ri, &r->root_item, sizeof(ROOT_ITEM));\n\n        Status = delete_tree_item(Vcb, &tp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        Status = insert_tree_item(Vcb, Vcb->root_root, tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, ri, sizeof(ROOT_ITEM), NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    if (r->received) {\n        KEY searchkey;\n        traverse_ptr tp;\n\n        if (!Vcb->uuid_root) {\n            root* uuid_root;\n\n            TRACE(\"uuid root doesn't exist, creating it\\n\");\n\n            Status = create_root(Vcb, BTRFS_ROOT_UUID, &uuid_root, false, 0, Irp);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"create_root returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            Vcb->uuid_root = uuid_root;\n        }\n\n        RtlCopyMemory(&searchkey.obj_id, &r->root_item.received_uuid, sizeof(uint64_t));\n        searchkey.obj_type = TYPE_SUBVOL_REC_UUID;\n        RtlCopyMemory(&searchkey.offset, &r->root_item.received_uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t));\n\n        Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (!keycmp(tp.item->key, searchkey)) {\n            if (tp.item->size + sizeof(uint64_t) <= Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node)) {\n                uint64_t* ids;\n\n                ids = ExAllocatePoolWithTag(PagedPool, tp.item->size + sizeof(uint64_t), ALLOC_TAG);\n                if (!ids) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(ids, tp.item->data, tp.item->size);\n                RtlCopyMemory((uint8_t*)ids + tp.item->size, &r->id, sizeof(uint64_t));\n\n                Status = delete_tree_item(Vcb, &tp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                    ExFreePool(ids);\n                    return Status;\n                }\n\n                Status = insert_tree_item(Vcb, Vcb->uuid_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, ids, tp.item->size + sizeof(uint64_t), NULL, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                    ExFreePool(ids);\n                    return Status;\n                }\n            }\n        } else {\n            uint64_t* root_num;\n\n            root_num = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t), ALLOC_TAG);\n            if (!root_num) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            *root_num = r->id;\n\n            Status = insert_tree_item(Vcb, Vcb->uuid_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, root_num, sizeof(uint64_t), NULL, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                ExFreePool(root_num);\n                return Status;\n            }\n        }\n\n        r->received = false;\n    }\n\n    r->dirty = false;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS test_not_full(device_extension* Vcb) {\n    uint64_t reserve, could_alloc, free_space;\n    LIST_ENTRY* le;\n\n    // This function ensures we drop into readonly mode if we're about to leave very little\n    // space for metadata - this is similar to the \"global reserve\" of the Linux driver.\n    // Otherwise we might completely fill our space, at which point due to COW we can't\n    // delete anything in order to fix this.\n\n    reserve = Vcb->extent_root->root_item.bytes_used;\n    reserve += Vcb->root_root->root_item.bytes_used;\n    if (Vcb->checksum_root) reserve += Vcb->checksum_root->root_item.bytes_used;\n\n    reserve = max(reserve, 0x1000000); // 16 M\n    reserve = min(reserve, 0x20000000); // 512 M\n\n    // Find out how much space would be available for new metadata chunks\n\n    could_alloc = 0;\n\n    if (Vcb->metadata_flags & BLOCK_FLAG_RAID5) {\n        uint64_t s1 = 0, s2 = 0, s3 = 0;\n\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n            if (!dev->readonly) {\n                uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used;\n\n                if (space >= s1) {\n                    s3 = s2;\n                    s2 = s1;\n                    s1 = space;\n                } else if (space >= s2) {\n                    s3 = s2;\n                    s2 = space;\n                } else if (space >= s3)\n                    s3 = space;\n            }\n\n            le = le->Flink;\n        }\n\n        could_alloc = s3 * 2;\n    } else if (Vcb->metadata_flags & (BLOCK_FLAG_RAID10 | BLOCK_FLAG_RAID6)) {\n        uint64_t s1 = 0, s2 = 0, s3 = 0, s4 = 0;\n\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n            if (!dev->readonly) {\n                uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used;\n\n                if (space >= s1) {\n                    s4 = s3;\n                    s3 = s2;\n                    s2 = s1;\n                    s1 = space;\n                } else if (space >= s2) {\n                    s4 = s3;\n                    s3 = s2;\n                    s2 = space;\n                } else if (space >= s3) {\n                    s4 = s3;\n                    s3 = space;\n                } else if (space >= s4)\n                    s4 = space;\n            }\n\n            le = le->Flink;\n        }\n\n        could_alloc = s4 * 2;\n    } else if (Vcb->metadata_flags & (BLOCK_FLAG_RAID0 | BLOCK_FLAG_RAID1)) {\n        uint64_t s1 = 0, s2 = 0;\n\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n            if (!dev->readonly) {\n                uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used;\n\n                if (space >= s1) {\n                    s2 = s1;\n                    s1 = space;\n                } else if (space >= s2)\n                    s2 = space;\n            }\n\n            le = le->Flink;\n        }\n\n        if (Vcb->metadata_flags & BLOCK_FLAG_RAID1)\n            could_alloc = s2;\n        else // RAID0\n            could_alloc = s2 * 2;\n    } else if (Vcb->metadata_flags & BLOCK_FLAG_DUPLICATE) {\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n            if (!dev->readonly) {\n                uint64_t space = (dev->devitem.num_bytes - dev->devitem.bytes_used) / 2;\n\n                could_alloc = max(could_alloc, space);\n            }\n\n            le = le->Flink;\n        }\n    } else if (Vcb->metadata_flags & BLOCK_FLAG_RAID1C3) {\n        uint64_t s1 = 0, s2 = 0, s3 = 0;\n\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n            if (!dev->readonly) {\n                uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used;\n\n                if (space >= s1) {\n                    s3 = s2;\n                    s2 = s1;\n                    s1 = space;\n                } else if (space >= s2) {\n                    s3 = s2;\n                    s2 = space;\n                } else if (space >= s3)\n                    s3 = space;\n            }\n\n            le = le->Flink;\n        }\n\n        could_alloc = s3;\n    } else if (Vcb->metadata_flags & BLOCK_FLAG_RAID1C4) {\n        uint64_t s1 = 0, s2 = 0, s3 = 0, s4 = 0;\n\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n            if (!dev->readonly) {\n                uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used;\n\n                if (space >= s1) {\n                    s4 = s3;\n                    s3 = s2;\n                    s2 = s1;\n                    s1 = space;\n                } else if (space >= s2) {\n                    s4 = s3;\n                    s3 = s2;\n                    s2 = space;\n                } else if (space >= s3) {\n                    s4 = s3;\n                    s3 = space;\n                } else if (space >= s4)\n                    s4 = space;\n            }\n\n            le = le->Flink;\n        }\n\n        could_alloc = s4;\n    } else { // SINGLE\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n            if (!dev->readonly) {\n                uint64_t space = dev->devitem.num_bytes - dev->devitem.bytes_used;\n\n                could_alloc = max(could_alloc, space);\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    if (could_alloc >= reserve)\n        return STATUS_SUCCESS;\n\n    free_space = 0;\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (!c->reloc && !c->readonly && c->chunk_item->type & BLOCK_FLAG_METADATA) {\n            free_space += c->chunk_item->size - c->used;\n\n            if (free_space + could_alloc >= reserve)\n                return STATUS_SUCCESS;\n        }\n\n        le = le->Flink;\n    }\n\n    return STATUS_DISK_FULL;\n}\n\nstatic NTSTATUS check_for_orphans_root(device_extension* Vcb, root* r, PIRP Irp) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    LIST_ENTRY rollback;\n\n    TRACE(\"(%p, %p)\\n\", Vcb, r);\n\n    InitializeListHead(&rollback);\n\n    searchkey.obj_id = BTRFS_ORPHAN_INODE_OBJID;\n    searchkey.obj_type = TYPE_ORPHAN_INODE;\n    searchkey.offset = 0;\n\n    Status = find_item(Vcb, r, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    do {\n        traverse_ptr next_tp;\n\n        if (tp.item->key.obj_id > searchkey.obj_id || (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type > searchkey.obj_type))\n            break;\n\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n            fcb* fcb;\n\n            TRACE(\"removing orphaned inode %I64x\\n\", tp.item->key.offset);\n\n            Status = open_fcb(Vcb, r, tp.item->key.offset, 0, NULL, false, NULL, &fcb, PagedPool, Irp);\n            if (!NT_SUCCESS(Status))\n                ERR(\"open_fcb returned %08lx\\n\", Status);\n            else {\n                if (fcb->inode_item.st_nlink == 0) {\n                    if (fcb->type != BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0) {\n                        Status = excise_extents(Vcb, fcb, 0, sector_align(fcb->inode_item.st_size, Vcb->superblock.sector_size), Irp, &rollback);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"excise_extents returned %08lx\\n\", Status);\n                            goto end;\n                        }\n                    }\n\n                    fcb->deleted = true;\n\n                    mark_fcb_dirty(fcb);\n                }\n\n                free_fcb(fcb);\n\n                Status = delete_tree_item(Vcb, &tp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                    goto end;\n                }\n            }\n        }\n\n        if (find_next_item(Vcb, &tp, &next_tp, false, Irp))\n            tp = next_tp;\n        else\n            break;\n    } while (true);\n\n    Status = STATUS_SUCCESS;\n\n    clear_rollback(&rollback);\n\nend:\n    do_rollback(Vcb, &rollback);\n\n    return Status;\n}\n\nstatic NTSTATUS check_for_orphans(device_extension* Vcb, PIRP Irp) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n\n    if (IsListEmpty(&Vcb->dirty_filerefs))\n        return STATUS_SUCCESS;\n\n    le = Vcb->dirty_filerefs.Flink;\n    while (le != &Vcb->dirty_filerefs) {\n        file_ref* fr = CONTAINING_RECORD(le, file_ref, list_entry_dirty);\n\n        if (!fr->fcb->subvol->checked_for_orphans) {\n            Status = check_for_orphans_root(Vcb, fr->fcb->subvol, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"check_for_orphans_root returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            fr->fcb->subvol->checked_for_orphans = true;\n        }\n\n        le = le->Flink;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS do_write2(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    LIST_ENTRY *le, batchlist;\n    bool cache_changed = false;\n    volume_device_extension* vde;\n    bool no_cache = false;\n#ifdef DEBUG_FLUSH_TIMES\n    uint64_t filerefs = 0, fcbs = 0;\n    LARGE_INTEGER freq, time1, time2;\n#endif\n#ifdef DEBUG_WRITE_LOOPS\n    UINT loops = 0;\n#endif\n\n    TRACE(\"(%p)\\n\", Vcb);\n\n    InitializeListHead(&batchlist);\n\n#ifdef DEBUG_FLUSH_TIMES\n    time1 = KeQueryPerformanceCounter(&freq);\n#endif\n\n    Status = check_for_orphans(Vcb, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"check_for_orphans returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->dirty_filerefs_lock, true);\n\n    while (!IsListEmpty(&Vcb->dirty_filerefs)) {\n        file_ref* fr = CONTAINING_RECORD(RemoveHeadList(&Vcb->dirty_filerefs), file_ref, list_entry_dirty);\n\n        flush_fileref(fr, &batchlist, Irp);\n        free_fileref(fr);\n\n#ifdef DEBUG_FLUSH_TIMES\n        filerefs++;\n#endif\n    }\n\n    ExReleaseResourceLite(&Vcb->dirty_filerefs_lock);\n\n    Status = commit_batch_list(Vcb, &batchlist, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"commit_batch_list returned %08lx\\n\", Status);\n        return Status;\n    }\n\n#ifdef DEBUG_FLUSH_TIMES\n    time2 = KeQueryPerformanceCounter(NULL);\n\n    ERR(\"flushed %I64u filerefs in %I64u (freq = %I64u)\\n\", filerefs, time2.QuadPart - time1.QuadPart, freq.QuadPart);\n\n    time1 = KeQueryPerformanceCounter(&freq);\n#endif\n\n    // We process deleted streams first, so we don't run over our xattr\n    // limit unless we absolutely have to.\n    // We also process deleted normal files, to avoid any problems\n    // caused by inode collisions.\n\n    ExAcquireResourceExclusiveLite(&Vcb->dirty_fcbs_lock, true);\n\n    le = Vcb->dirty_fcbs.Flink;\n    while (le != &Vcb->dirty_fcbs) {\n        fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_dirty);\n        LIST_ENTRY* le2 = le->Flink;\n\n        if (fcb->deleted) {\n            ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n            Status = flush_fcb(fcb, false, &batchlist, Irp);\n            ExReleaseResourceLite(fcb->Header.Resource);\n\n            free_fcb(fcb);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"flush_fcb returned %08lx\\n\", Status);\n                clear_batch_list(Vcb, &batchlist);\n                ExReleaseResourceLite(&Vcb->dirty_fcbs_lock);\n                return Status;\n            }\n\n#ifdef DEBUG_FLUSH_TIMES\n            fcbs++;\n#endif\n        }\n\n        le = le2;\n    }\n\n    Status = commit_batch_list(Vcb, &batchlist, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"commit_batch_list returned %08lx\\n\", Status);\n        ExReleaseResourceLite(&Vcb->dirty_fcbs_lock);\n        return Status;\n    }\n\n    le = Vcb->dirty_fcbs.Flink;\n    while (le != &Vcb->dirty_fcbs) {\n        fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_dirty);\n        LIST_ENTRY* le2 = le->Flink;\n\n        if (fcb->subvol != Vcb->root_root) {\n            ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n            Status = flush_fcb(fcb, false, &batchlist, Irp);\n            ExReleaseResourceLite(fcb->Header.Resource);\n            free_fcb(fcb);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"flush_fcb returned %08lx\\n\", Status);\n                ExReleaseResourceLite(&Vcb->dirty_fcbs_lock);\n                return Status;\n            }\n\n#ifdef DEBUG_FLUSH_TIMES\n            fcbs++;\n#endif\n        }\n\n        le = le2;\n    }\n\n    ExReleaseResourceLite(&Vcb->dirty_fcbs_lock);\n\n    Status = commit_batch_list(Vcb, &batchlist, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"commit_batch_list returned %08lx\\n\", Status);\n        return Status;\n    }\n\n#ifdef DEBUG_FLUSH_TIMES\n    time2 = KeQueryPerformanceCounter(NULL);\n\n    ERR(\"flushed %I64u fcbs in %I64u (freq = %I64u)\\n\", filerefs, time2.QuadPart - time1.QuadPart, freq.QuadPart);\n#endif\n\n    // no need to get dirty_subvols_lock here, as we have tree_lock exclusively\n    while (!IsListEmpty(&Vcb->dirty_subvols)) {\n        root* r = CONTAINING_RECORD(RemoveHeadList(&Vcb->dirty_subvols), root, list_entry_dirty);\n\n        Status = flush_subvol(Vcb, r, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"flush_subvol returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    if (!IsListEmpty(&Vcb->drop_roots)) {\n        Status = drop_roots(Vcb, Irp, rollback);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"drop_roots returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    Status = update_chunks(Vcb, &batchlist, Irp, rollback);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"update_chunks returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    Status = commit_batch_list(Vcb, &batchlist, Irp);\n\n    // If only changing superblock, e.g. changing label, we still need to rewrite\n    // the root tree so the generations match, otherwise you won't be able to mount on Linux.\n    if (!Vcb->root_root->treeholder.tree || !Vcb->root_root->treeholder.tree->write) {\n        KEY searchkey;\n\n        traverse_ptr tp;\n\n        searchkey.obj_id = 0;\n        searchkey.obj_type = 0;\n        searchkey.offset = 0;\n\n        Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        Vcb->root_root->treeholder.tree->write = true;\n    }\n\n    // make sure we always update the extent tree\n    Status = add_root_item_to_cache(Vcb, BTRFS_ROOT_EXTENT, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"add_root_item_to_cache returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (Vcb->stats_changed) {\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n            if (dev->stats_changed) {\n                Status = flush_changed_dev_stats(Vcb, dev, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"flush_changed_dev_stats returned %08lx\\n\", Status);\n                    return Status;\n                }\n                dev->stats_changed = false;\n            }\n\n            le = le->Flink;\n        }\n\n        Vcb->stats_changed = false;\n    }\n\n    do {\n        Status = add_parents(Vcb, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_parents returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        Status = allocate_tree_extents(Vcb, Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"allocate_tree_extents returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        Status = do_splits(Vcb, Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"do_splits returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        Status = update_chunk_usage(Vcb, Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"update_chunk_usage returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        if (!(Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE)) {\n            if (!no_cache) {\n                Status = allocate_cache(Vcb, &cache_changed, Irp, rollback);\n                if (!NT_SUCCESS(Status)) {\n                    WARN(\"allocate_cache returned %08lx\\n\", Status);\n                    no_cache = true;\n                    cache_changed = false;\n                }\n            }\n        } else {\n            Status = update_chunk_caches_tree(Vcb, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"update_chunk_caches_tree returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n\n#ifdef DEBUG_WRITE_LOOPS\n        loops++;\n\n        if (cache_changed)\n            ERR(\"cache has changed, looping again\\n\");\n#endif\n    } while (cache_changed || !trees_consistent(Vcb));\n\n#ifdef DEBUG_WRITE_LOOPS\n    ERR(\"%u loops\\n\", loops);\n#endif\n\n    TRACE(\"trees consistent\\n\");\n\n    Status = update_root_root(Vcb, no_cache, Irp, rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"update_root_root returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    Status = write_trees(Vcb, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"write_trees returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    Status = test_not_full(Vcb);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"test_not_full returned %08lx\\n\", Status);\n        goto end;\n    }\n\n#ifdef DEBUG_PARANOID\n    le = Vcb->trees.Flink;\n    while (le != &Vcb->trees) {\n        tree* t = CONTAINING_RECORD(le, tree, list_entry);\n        KEY searchkey;\n        traverse_ptr tp;\n\n        searchkey.obj_id = t->header.address;\n        searchkey.obj_type = TYPE_METADATA_ITEM;\n        searchkey.offset = 0xffffffffffffffff;\n\n        Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n            searchkey.obj_id = t->header.address;\n            searchkey.obj_type = TYPE_EXTENT_ITEM;\n            searchkey.offset = 0xffffffffffffffff;\n\n            Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"error - find_item returned %08lx\\n\", Status);\n                goto end;\n            }\n\n            if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n                ERR(\"error - could not find entry in extent tree for tree at %I64x\\n\", t->header.address);\n                Status = STATUS_INTERNAL_ERROR;\n                goto end;\n            }\n        }\n\n        le = le->Flink;\n    }\n#endif\n\n    Vcb->superblock.cache_generation = Vcb->superblock.generation;\n\n    if (!Vcb->options.no_barrier)\n        flush_disk_caches(Vcb);\n\n    Status = write_superblocks(Vcb, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"write_superblocks returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    vde = Vcb->vde;\n\n    if (vde) {\n        pdo_device_extension* pdode = vde->pdode;\n\n        ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n        le = pdode->children.Flink;\n\n        while (le != &pdode->children) {\n            volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n            vc->generation = Vcb->superblock.generation;\n            le = le->Flink;\n        }\n\n        ExReleaseResourceLite(&pdode->child_lock);\n    }\n\n    clean_space_cache(Vcb);\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        c->changed = false;\n        c->space_changed = false;\n\n        le = le->Flink;\n    }\n\n    Vcb->superblock.generation++;\n\n    Status = STATUS_SUCCESS;\n\n    le = Vcb->trees.Flink;\n    while (le != &Vcb->trees) {\n        tree* t = CONTAINING_RECORD(le, tree, list_entry);\n\n        t->write = false;\n\n        le = le->Flink;\n    }\n\n    Vcb->need_write = false;\n\n    while (!IsListEmpty(&Vcb->drop_roots)) {\n        root* r = CONTAINING_RECORD(RemoveHeadList(&Vcb->drop_roots), root, list_entry);\n\n        if (IsListEmpty(&r->fcbs)) {\n            ExDeleteResourceLite(&r->nonpaged->load_tree_lock);\n            ExFreePool(r->nonpaged);\n            ExFreePool(r);\n        } else\n            r->dropped = true;\n    }\n\nend:\n    TRACE(\"do_write returning %08lx\\n\", Status);\n\n    return Status;\n}\n\nNTSTATUS do_write(device_extension* Vcb, PIRP Irp) {\n    LIST_ENTRY rollback;\n    NTSTATUS Status;\n\n    InitializeListHead(&rollback);\n\n    Status = do_write2(Vcb, Irp, &rollback);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"do_write2 returned %08lx, dropping into readonly mode\\n\", Status);\n        Vcb->readonly = true;\n        FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_FORCED_CLOSED);\n        do_rollback(Vcb, &rollback);\n    } else\n        clear_rollback(&rollback);\n\n    return Status;\n}\n\nstatic void do_flush(device_extension* Vcb) {\n    NTSTATUS Status;\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    if (Vcb->need_write && !Vcb->readonly)\n        Status = do_write(Vcb, NULL);\n    else\n        Status = STATUS_SUCCESS;\n\n    free_trees(Vcb);\n\n    if (!NT_SUCCESS(Status))\n        ERR(\"do_write returned %08lx\\n\", Status);\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n}\n\n_Function_class_(KSTART_ROUTINE)\nvoid __stdcall flush_thread(void* context) {\n    DEVICE_OBJECT* devobj = context;\n    device_extension* Vcb = devobj->DeviceExtension;\n    LARGE_INTEGER due_time;\n\n    ObReferenceObject(devobj);\n\n    KeInitializeTimer(&Vcb->flush_thread_timer);\n\n    due_time.QuadPart = (uint64_t)Vcb->options.flush_interval * -10000000;\n\n    KeSetTimer(&Vcb->flush_thread_timer, due_time, NULL);\n\n    while (true) {\n        KeWaitForSingleObject(&Vcb->flush_thread_timer, Executive, KernelMode, false, NULL);\n\n        if (!(devobj->Vpb->Flags & VPB_MOUNTED) || Vcb->removing)\n            break;\n\n        if (!Vcb->locked)\n            do_flush(Vcb);\n\n        KeSetTimer(&Vcb->flush_thread_timer, due_time, NULL);\n    }\n\n    ObDereferenceObject(devobj);\n    KeCancelTimer(&Vcb->flush_thread_timer);\n\n    KeSetEvent(&Vcb->flush_thread_finished, 0, false);\n\n    PsTerminateSystemThread(STATUS_SUCCESS);\n}\n"
  },
  {
    "path": "src/free-space.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include \"crc32c.h\"\n\n// Number of increments in the size of each cache inode, in sectors. Should\n// this be a constant number of sectors, a constant 256 KB, or what?\n#define CACHE_INCREMENTS    64\n\nstatic NTSTATUS remove_free_space_inode(device_extension* Vcb, uint64_t inode, LIST_ENTRY* batchlist, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    fcb* fcb;\n\n    Status = open_fcb(Vcb, Vcb->root_root, inode, BTRFS_TYPE_FILE, NULL, false, NULL, &fcb, PagedPool, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"open_fcb returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    mark_fcb_dirty(fcb);\n\n    if (fcb->inode_item.st_size > 0) {\n        Status = excise_extents(fcb->Vcb, fcb, 0, sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size), Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"excise_extents returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    fcb->deleted = true;\n\n    Status = flush_fcb(fcb, false, batchlist, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"flush_fcb returned %08lx\\n\", Status);\n        free_fcb(fcb);\n        return Status;\n    }\n\n    free_fcb(fcb);\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS clear_free_space_cache(device_extension* Vcb, LIST_ENTRY* batchlist, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp, next_tp;\n    NTSTATUS Status;\n    bool b;\n    LIST_ENTRY rollback;\n\n    InitializeListHead(&rollback);\n\n    searchkey.obj_id = FREE_SPACE_CACHE_ID;\n    searchkey.obj_type = 0;\n    searchkey.offset = 0;\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    do {\n        if (tp.item->key.obj_id > searchkey.obj_id || (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type > searchkey.obj_type))\n            break;\n\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (tp.item->size >= sizeof(FREE_SPACE_ITEM)) {\n                FREE_SPACE_ITEM* fsi = (FREE_SPACE_ITEM*)tp.item->data;\n\n                if (fsi->key.obj_type != TYPE_INODE_ITEM)\n                    WARN(\"key (%I64x,%x,%I64x) does not point to an INODE_ITEM\\n\", fsi->key.obj_id, fsi->key.obj_type, fsi->key.offset);\n                else {\n                    LIST_ENTRY* le;\n\n                    Status = remove_free_space_inode(Vcb, fsi->key.obj_id, batchlist, Irp, &rollback);\n\n                    if (!NT_SUCCESS(Status))\n                        ERR(\"remove_free_space_inode for (%I64x,%x,%I64x) returned %08lx\\n\", fsi->key.obj_id, fsi->key.obj_type, fsi->key.offset, Status);\n\n                    le = Vcb->chunks.Flink;\n                    while (le != &Vcb->chunks) {\n                        chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n\n                        if (c->offset == tp.item->key.offset && c->cache) {\n                            reap_fcb(c->cache);\n                            c->cache = NULL;\n                        }\n\n                        le = le->Flink;\n                    }\n                }\n            } else\n                WARN(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(FREE_SPACE_ITEM));\n        }\n\n        b = find_next_item(Vcb, &tp, &next_tp, false, Irp);\n        if (b)\n            tp = next_tp;\n    } while (b);\n\n    Status = STATUS_SUCCESS;\n\n    if (NT_SUCCESS(Status))\n        clear_rollback(&rollback);\n    else\n        do_rollback(Vcb, &rollback);\n\n    if (Vcb->space_root) {\n        searchkey.obj_id = 0;\n        searchkey.obj_type = 0;\n        searchkey.offset = 0;\n\n        Status = find_item(Vcb, Vcb->space_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        do {\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            b = find_next_item(Vcb, &tp, &next_tp, false, Irp);\n            if (b)\n                tp = next_tp;\n        } while (b);\n    }\n\n    // regenerate free space tree\n    if (Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE) {\n        LIST_ENTRY* le;\n\n        ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);\n\n        le = Vcb->chunks.Flink;\n        while (le != &Vcb->chunks) {\n            chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n\n            if (!c->cache_loaded) {\n                acquire_chunk_lock(c, Vcb);\n\n                Status = load_cache_chunk(Vcb, c, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"load_cache_chunk(%I64x) returned %08lx\\n\", c->offset, Status);\n                    release_chunk_lock(c, Vcb);\n                    ExReleaseResourceLite(&Vcb->chunk_lock);\n                    return Status;\n                }\n\n                c->changed = true;\n                c->space_changed = true;\n\n                release_chunk_lock(c, Vcb);\n            }\n\n            le = le->Flink;\n        }\n\n        ExReleaseResourceLite(&Vcb->chunk_lock);\n    }\n\n    return Status;\n}\n\nNTSTATUS add_space_entry(LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t offset, uint64_t size) {\n    space* s;\n\n    s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);\n\n    if (!s) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    s->address = offset;\n    s->size = size;\n\n    if (IsListEmpty(list))\n        InsertTailList(list, &s->list_entry);\n    else {\n        space* s2 = CONTAINING_RECORD(list->Blink, space, list_entry);\n\n        if (s2->address < offset)\n            InsertTailList(list, &s->list_entry);\n        else {\n            LIST_ENTRY* le;\n\n            le = list->Flink;\n            while (le != list) {\n                s2 = CONTAINING_RECORD(le, space, list_entry);\n\n                if (s2->address > offset) {\n                    InsertTailList(le, &s->list_entry);\n                    goto size;\n                }\n\n                le = le->Flink;\n            }\n        }\n    }\n\nsize:\n    if (!list_size)\n        return STATUS_SUCCESS;\n\n    if (IsListEmpty(list_size))\n        InsertTailList(list_size, &s->list_entry_size);\n    else {\n        space* s2 = CONTAINING_RECORD(list_size->Blink, space, list_entry_size);\n\n        if (s2->size >= size)\n            InsertTailList(list_size, &s->list_entry_size);\n        else {\n            LIST_ENTRY* le;\n\n            le = list_size->Flink;\n            while (le != list_size) {\n                s2 = CONTAINING_RECORD(le, space, list_entry_size);\n\n                if (s2->size <= size) {\n                    InsertHeadList(le->Blink, &s->list_entry_size);\n                    return STATUS_SUCCESS;\n                }\n\n                le = le->Flink;\n            }\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic void load_free_space_bitmap(device_extension* Vcb, chunk* c, uint64_t offset, void* data, uint64_t* total_space) {\n    RTL_BITMAP bmph;\n    uint32_t i, len, *dwords = data;\n    ULONG runlength, index;\n\n    // flip bits\n    for (i = 0; i < Vcb->superblock.sector_size / sizeof(uint32_t); i++) {\n        dwords[i] = ~dwords[i];\n    }\n\n    len = Vcb->superblock.sector_size * 8;\n\n    RtlInitializeBitMap(&bmph, data, len);\n\n    index = 0;\n    runlength = RtlFindFirstRunClear(&bmph, &index);\n\n    while (runlength != 0) {\n        uint64_t addr, length;\n\n        if (index >= len)\n            break;\n\n        if (index + runlength >= len) {\n            runlength = len - index;\n\n            if (runlength == 0)\n                break;\n        }\n\n        addr = offset + (index << Vcb->sector_shift);\n        length = runlength << Vcb->sector_shift;\n\n        add_space_entry(&c->space, &c->space_size, addr, length);\n        index += runlength;\n        *total_space += length;\n\n        runlength = RtlFindNextForwardRunClear(&bmph, index, &index);\n    }\n}\n\nstatic void order_space_entry(space* s, LIST_ENTRY* list_size) {\n    LIST_ENTRY* le;\n\n    if (IsListEmpty(list_size)) {\n        InsertHeadList(list_size, &s->list_entry_size);\n        return;\n    }\n\n    le = list_size->Flink;\n\n    while (le != list_size) {\n        space* s2 = CONTAINING_RECORD(le, space, list_entry_size);\n\n        if (s2->size <= s->size) {\n            InsertHeadList(le->Blink, &s->list_entry_size);\n            return;\n        }\n\n        le = le->Flink;\n    }\n\n    InsertTailList(list_size, &s->list_entry_size);\n}\n\ntypedef struct {\n    uint64_t stripe;\n    LIST_ENTRY list_entry;\n} superblock_stripe;\n\nstatic NTSTATUS add_superblock_stripe(LIST_ENTRY* stripes, uint64_t off, uint64_t len) {\n    uint64_t i;\n\n    for (i = 0; i < len; i++) {\n        LIST_ENTRY* le;\n        superblock_stripe* ss;\n        bool ignore = false;\n\n        le = stripes->Flink;\n        while (le != stripes) {\n            ss = CONTAINING_RECORD(le, superblock_stripe, list_entry);\n\n            if (ss->stripe == off + i) {\n                ignore = true;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        if (ignore)\n            continue;\n\n        ss = ExAllocatePoolWithTag(PagedPool, sizeof(superblock_stripe), ALLOC_TAG);\n        if (!ss) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        ss->stripe = off + i;\n        InsertTailList(stripes, &ss->list_entry);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS get_superblock_size(chunk* c, uint64_t* size) {\n    NTSTATUS Status;\n    CHUNK_ITEM* ci = c->chunk_item;\n    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1];\n    uint64_t off_start, off_end, space = 0;\n    uint16_t i = 0, j;\n    LIST_ENTRY stripes;\n\n    InitializeListHead(&stripes);\n\n    while (superblock_addrs[i] != 0) {\n        if (ci->type & BLOCK_FLAG_RAID0 || ci->type & BLOCK_FLAG_RAID10) {\n            for (j = 0; j < ci->num_stripes; j++) {\n                ULONG sub_stripes = max(ci->sub_stripes, 1);\n\n                if (cis[j].offset + (ci->size * ci->num_stripes / sub_stripes) > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {\n                    off_start = superblock_addrs[i] - cis[j].offset;\n                    off_start -= off_start % ci->stripe_length;\n                    off_start *= ci->num_stripes / sub_stripes;\n                    off_start += (j / sub_stripes) * ci->stripe_length;\n\n                    off_end = off_start + ci->stripe_length;\n\n                    Status = add_superblock_stripe(&stripes, off_start / ci->stripe_length, 1);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"add_superblock_stripe returned %08lx\\n\", Status);\n                        goto end;\n                    }\n                }\n            }\n        } else if (ci->type & BLOCK_FLAG_RAID5) {\n            for (j = 0; j < ci->num_stripes; j++) {\n                uint64_t stripe_size = ci->size / (ci->num_stripes - 1);\n\n                if (cis[j].offset + stripe_size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {\n                    off_start = superblock_addrs[i] - cis[j].offset;\n                    off_start -= off_start % (ci->stripe_length * (ci->num_stripes - 1));\n                    off_start *= ci->num_stripes - 1;\n\n                    off_end = off_start + (ci->stripe_length * (ci->num_stripes - 1));\n\n                    Status = add_superblock_stripe(&stripes, off_start / ci->stripe_length, (off_end - off_start) / ci->stripe_length);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"add_superblock_stripe returned %08lx\\n\", Status);\n                        goto end;\n                    }\n                }\n            }\n        } else if (ci->type & BLOCK_FLAG_RAID6) {\n            for (j = 0; j < ci->num_stripes; j++) {\n                uint64_t stripe_size = ci->size / (ci->num_stripes - 2);\n\n                if (cis[j].offset + stripe_size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {\n                    off_start = superblock_addrs[i] - cis[j].offset;\n                    off_start -= off_start % (ci->stripe_length * (ci->num_stripes - 2));\n                    off_start *= ci->num_stripes - 2;\n\n                    off_end = off_start + (ci->stripe_length * (ci->num_stripes - 2));\n\n                    Status = add_superblock_stripe(&stripes, off_start / ci->stripe_length, (off_end - off_start) / ci->stripe_length);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"add_superblock_stripe returned %08lx\\n\", Status);\n                        goto end;\n                    }\n                }\n            }\n        } else { // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4\n            for (j = 0; j < ci->num_stripes; j++) {\n                if (cis[j].offset + ci->size > superblock_addrs[i] && cis[j].offset <= superblock_addrs[i] + sizeof(superblock)) {\n                    off_start = ((superblock_addrs[i] - cis[j].offset) / c->chunk_item->stripe_length) * c->chunk_item->stripe_length;\n                    off_end = sector_align(superblock_addrs[i] - cis[j].offset + sizeof(superblock), c->chunk_item->stripe_length);\n\n                    Status = add_superblock_stripe(&stripes, off_start / ci->stripe_length, (off_end - off_start) / ci->stripe_length);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"add_superblock_stripe returned %08lx\\n\", Status);\n                        goto end;\n                    }\n                }\n            }\n        }\n\n        i++;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    while (!IsListEmpty(&stripes)) {\n        LIST_ENTRY* le = RemoveHeadList(&stripes);\n        superblock_stripe* ss = CONTAINING_RECORD(le, superblock_stripe, list_entry);\n\n        space++;\n\n        ExFreePool(ss);\n    }\n\n    if (NT_SUCCESS(Status))\n        *size = space * ci->stripe_length;\n\n    return Status;\n}\n\nNTSTATUS load_stored_free_space_cache(device_extension* Vcb, chunk* c, bool load_only, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    FREE_SPACE_ITEM* fsi;\n    uint64_t inode, *generation;\n    uint8_t* data;\n    NTSTATUS Status;\n    uint32_t *checksums, crc32, num_sectors, num_valid_sectors, size;\n    FREE_SPACE_ENTRY* fse;\n    uint64_t num_entries, num_bitmaps, extent_length, bmpnum, off, total_space = 0, superblock_size;\n    LIST_ENTRY *le, rollback;\n\n    // FIXME - does this break if Vcb->superblock.sector_size is not 4096?\n\n    TRACE(\"(%p, %I64x)\\n\", Vcb, c->offset);\n\n    searchkey.obj_id = FREE_SPACE_CACHE_ID;\n    searchkey.obj_type = 0;\n    searchkey.offset = c->offset;\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (keycmp(tp.item->key, searchkey)) {\n        TRACE(\"(%I64x,%x,%I64x) not found\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n        return STATUS_NOT_FOUND;\n    }\n\n    if (tp.item->size < sizeof(FREE_SPACE_ITEM)) {\n        WARN(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(FREE_SPACE_ITEM));\n        return STATUS_NOT_FOUND;\n    }\n\n    fsi = (FREE_SPACE_ITEM*)tp.item->data;\n\n    if (fsi->key.obj_type != TYPE_INODE_ITEM) {\n        WARN(\"cache pointed to something other than an INODE_ITEM\\n\");\n        return STATUS_NOT_FOUND;\n    }\n\n    inode = fsi->key.obj_id;\n    num_entries = fsi->num_entries;\n    num_bitmaps = fsi->num_bitmaps;\n\n    Status = open_fcb(Vcb, Vcb->root_root, inode, BTRFS_TYPE_FILE, NULL, false, NULL, &c->cache, PagedPool, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"open_fcb returned %08lx\\n\", Status);\n        return STATUS_NOT_FOUND;\n    }\n\n    if (load_only)\n        return STATUS_SUCCESS;\n\n    if (c->cache->inode_item.st_size == 0) {\n        WARN(\"cache had zero length\\n\");\n        free_fcb(c->cache);\n        c->cache = NULL;\n        return STATUS_NOT_FOUND;\n    }\n\n    c->cache->inode_item.flags |= BTRFS_INODE_NODATACOW;\n\n    if (num_entries == 0 && num_bitmaps == 0)\n        return STATUS_SUCCESS;\n\n    size = (uint32_t)sector_align(c->cache->inode_item.st_size, Vcb->superblock.sector_size);\n\n    data = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG);\n\n    if (!data) {\n        ERR(\"out of memory\\n\");\n        free_fcb(c->cache);\n        c->cache = NULL;\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    if (c->chunk_item->size < 0x6400000) { // 100 MB\n        WARN(\"deleting free space cache for chunk smaller than 100MB\\n\");\n        goto clearcache;\n    }\n\n    Status = read_file(c->cache, data, 0, c->cache->inode_item.st_size, NULL, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"read_file returned %08lx\\n\", Status);\n        ExFreePool(data);\n\n        c->cache->deleted = true;\n        mark_fcb_dirty(c->cache);\n\n        free_fcb(c->cache);\n        c->cache = NULL;\n        return STATUS_NOT_FOUND;\n    }\n\n    if (size > c->cache->inode_item.st_size)\n        RtlZeroMemory(&data[c->cache->inode_item.st_size], (ULONG)(size - c->cache->inode_item.st_size));\n\n    num_sectors = size >> Vcb->sector_shift;\n\n    generation = (uint64_t*)(data + (num_sectors * sizeof(uint32_t)));\n\n    if (*generation != fsi->generation) {\n        WARN(\"free space cache generation for %I64x was %I64x, expected %I64x\\n\", c->offset, *generation, fsi->generation);\n        goto clearcache;\n    }\n\n    extent_length = (num_sectors * sizeof(uint32_t)) + sizeof(uint64_t) + (num_entries * sizeof(FREE_SPACE_ENTRY));\n\n    num_valid_sectors = (ULONG)((sector_align(extent_length, Vcb->superblock.sector_size) >> Vcb->sector_shift) + num_bitmaps);\n\n    if (num_valid_sectors > num_sectors) {\n        ERR(\"free space cache for %I64x was %u sectors, expected at least %u\\n\", c->offset, num_sectors, num_valid_sectors);\n        goto clearcache;\n    }\n\n    checksums = (uint32_t*)data;\n\n    for (uint32_t i = 0; i < num_valid_sectors; i++) {\n        if (i << Vcb->sector_shift > sizeof(uint32_t) * num_sectors)\n            crc32 = ~calc_crc32c(0xffffffff, &data[i << Vcb->sector_shift], Vcb->superblock.sector_size);\n        else if ((i + 1) << Vcb->sector_shift < sizeof(uint32_t) * num_sectors)\n            crc32 = 0; // FIXME - test this\n        else\n            crc32 = ~calc_crc32c(0xffffffff, &data[sizeof(uint32_t) * num_sectors], ((i + 1) << Vcb->sector_shift) - (sizeof(uint32_t) * num_sectors));\n\n        if (crc32 != checksums[i]) {\n            WARN(\"checksum %u was %08x, expected %08x\\n\", i, crc32, checksums[i]);\n            goto clearcache;\n        }\n    }\n\n    off = (sizeof(uint32_t) * num_sectors) + sizeof(uint64_t);\n\n    bmpnum = 0;\n    for (uint32_t i = 0; i < num_entries; i++) {\n        if ((off + sizeof(FREE_SPACE_ENTRY)) >> Vcb->sector_shift != off >> Vcb->sector_shift)\n            off = sector_align(off, Vcb->superblock.sector_size);\n\n        fse = (FREE_SPACE_ENTRY*)&data[off];\n\n        if (fse->type == FREE_SPACE_EXTENT) {\n            Status = add_space_entry(&c->space, &c->space_size, fse->offset, fse->size);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"add_space_entry returned %08lx\\n\", Status);\n                ExFreePool(data);\n                return Status;\n            }\n\n            total_space += fse->size;\n        } else if (fse->type != FREE_SPACE_BITMAP) {\n            ERR(\"unknown free-space type %x\\n\", fse->type);\n        }\n\n        off += sizeof(FREE_SPACE_ENTRY);\n    }\n\n    if (num_bitmaps > 0) {\n        bmpnum = sector_align(off, Vcb->superblock.sector_size) >> Vcb->sector_shift;\n        off = (sizeof(uint32_t) * num_sectors) + sizeof(uint64_t);\n\n        for (uint32_t i = 0; i < num_entries; i++) {\n            if ((off + sizeof(FREE_SPACE_ENTRY)) >> Vcb->sector_shift != off >> Vcb->sector_shift)\n                off = sector_align(off, Vcb->superblock.sector_size);\n\n            fse = (FREE_SPACE_ENTRY*)&data[off];\n\n            if (fse->type == FREE_SPACE_BITMAP) {\n                // FIXME - make sure we don't overflow the buffer here\n                load_free_space_bitmap(Vcb, c, fse->offset, &data[bmpnum << Vcb->sector_shift], &total_space);\n                bmpnum++;\n            }\n\n            off += sizeof(FREE_SPACE_ENTRY);\n        }\n    }\n\n    // do sanity check\n\n    Status = get_superblock_size(c, &superblock_size);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"get_superblock_size returned %08lx\\n\", Status);\n        ExFreePool(data);\n        return Status;\n    }\n\n    if (c->chunk_item->size - c->used != total_space + superblock_size) {\n        WARN(\"invalidating cache for chunk %I64x: space was %I64x, expected %I64x\\n\", c->offset, total_space + superblock_size, c->chunk_item->size - c->used);\n        goto clearcache;\n    }\n\n    le = c->space.Flink;\n    while (le != &c->space) {\n        space* s = CONTAINING_RECORD(le, space, list_entry);\n        LIST_ENTRY* le2 = le->Flink;\n\n        if (le2 != &c->space) {\n            space* s2 = CONTAINING_RECORD(le2, space, list_entry);\n\n            if (s2->address == s->address + s->size) {\n                s->size += s2->size;\n\n                RemoveEntryList(&s2->list_entry);\n                RemoveEntryList(&s2->list_entry_size);\n                ExFreePool(s2);\n\n                RemoveEntryList(&s->list_entry_size);\n                order_space_entry(s, &c->space_size);\n\n                le2 = le;\n            }\n        }\n\n        le = le2;\n    }\n\n    ExFreePool(data);\n\n    return STATUS_SUCCESS;\n\nclearcache:\n    ExFreePool(data);\n\n    InitializeListHead(&rollback);\n\n    Status = delete_tree_item(Vcb, &tp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"delete_tree_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    Status = excise_extents(Vcb, c->cache, 0, c->cache->inode_item.st_size, Irp, &rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"excise_extents returned %08lx\\n\", Status);\n        do_rollback(Vcb, &rollback);\n        return Status;\n    }\n\n    clear_rollback(&rollback);\n\n    c->cache->deleted = true;\n    mark_fcb_dirty(c->cache);\n\n    c->old_cache = c->cache;\n    c->cache = NULL;\n\n    le = c->space.Flink;\n    while (le != &c->space) {\n        space* s = CONTAINING_RECORD(le, space, list_entry);\n        LIST_ENTRY* le2 = le->Flink;\n\n        RemoveEntryList(&s->list_entry);\n        RemoveEntryList(&s->list_entry_size);\n        ExFreePool(s);\n\n        le = le2;\n    }\n\n    return STATUS_NOT_FOUND;\n}\n\nstatic NTSTATUS load_stored_free_space_tree(device_extension* Vcb, chunk* c, PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp, next_tp;\n    NTSTATUS Status;\n    ULONG* bmparr = NULL;\n    ULONG bmplen = 0;\n    LIST_ENTRY* le;\n\n    TRACE(\"(%p, %I64x)\\n\", Vcb, c->offset);\n\n    if (!Vcb->space_root)\n        return STATUS_NOT_FOUND;\n\n    searchkey.obj_id = c->offset;\n    searchkey.obj_type = TYPE_FREE_SPACE_INFO;\n    searchkey.offset = c->chunk_item->size;\n\n    Status = find_item(Vcb, Vcb->space_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (keycmp(tp.item->key, searchkey)) {\n        TRACE(\"(%I64x,%x,%I64x) not found\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n        return STATUS_NOT_FOUND;\n    }\n\n    if (tp.item->size < sizeof(FREE_SPACE_INFO)) {\n        WARN(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(FREE_SPACE_INFO));\n        return STATUS_NOT_FOUND;\n    }\n\n    while (find_next_item(Vcb, &tp, &next_tp, false, Irp)) {\n        tp = next_tp;\n\n        if (tp.item->key.obj_id >= c->offset + c->chunk_item->size)\n            break;\n\n        if (tp.item->key.obj_type == TYPE_FREE_SPACE_EXTENT) {\n            Status = add_space_entry(&c->space, &c->space_size, tp.item->key.obj_id, tp.item->key.offset);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"add_space_entry returned %08lx\\n\", Status);\n                if (bmparr) ExFreePool(bmparr);\n                return Status;\n            }\n        } else if (tp.item->key.obj_type == TYPE_FREE_SPACE_BITMAP) {\n            ULONG explen, index, runlength;\n            RTL_BITMAP bmp;\n            uint64_t lastoff;\n            ULONG bmpl;\n\n            explen = (ULONG)(tp.item->key.offset >> Vcb->sector_shift) / 8;\n\n            if (tp.item->size < explen) {\n                WARN(\"(%I64x,%x,%I64x) was %u bytes, expected %lu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, explen);\n                return STATUS_NOT_FOUND;\n            } else if (tp.item->size == 0) {\n                WARN(\"(%I64x,%x,%I64x) has size of 0\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n                return STATUS_NOT_FOUND;\n            }\n\n            if (bmplen < tp.item->size) {\n                if (bmparr)\n                    ExFreePool(bmparr);\n\n                bmplen = (ULONG)sector_align(tp.item->size, sizeof(ULONG));\n                bmparr = ExAllocatePoolWithTag(PagedPool, bmplen, ALLOC_TAG);\n                if (!bmparr) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n            }\n\n            // We copy the bitmap because it supposedly has to be ULONG-aligned\n            RtlCopyMemory(bmparr, tp.item->data, tp.item->size);\n\n            bmpl = (ULONG)tp.item->key.offset >> Vcb->sector_shift;\n\n            RtlInitializeBitMap(&bmp, bmparr, bmpl);\n\n            lastoff = tp.item->key.obj_id;\n\n            runlength = RtlFindFirstRunClear(&bmp, &index);\n\n            while (runlength != 0) {\n                uint64_t runstart, runend;\n\n                if (index >= bmpl)\n                    break;\n\n                if (index + runlength >= bmpl) {\n                    runlength = bmpl - index;\n\n                    if (runlength == 0)\n                        break;\n                }\n\n                runstart = tp.item->key.obj_id + (index << Vcb->sector_shift);\n                runend = runstart + (runlength << Vcb->sector_shift);\n\n                if (runstart > lastoff) {\n                    Status = add_space_entry(&c->space, &c->space_size, lastoff, runstart - lastoff);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"add_space_entry returned %08lx\\n\", Status);\n                        if (bmparr) ExFreePool(bmparr);\n                        return Status;\n                    }\n                }\n\n                lastoff = runend;\n\n                runlength = RtlFindNextForwardRunClear(&bmp, index + runlength, &index);\n            }\n\n            if (lastoff < tp.item->key.obj_id + tp.item->key.offset) {\n                Status = add_space_entry(&c->space, &c->space_size, lastoff, tp.item->key.obj_id + tp.item->key.offset - lastoff);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"add_space_entry returned %08lx\\n\", Status);\n                    if (bmparr) ExFreePool(bmparr);\n                    return Status;\n                }\n            }\n        }\n    }\n\n    if (bmparr)\n        ExFreePool(bmparr);\n\n    le = c->space.Flink;\n    while (le != &c->space) {\n        space* s = CONTAINING_RECORD(le, space, list_entry);\n        LIST_ENTRY* le2 = le->Flink;\n\n        if (le2 != &c->space) {\n            space* s2 = CONTAINING_RECORD(le2, space, list_entry);\n\n            if (s2->address == s->address + s->size) {\n                s->size += s2->size;\n\n                RemoveEntryList(&s2->list_entry);\n                RemoveEntryList(&s2->list_entry_size);\n                ExFreePool(s2);\n\n                RemoveEntryList(&s->list_entry_size);\n                order_space_entry(s, &c->space_size);\n\n                le2 = le;\n            }\n        }\n\n        le = le2;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS load_free_space_cache(device_extension* Vcb, chunk* c, PIRP Irp) {\n    traverse_ptr tp, next_tp;\n    KEY searchkey;\n    uint64_t lastaddr;\n    bool b;\n    space* s;\n    NTSTATUS Status;\n\n    if (Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE && Vcb->superblock.compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID) {\n        Status = load_stored_free_space_tree(Vcb, c, Irp);\n\n        if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) {\n            ERR(\"load_stored_free_space_tree returned %08lx\\n\", Status);\n            return Status;\n        }\n    } else if (Vcb->superblock.generation - 1 == Vcb->superblock.cache_generation) {\n        Status = load_stored_free_space_cache(Vcb, c, false, Irp);\n\n        if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) {\n            ERR(\"load_stored_free_space_cache returned %08lx\\n\", Status);\n            return Status;\n        }\n    } else\n        Status = STATUS_NOT_FOUND;\n\n    if (Status == STATUS_NOT_FOUND) {\n        TRACE(\"generating free space cache for chunk %I64x\\n\", c->offset);\n\n        searchkey.obj_id = c->offset;\n        searchkey.obj_type = TYPE_EXTENT_ITEM;\n        searchkey.offset = 0;\n\n        Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        lastaddr = c->offset;\n\n        do {\n            if (tp.item->key.obj_id >= c->offset + c->chunk_item->size)\n                break;\n\n            if (tp.item->key.obj_id >= c->offset && (tp.item->key.obj_type == TYPE_EXTENT_ITEM || tp.item->key.obj_type == TYPE_METADATA_ITEM)) {\n                if (tp.item->key.obj_id > lastaddr) {\n                    s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);\n\n                    if (!s) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    s->address = lastaddr;\n                    s->size = tp.item->key.obj_id - lastaddr;\n                    InsertTailList(&c->space, &s->list_entry);\n\n                    order_space_entry(s, &c->space_size);\n\n                    TRACE(\"(%I64x,%I64x)\\n\", s->address, s->size);\n                }\n\n                if (tp.item->key.obj_type == TYPE_METADATA_ITEM)\n                    lastaddr = tp.item->key.obj_id + Vcb->superblock.node_size;\n                else\n                    lastaddr = tp.item->key.obj_id + tp.item->key.offset;\n            }\n\n            b = find_next_item(Vcb, &tp, &next_tp, false, Irp);\n            if (b)\n                tp = next_tp;\n        } while (b);\n\n        if (lastaddr < c->offset + c->chunk_item->size) {\n            s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);\n\n            if (!s) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            s->address = lastaddr;\n            s->size = c->offset + c->chunk_item->size - lastaddr;\n            InsertTailList(&c->space, &s->list_entry);\n\n            order_space_entry(s, &c->space_size);\n\n            TRACE(\"(%I64x,%I64x)\\n\", s->address, s->size);\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS load_cache_chunk(device_extension* Vcb, chunk* c, PIRP Irp) {\n    NTSTATUS Status;\n\n    if (c->cache_loaded)\n        return STATUS_SUCCESS;\n\n    Status = load_free_space_cache(Vcb, c, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"load_free_space_cache returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    protect_superblocks(c);\n\n    c->cache_loaded = true;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS insert_cache_extent(fcb* fcb, uint64_t start, uint64_t length, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    LIST_ENTRY* le = fcb->Vcb->chunks.Flink;\n    chunk* c;\n    uint64_t flags;\n\n    flags = fcb->Vcb->data_flags;\n\n    while (le != &fcb->Vcb->chunks) {\n        c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (!c->readonly && !c->reloc) {\n            acquire_chunk_lock(c, fcb->Vcb);\n\n            if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) {\n                if (insert_extent_chunk(fcb->Vcb, fcb, c, start, length, false, NULL, NULL, rollback, BTRFS_COMPRESSION_NONE, length, false, 0))\n                    return STATUS_SUCCESS;\n            }\n\n            release_chunk_lock(c, fcb->Vcb);\n        }\n\n        le = le->Flink;\n    }\n\n    Status = alloc_chunk(fcb->Vcb, flags, &c, false);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"alloc_chunk returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    acquire_chunk_lock(c, fcb->Vcb);\n\n    if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= length) {\n        if (insert_extent_chunk(fcb->Vcb, fcb, c, start, length, false, NULL, NULL, rollback, BTRFS_COMPRESSION_NONE, length, false, 0))\n            return STATUS_SUCCESS;\n    }\n\n    release_chunk_lock(c, fcb->Vcb);\n\n    return STATUS_DISK_FULL;\n}\n\nstatic NTSTATUS allocate_cache_chunk(device_extension* Vcb, chunk* c, bool* changed, LIST_ENTRY* batchlist, PIRP Irp, LIST_ENTRY* rollback) {\n    LIST_ENTRY* le;\n    NTSTATUS Status;\n    uint64_t num_entries, new_cache_size, i;\n    uint32_t num_sectors;\n    bool realloc_extents = false;\n\n    // FIXME - also do bitmaps\n    // FIXME - make sure this works when sector_size is not 4096\n\n    *changed = false;\n\n    num_entries = 0;\n\n    // num_entries is the number of entries in c->space and c->deleting - it might\n    // be slightly higher then what we end up writing, but doing it this way is much\n    // quicker and simpler.\n    if (!IsListEmpty(&c->space)) {\n        le = c->space.Flink;\n        while (le != &c->space) {\n            num_entries++;\n\n            le = le->Flink;\n        }\n    }\n\n    if (!IsListEmpty(&c->deleting)) {\n        le = c->deleting.Flink;\n        while (le != &c->deleting) {\n            num_entries++;\n\n            le = le->Flink;\n        }\n    }\n\n    new_cache_size = sizeof(uint64_t) + (num_entries * sizeof(FREE_SPACE_ENTRY));\n\n    num_sectors = (uint32_t)sector_align(new_cache_size, Vcb->superblock.sector_size) >> Vcb->sector_shift;\n    num_sectors = (uint32_t)sector_align(num_sectors, CACHE_INCREMENTS);\n\n    // adjust for padding\n    // FIXME - there must be a more efficient way of doing this\n    new_cache_size = sizeof(uint64_t) + (sizeof(uint32_t) * num_sectors);\n    for (i = 0; i < num_entries; i++) {\n        if ((new_cache_size >> Vcb->sector_shift) != ((new_cache_size + sizeof(FREE_SPACE_ENTRY)) >> Vcb->sector_shift))\n            new_cache_size = sector_align(new_cache_size, Vcb->superblock.sector_size);\n\n        new_cache_size += sizeof(FREE_SPACE_ENTRY);\n    }\n\n    new_cache_size = sector_align(new_cache_size, CACHE_INCREMENTS << Vcb->sector_shift);\n\n    TRACE(\"chunk %I64x: cache_size = %I64x, new_cache_size = %I64x\\n\", c->offset, c->cache ? c->cache->inode_item.st_size : 0, new_cache_size);\n\n    if (c->cache) {\n        if (new_cache_size > c->cache->inode_item.st_size)\n            realloc_extents = true;\n        else {\n            le = c->cache->extents.Flink;\n\n            while (le != &c->cache->extents) {\n                extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n                if (!ext->ignore && (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC)) {\n                    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ext->extent_data.data[0];\n\n                    if (ed2->size != 0) {\n                        chunk* c2 = get_chunk_from_address(Vcb, ed2->address);\n\n                        if (c2 && (c2->readonly || c2->reloc)) {\n                            realloc_extents = true;\n                            break;\n                        }\n                    }\n                }\n\n                le = le->Flink;\n            }\n        }\n    }\n\n    if (!c->cache) {\n        FREE_SPACE_ITEM* fsi;\n        KEY searchkey;\n        traverse_ptr tp;\n\n        // create new inode\n\n        c->cache = create_fcb(Vcb, PagedPool);\n        if (!c->cache) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        c->cache->Vcb = Vcb;\n\n        c->cache->inode_item.st_size = new_cache_size;\n        c->cache->inode_item.st_blocks = new_cache_size;\n        c->cache->inode_item.st_nlink = 1;\n        c->cache->inode_item.st_mode = S_IRUSR | S_IWUSR | __S_IFREG;\n        c->cache->inode_item.flags = BTRFS_INODE_NODATASUM | BTRFS_INODE_NODATACOW | BTRFS_INODE_NOCOMPRESS | BTRFS_INODE_PREALLOC;\n\n        c->cache->Header.IsFastIoPossible = fast_io_possible(c->cache);\n        c->cache->Header.AllocationSize.QuadPart = 0;\n        c->cache->Header.FileSize.QuadPart = 0;\n        c->cache->Header.ValidDataLength.QuadPart = 0;\n\n        c->cache->subvol = Vcb->root_root;\n\n        c->cache->inode = InterlockedIncrement64(&Vcb->root_root->lastinode);\n        c->cache->hash = calc_crc32c(0xffffffff, (uint8_t*)&c->cache->inode, sizeof(uint64_t));\n\n        c->cache->type = BTRFS_TYPE_FILE;\n        c->cache->created = true;\n\n        // create new free space entry\n\n        fsi = ExAllocatePoolWithTag(PagedPool, sizeof(FREE_SPACE_ITEM), ALLOC_TAG);\n        if (!fsi) {\n            ERR(\"out of memory\\n\");\n            reap_fcb(c->cache);\n            c->cache = NULL;\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        searchkey.obj_id = FREE_SPACE_CACHE_ID;\n        searchkey.obj_type = 0;\n        searchkey.offset = c->offset;\n\n        Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            ExFreePool(fsi);\n            reap_fcb(c->cache);\n            c->cache = NULL;\n            return Status;\n        }\n\n        if (!keycmp(searchkey, tp.item->key)) {\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                ExFreePool(fsi);\n                reap_fcb(c->cache);\n                c->cache = NULL;\n                return Status;\n            }\n        }\n\n        fsi->key.obj_id = c->cache->inode;\n        fsi->key.obj_type = TYPE_INODE_ITEM;\n        fsi->key.offset = 0;\n\n        Status = insert_tree_item(Vcb, Vcb->root_root, FREE_SPACE_CACHE_ID, 0, c->offset, fsi, sizeof(FREE_SPACE_ITEM), NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n            ExFreePool(fsi);\n            reap_fcb(c->cache);\n            c->cache = NULL;\n            return Status;\n        }\n\n        // allocate space\n\n        Status = insert_cache_extent(c->cache, 0, new_cache_size, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_cache_extent returned %08lx\\n\", Status);\n            reap_fcb(c->cache);\n            c->cache = NULL;\n            return Status;\n        }\n\n        c->cache->extents_changed = true;\n        InsertTailList(&Vcb->all_fcbs, &c->cache->list_entry_all);\n\n        add_fcb_to_subvol(c->cache);\n\n        Status = flush_fcb(c->cache, true, batchlist, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"flush_fcb returned %08lx\\n\", Status);\n            free_fcb(c->cache);\n            c->cache = NULL;\n            return Status;\n        }\n\n        *changed = true;\n    } else if (realloc_extents) {\n        KEY searchkey;\n        traverse_ptr tp;\n\n        TRACE(\"reallocating extents\\n\");\n\n        // add free_space entry to tree cache\n\n        searchkey.obj_id = FREE_SPACE_CACHE_ID;\n        searchkey.obj_type = 0;\n        searchkey.offset = c->offset;\n\n        Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (keycmp(searchkey, tp.item->key)) {\n            ERR(\"could not find (%I64x,%x,%I64x) in root_root\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (tp.item->size < sizeof(FREE_SPACE_ITEM)) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(FREE_SPACE_ITEM));\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        tp.tree->write = true;\n\n        // remove existing extents\n\n        if (c->cache->inode_item.st_size > 0) {\n            le = c->cache->extents.Flink;\n\n            while (le != &c->cache->extents) {\n                extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n                if (!ext->ignore && (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC)) {\n                    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ext->extent_data.data[0];\n\n                    if (ed2->size != 0) {\n                        chunk* c2 = get_chunk_from_address(Vcb, ed2->address);\n\n                        if (c2) {\n                            c2->changed = true;\n                            c2->space_changed = true;\n                        }\n                    }\n                }\n\n                le = le->Flink;\n            }\n\n            Status = excise_extents(Vcb, c->cache, 0, c->cache->inode_item.st_size, Irp, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"excise_extents returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        // add new extent\n\n        Status = insert_cache_extent(c->cache, 0, new_cache_size, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_cache_extent returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        // modify INODE_ITEM\n\n        c->cache->inode_item.st_size = new_cache_size;\n        c->cache->inode_item.st_blocks = new_cache_size;\n\n        Status = flush_fcb(c->cache, true, batchlist, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"flush_fcb returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        *changed = true;\n    } else {\n        KEY searchkey;\n        traverse_ptr tp;\n\n        // add INODE_ITEM and free_space entry to tree cache, for writing later\n\n        searchkey.obj_id = c->cache->inode;\n        searchkey.obj_type = TYPE_INODE_ITEM;\n        searchkey.offset = 0;\n\n        Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (keycmp(searchkey, tp.item->key)) {\n            INODE_ITEM* ii;\n\n            ii = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_ITEM), ALLOC_TAG);\n            if (!ii) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(ii, &c->cache->inode_item, sizeof(INODE_ITEM));\n\n            Status = insert_tree_item(Vcb, Vcb->root_root, c->cache->inode, TYPE_INODE_ITEM, 0, ii, sizeof(INODE_ITEM), NULL, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"insert_tree_item returned %08lx\\n\", Status);\n                ExFreePool(ii);\n                return Status;\n            }\n\n            *changed = true;\n        } else {\n            if (tp.item->size < sizeof(INODE_ITEM)) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(INODE_ITEM));\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            tp.tree->write = true;\n        }\n\n        searchkey.obj_id = FREE_SPACE_CACHE_ID;\n        searchkey.obj_type = 0;\n        searchkey.offset = c->offset;\n\n        Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"error - find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (keycmp(searchkey, tp.item->key)) {\n            ERR(\"could not find (%I64x,%x,%I64x) in root_root\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (tp.item->size < sizeof(FREE_SPACE_ITEM)) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(FREE_SPACE_ITEM));\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        tp.tree->write = true;\n    }\n\n    // FIXME - reduce inode allocation if cache is shrinking. Make sure to avoid infinite write loops\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS allocate_cache(device_extension* Vcb, bool* changed, PIRP Irp, LIST_ENTRY* rollback) {\n    LIST_ENTRY *le, batchlist;\n    NTSTATUS Status;\n\n    *changed = false;\n\n    InitializeListHead(&batchlist);\n\n    ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true);\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (c->space_changed && c->chunk_item->size >= 0x6400000) { // 100MB\n            bool b;\n\n            acquire_chunk_lock(c, Vcb);\n            Status = allocate_cache_chunk(Vcb, c, &b, &batchlist, Irp, rollback);\n            release_chunk_lock(c, Vcb);\n\n            if (b)\n                *changed = true;\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"allocate_cache_chunk(%I64x) returned %08lx\\n\", c->offset, Status);\n                ExReleaseResourceLite(&Vcb->chunk_lock);\n                clear_batch_list(Vcb, &batchlist);\n                return Status;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&Vcb->chunk_lock);\n\n    Status = commit_batch_list(Vcb, &batchlist, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"commit_batch_list returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic void add_rollback_space(LIST_ENTRY* rollback, bool add, LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t address, uint64_t length, chunk* c) {\n    rollback_space* rs;\n\n    rs = ExAllocatePoolWithTag(PagedPool, sizeof(rollback_space), ALLOC_TAG);\n    if (!rs) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    rs->list = list;\n    rs->list_size = list_size;\n    rs->address = address;\n    rs->length = length;\n    rs->chunk = c;\n\n    add_rollback(rollback, add ? ROLLBACK_ADD_SPACE : ROLLBACK_SUBTRACT_SPACE, rs);\n}\n\nvoid space_list_add2(LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t address, uint64_t length, chunk* c, LIST_ENTRY* rollback) {\n    LIST_ENTRY* le;\n    space *s, *s2;\n\n    if (IsListEmpty(list)) {\n        s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);\n\n        if (!s) {\n            ERR(\"out of memory\\n\");\n            return;\n        }\n\n        s->address = address;\n        s->size = length;\n        InsertTailList(list, &s->list_entry);\n\n        if (list_size)\n            InsertTailList(list_size, &s->list_entry_size);\n\n        if (rollback)\n            add_rollback_space(rollback, true, list, list_size, address, length, c);\n\n        return;\n    }\n\n    le = list->Flink;\n    do {\n        s2 = CONTAINING_RECORD(le, space, list_entry);\n\n        // old entry envelops new one completely\n        if (s2->address <= address && s2->address + s2->size >= address + length)\n            return;\n\n        // new entry envelops old one completely\n        if (address <= s2->address && address + length >= s2->address + s2->size) {\n            if (address < s2->address) {\n                if (rollback)\n                    add_rollback_space(rollback, true, list, list_size, address, s2->address - address, c);\n\n                s2->size += s2->address - address;\n                s2->address = address;\n\n                while (s2->list_entry.Blink != list) {\n                    space* s3 = CONTAINING_RECORD(s2->list_entry.Blink, space, list_entry);\n\n                    if (s3->address + s3->size == s2->address) {\n                        s2->address = s3->address;\n                        s2->size += s3->size;\n\n                        RemoveEntryList(&s3->list_entry);\n\n                        if (list_size)\n                            RemoveEntryList(&s3->list_entry_size);\n\n                        ExFreePool(s3);\n                    } else\n                        break;\n                }\n            }\n\n            if (length > s2->size) {\n                if (rollback)\n                    add_rollback_space(rollback, true, list, list_size, s2->address + s2->size, address + length - s2->address - s2->size, c);\n\n                s2->size = length;\n\n                while (s2->list_entry.Flink != list) {\n                    space* s3 = CONTAINING_RECORD(s2->list_entry.Flink, space, list_entry);\n\n                    if (s3->address <= s2->address + s2->size) {\n                        s2->size = max(s2->size, s3->address + s3->size - s2->address);\n\n                        RemoveEntryList(&s3->list_entry);\n\n                        if (list_size)\n                            RemoveEntryList(&s3->list_entry_size);\n\n                        ExFreePool(s3);\n                    } else\n                        break;\n                }\n            }\n\n            if (list_size) {\n                RemoveEntryList(&s2->list_entry_size);\n                order_space_entry(s2, list_size);\n            }\n\n            return;\n        }\n\n        // new entry overlaps start of old one\n        if (address < s2->address && address + length >= s2->address) {\n            if (rollback)\n                add_rollback_space(rollback, true, list, list_size, address, s2->address - address, c);\n\n            s2->size += s2->address - address;\n            s2->address = address;\n\n            while (s2->list_entry.Blink != list) {\n                space* s3 = CONTAINING_RECORD(s2->list_entry.Blink, space, list_entry);\n\n                if (s3->address + s3->size == s2->address) {\n                    s2->address = s3->address;\n                    s2->size += s3->size;\n\n                    RemoveEntryList(&s3->list_entry);\n\n                    if (list_size)\n                        RemoveEntryList(&s3->list_entry_size);\n\n                    ExFreePool(s3);\n                } else\n                    break;\n            }\n\n            if (list_size) {\n                RemoveEntryList(&s2->list_entry_size);\n                order_space_entry(s2, list_size);\n            }\n\n            return;\n        }\n\n        // new entry overlaps end of old one\n        if (address <= s2->address + s2->size && address + length > s2->address + s2->size) {\n            if (rollback)\n                add_rollback_space(rollback, true, list, list_size, address, s2->address + s2->size - address, c);\n\n            s2->size = address + length - s2->address;\n\n            while (s2->list_entry.Flink != list) {\n                space* s3 = CONTAINING_RECORD(s2->list_entry.Flink, space, list_entry);\n\n                if (s3->address <= s2->address + s2->size) {\n                    s2->size = max(s2->size, s3->address + s3->size - s2->address);\n\n                    RemoveEntryList(&s3->list_entry);\n\n                    if (list_size)\n                        RemoveEntryList(&s3->list_entry_size);\n\n                    ExFreePool(s3);\n                } else\n                    break;\n            }\n\n            if (list_size) {\n                RemoveEntryList(&s2->list_entry_size);\n                order_space_entry(s2, list_size);\n            }\n\n            return;\n        }\n\n        // add completely separate entry\n        if (s2->address > address + length) {\n            s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);\n\n            if (!s) {\n                ERR(\"out of memory\\n\");\n                return;\n            }\n\n            if (rollback)\n                add_rollback_space(rollback, true, list, list_size, address, length, c);\n\n            s->address = address;\n            s->size = length;\n            InsertHeadList(s2->list_entry.Blink, &s->list_entry);\n\n            if (list_size)\n                order_space_entry(s, list_size);\n\n            return;\n        }\n\n        le = le->Flink;\n    } while (le != list);\n\n    // check if contiguous with last entry\n    if (s2->address + s2->size == address) {\n        s2->size += length;\n\n        if (list_size) {\n            RemoveEntryList(&s2->list_entry_size);\n            order_space_entry(s2, list_size);\n        }\n\n        return;\n    }\n\n    // otherwise, insert at end\n    s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);\n\n    if (!s) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    s->address = address;\n    s->size = length;\n    InsertTailList(list, &s->list_entry);\n\n    if (list_size)\n        order_space_entry(s, list_size);\n\n    if (rollback)\n        add_rollback_space(rollback, true, list, list_size, address, length, c);\n}\n\nvoid space_list_merge(LIST_ENTRY* spacelist, LIST_ENTRY* spacelist_size, LIST_ENTRY* deleting) {\n    LIST_ENTRY* le = deleting->Flink;\n\n    while (le != deleting) {\n        space* s = CONTAINING_RECORD(le, space, list_entry);\n\n        space_list_add2(spacelist, spacelist_size, s->address, s->size, NULL, NULL);\n\n        le = le->Flink;\n    }\n}\n\nstatic NTSTATUS copy_space_list(LIST_ENTRY* old_list, LIST_ENTRY* new_list) {\n    LIST_ENTRY* le;\n\n    le = old_list->Flink;\n    while (le != old_list) {\n        space* s = CONTAINING_RECORD(le, space, list_entry);\n        space* s2;\n\n        s2 = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);\n        if (!s2) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        s2->address = s->address;\n        s2->size = s->size;\n\n        InsertTailList(new_list, &s2->list_entry);\n\n        le = le->Flink;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS update_chunk_cache(device_extension* Vcb, chunk* c, BTRFS_TIME* now, LIST_ENTRY* batchlist, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    FREE_SPACE_ITEM* fsi;\n    void* data;\n    uint64_t num_entries, *cachegen, off;\n    uint32_t *checksums, num_sectors;\n    LIST_ENTRY space_list, deleting;\n\n    data = ExAllocatePoolWithTag(NonPagedPool, (ULONG)c->cache->inode_item.st_size, ALLOC_TAG);\n    if (!data) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(data, (ULONG)c->cache->inode_item.st_size);\n\n    InitializeListHead(&space_list);\n    InitializeListHead(&deleting);\n\n    Status = copy_space_list(&c->space, &space_list);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"copy_space_list returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    Status = copy_space_list(&c->deleting, &deleting);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"copy_space_list returned %08lx\\n\", Status);\n\n        while (!IsListEmpty(&space_list)) {\n            ExFreePool(CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry));\n        }\n\n        goto end;\n    }\n\n    space_list_merge(&space_list, NULL, &deleting);\n\n    num_entries = 0;\n    num_sectors = (uint32_t)(c->cache->inode_item.st_size >> Vcb->sector_shift);\n    off = (sizeof(uint32_t) * num_sectors) + sizeof(uint64_t);\n\n    while (!IsListEmpty(&space_list)) {\n        FREE_SPACE_ENTRY* fse;\n\n        space* s = CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry);\n\n        if ((off + sizeof(FREE_SPACE_ENTRY)) >> Vcb->sector_shift != off >> Vcb->sector_shift)\n            off = sector_align(off, Vcb->superblock.sector_size);\n\n        fse = (FREE_SPACE_ENTRY*)((uint8_t*)data + off);\n\n        fse->offset = s->address;\n        fse->size = s->size;\n        fse->type = FREE_SPACE_EXTENT;\n        num_entries++;\n\n        off += sizeof(FREE_SPACE_ENTRY);\n    }\n\n    // update INODE_ITEM\n\n    c->cache->inode_item.generation = Vcb->superblock.generation;\n    c->cache->inode_item.transid = Vcb->superblock.generation;\n    c->cache->inode_item.sequence++;\n    c->cache->inode_item.st_ctime = *now;\n\n    Status = flush_fcb(c->cache, true, batchlist, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"flush_fcb returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    // update free_space item\n\n    searchkey.obj_id = FREE_SPACE_CACHE_ID;\n    searchkey.obj_type = 0;\n    searchkey.offset = c->offset;\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (keycmp(searchkey, tp.item->key)) {\n        ERR(\"could not find (%I64x,%x,%I64x) in root_root\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n        Status = STATUS_INTERNAL_ERROR;\n        goto end;\n    }\n\n    if (tp.item->size < sizeof(FREE_SPACE_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(FREE_SPACE_ITEM));\n        Status = STATUS_INTERNAL_ERROR;\n        goto end;\n    }\n\n    fsi = (FREE_SPACE_ITEM*)tp.item->data;\n\n    fsi->generation = Vcb->superblock.generation;\n    fsi->num_entries = num_entries;\n    fsi->num_bitmaps = 0;\n\n    // set cache generation\n\n    cachegen = (uint64_t*)((uint8_t*)data + (sizeof(uint32_t) * num_sectors));\n    *cachegen = Vcb->superblock.generation;\n\n    // calculate cache checksums\n\n    checksums = (uint32_t*)data;\n\n    // FIXME - if we know sector is fully zeroed, use cached checksum\n\n    for (uint32_t i = 0; i < num_sectors; i++) {\n        if (i << Vcb->sector_shift > sizeof(uint32_t) * num_sectors)\n            checksums[i] = ~calc_crc32c(0xffffffff, (uint8_t*)data + (i << Vcb->sector_shift), Vcb->superblock.sector_size);\n        else if ((i + 1) << Vcb->sector_shift < sizeof(uint32_t) * num_sectors)\n            checksums[i] = 0; // FIXME - test this\n        else\n            checksums[i] = ~calc_crc32c(0xffffffff, (uint8_t*)data + (sizeof(uint32_t) * num_sectors), ((i + 1) << Vcb->sector_shift) - (sizeof(uint32_t) * num_sectors));\n    }\n\n    // write cache\n\n    Status = do_write_file(c->cache, 0, c->cache->inode_item.st_size, data, NULL, false, 0, rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"do_write_file returned %08lx\\n\", Status);\n\n        // Writing the cache isn't critical, so we don't return an error if writing fails. This means\n        // we can still flush on a degraded mount if metadata is RAID1 but data is RAID0.\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExFreePool(data);\n\n    return Status;\n}\n\nstatic NTSTATUS update_chunk_cache_tree(device_extension* Vcb, chunk* c, PIRP Irp) {\n    NTSTATUS Status;\n    LIST_ENTRY space_list;\n    FREE_SPACE_INFO* fsi;\n    KEY searchkey;\n    traverse_ptr tp;\n    uint32_t fsi_count = 0;\n\n    InitializeListHead(&space_list);\n\n    Status = copy_space_list(&c->space, &space_list);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"copy_space_list returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    space_list_merge(&space_list, NULL, &c->deleting);\n\n    searchkey.obj_id = c->offset;\n    searchkey.obj_type = TYPE_FREE_SPACE_EXTENT;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->space_root, &tp, &searchkey, false, Irp);\n    if (Status == STATUS_NOT_FOUND)\n        goto after_tree_walk;\n    else if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n\n        while (!IsListEmpty(&space_list)) {\n            ExFreePool(CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry));\n        }\n\n        return Status;\n    }\n\n    while (!IsListEmpty(&space_list) && tp.item->key.obj_id < c->offset + c->chunk_item->size) {\n        traverse_ptr next_tp;\n\n        if (tp.item->key.obj_type == TYPE_FREE_SPACE_EXTENT) {\n            space* s = CONTAINING_RECORD(space_list.Flink, space, list_entry);\n\n            if (s->address < tp.item->key.obj_id || (s->address == tp.item->key.obj_id && s->size < tp.item->key.offset)) {\n                // add entry\n\n                Status = insert_tree_item(Vcb, Vcb->space_root, s->address, TYPE_FREE_SPACE_EXTENT, s->size, NULL, 0,\n                                          NULL, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"insert_tree_item returned %08lx\\n\", Status);\n\n                    while (!IsListEmpty(&space_list)) {\n                        ExFreePool(CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry));\n                    }\n\n                    return Status;\n                }\n\n                fsi_count++;\n\n                RemoveHeadList(&space_list);\n                ExFreePool(s);\n                continue;\n            } else if (s->address == tp.item->key.obj_id && s->size == tp.item->key.offset) {\n                // unchanged entry\n\n                fsi_count++;\n\n                RemoveHeadList(&space_list);\n                ExFreePool(s);\n            } else {\n                // remove entry\n\n                Status = delete_tree_item(Vcb, &tp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"delete_tree_item returned %08lx\\n\", Status);\n\n                    while (!IsListEmpty(&space_list)) {\n                        ExFreePool(CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry));\n                    }\n\n                    return Status;\n                }\n            }\n        } else if (tp.item->key.obj_type == TYPE_FREE_SPACE_BITMAP) {\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n\n                while (!IsListEmpty(&space_list)) {\n                    ExFreePool(CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry));\n                }\n\n                return Status;\n            }\n        }\n\n        if (!find_next_item(Vcb, &tp, &next_tp, false, Irp))\n            goto after_tree_walk;\n\n        tp = next_tp;\n    }\n\n    // after loop, delete remaining tree items for this chunk\n\n    while (tp.item->key.obj_id < c->offset + c->chunk_item->size) {\n        traverse_ptr next_tp;\n\n        if (tp.item->key.obj_type == TYPE_FREE_SPACE_EXTENT || tp.item->key.obj_type == TYPE_FREE_SPACE_BITMAP) {\n            Status = delete_tree_item(Vcb, &tp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"delete_tree_item returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        if (!find_next_item(Vcb, &tp, &next_tp, false, NULL))\n            break;\n\n        tp = next_tp;\n    }\n\nafter_tree_walk:\n    // after loop, insert remaining space_list entries\n\n    while (!IsListEmpty(&space_list)) {\n        space* s = CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry);\n\n        Status = insert_tree_item(Vcb, Vcb->space_root, s->address, TYPE_FREE_SPACE_EXTENT, s->size, NULL, 0,\n                                  NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_tree_item returned %08lx\\n\", Status);\n\n            while (!IsListEmpty(&space_list)) {\n                ExFreePool(CONTAINING_RECORD(RemoveHeadList(&space_list), space, list_entry));\n            }\n\n            return Status;\n        }\n\n        fsi_count++;\n\n        ExFreePool(s);\n    }\n\n    // change TYPE_FREE_SPACE_INFO in place if present, and insert otherwise\n\n    searchkey.obj_id = c->offset;\n    searchkey.obj_type = TYPE_FREE_SPACE_INFO;\n    searchkey.offset = c->chunk_item->size;\n\n    Status = find_item(Vcb, Vcb->space_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (NT_SUCCESS(Status) && !keycmp(tp.item->key, searchkey)) {\n        if (tp.item->size == sizeof(FREE_SPACE_INFO)) {\n            tree* t;\n\n            // change in place if possible\n\n            fsi = (FREE_SPACE_INFO*)tp.item->data;\n\n            fsi->count = fsi_count;\n            fsi->flags = 0;\n\n            tp.tree->write = true;\n\n            t = tp.tree;\n            while (t) {\n                if (t->paritem && t->paritem->ignore) {\n                    t->paritem->ignore = false;\n                    t->parent->header.num_items++;\n                    t->parent->size += sizeof(internal_node);\n                }\n\n                t->header.generation = Vcb->superblock.generation;\n                t = t->parent;\n            }\n\n            return STATUS_SUCCESS;\n        } else\n            delete_tree_item(Vcb, &tp);\n    }\n\n    // insert FREE_SPACE_INFO\n\n    fsi = ExAllocatePoolWithTag(PagedPool, sizeof(FREE_SPACE_INFO), ALLOC_TAG);\n    if (!fsi) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    fsi->count = fsi_count;\n    fsi->flags = 0;\n\n    Status = insert_tree_item(Vcb, Vcb->space_root, c->offset, TYPE_FREE_SPACE_INFO, c->chunk_item->size, fsi, sizeof(*fsi),\n                              NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS update_chunk_caches(device_extension* Vcb, PIRP Irp, LIST_ENTRY* rollback) {\n    LIST_ENTRY *le, batchlist;\n    NTSTATUS Status;\n    chunk* c;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    InitializeListHead(&batchlist);\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (c->space_changed && c->chunk_item->size >= 0x6400000) { // 100MB\n            acquire_chunk_lock(c, Vcb);\n            Status = update_chunk_cache(Vcb, c, &now, &batchlist, Irp, rollback);\n            release_chunk_lock(c, Vcb);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"update_chunk_cache(%I64x) returned %08lx\\n\", c->offset, Status);\n                clear_batch_list(Vcb, &batchlist);\n                return Status;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    Status = commit_batch_list(Vcb, &batchlist, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"commit_batch_list returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (c->changed && (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6)) {\n            ExAcquireResourceExclusiveLite(&c->partial_stripes_lock, true);\n\n            while (!IsListEmpty(&c->partial_stripes)) {\n                partial_stripe* ps = CONTAINING_RECORD(RemoveHeadList(&c->partial_stripes), partial_stripe, list_entry);\n\n                Status = flush_partial_stripe(Vcb, c, ps);\n\n                if (ps->bmparr)\n                    ExFreePool(ps->bmparr);\n\n                ExFreePool(ps);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"flush_partial_stripe returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(&c->partial_stripes_lock);\n                    return Status;\n                }\n            }\n\n            ExReleaseResourceLite(&c->partial_stripes_lock);\n        }\n\n        le = le->Flink;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS update_chunk_caches_tree(device_extension* Vcb, PIRP Irp) {\n    LIST_ENTRY *le;\n    NTSTATUS Status;\n    chunk* c;\n\n    Vcb->superblock.compat_ro_flags |= BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID;\n\n    ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (c->space_changed) {\n            acquire_chunk_lock(c, Vcb);\n            Status = update_chunk_cache_tree(Vcb, c, Irp);\n            release_chunk_lock(c, Vcb);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"update_chunk_cache_tree(%I64x) returned %08lx\\n\", c->offset, Status);\n                ExReleaseResourceLite(&Vcb->chunk_lock);\n                return Status;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&Vcb->chunk_lock);\n\n    return STATUS_SUCCESS;\n}\n\nvoid space_list_add(chunk* c, uint64_t address, uint64_t length, LIST_ENTRY* rollback) {\n    TRACE(\"(%p, %I64x, %I64x, %p)\\n\", c, address, length, rollback);\n\n    c->changed = true;\n    c->space_changed = true;\n\n    space_list_add2(&c->deleting, NULL, address, length, c, rollback);\n}\n\nvoid space_list_subtract2(LIST_ENTRY* list, LIST_ENTRY* list_size, uint64_t address, uint64_t length, chunk* c, LIST_ENTRY* rollback) {\n    LIST_ENTRY *le, *le2;\n    space *s, *s2;\n\n    if (IsListEmpty(list))\n        return;\n\n    le = list->Flink;\n    while (le != list) {\n        s2 = CONTAINING_RECORD(le, space, list_entry);\n        le2 = le->Flink;\n\n        if (s2->address >= address + length)\n            return;\n\n        if (s2->address >= address && s2->address + s2->size <= address + length) { // remove entry entirely\n            if (rollback)\n                add_rollback_space(rollback, false, list, list_size, s2->address, s2->size, c);\n\n            RemoveEntryList(&s2->list_entry);\n\n            if (list_size)\n                RemoveEntryList(&s2->list_entry_size);\n\n            ExFreePool(s2);\n        } else if (address + length > s2->address && address + length < s2->address + s2->size) {\n            if (address > s2->address) { // cut out hole\n                if (rollback)\n                    add_rollback_space(rollback, false, list, list_size, address, length, c);\n\n                s = ExAllocatePoolWithTag(PagedPool, sizeof(space), ALLOC_TAG);\n\n                if (!s) {\n                    ERR(\"out of memory\\n\");\n                    return;\n                }\n\n                s->address = s2->address;\n                s->size = address - s2->address;\n                InsertHeadList(s2->list_entry.Blink, &s->list_entry);\n\n                s2->size = s2->address + s2->size - address - length;\n                s2->address = address + length;\n\n                if (list_size) {\n                    RemoveEntryList(&s2->list_entry_size);\n                    order_space_entry(s2, list_size);\n                    order_space_entry(s, list_size);\n                }\n\n                return;\n            } else { // remove start of entry\n                if (rollback)\n                    add_rollback_space(rollback, false, list, list_size, s2->address, address + length - s2->address, c);\n\n                s2->size -= address + length - s2->address;\n                s2->address = address + length;\n\n                if (list_size) {\n                    RemoveEntryList(&s2->list_entry_size);\n                    order_space_entry(s2, list_size);\n                }\n            }\n        } else if (address > s2->address && address < s2->address + s2->size) { // remove end of entry\n            if (rollback)\n                add_rollback_space(rollback, false, list, list_size, address, s2->address + s2->size - address, c);\n\n            s2->size = address - s2->address;\n\n            if (list_size) {\n                RemoveEntryList(&s2->list_entry_size);\n                order_space_entry(s2, list_size);\n            }\n        }\n\n        le = le2;\n    }\n}\n\nvoid space_list_subtract(chunk* c, uint64_t address, uint64_t length, LIST_ENTRY* rollback) {\n    c->changed = true;\n    c->space_changed = true;\n\n    space_list_subtract2(&c->space, &c->space_size, address, length, c, rollback);\n\n    space_list_subtract2(&c->deleting, NULL, address, length, c, rollback);\n}\n"
  },
  {
    "path": "src/fsctl.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include \"btrfsioctl.h\"\n#include \"crc32c.h\"\n#include <ntddstor.h>\n#include <ntdddisk.h>\n#include <sys/stat.h>\n\n#ifndef FSCTL_CSV_CONTROL\n#define FSCTL_CSV_CONTROL CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 181, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#endif\n\n#ifndef FSCTL_QUERY_VOLUME_CONTAINER_STATE\n#define FSCTL_QUERY_VOLUME_CONTAINER_STATE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 228, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#endif\n\n#define DOTDOT \"..\"\n\n#define SEF_AVOID_PRIVILEGE_CHECK 0x08 // on MSDN but not in any header files(?)\n\n#define SEF_SACL_AUTO_INHERIT 0x02\n\nextern LIST_ENTRY VcbList;\nextern ERESOURCE global_loading_lock;\nextern PDRIVER_OBJECT drvobj;\nextern tFsRtlCheckLockForOplockRequest fFsRtlCheckLockForOplockRequest;\nextern tFsRtlAreThereCurrentOrInProgressFileLocks fFsRtlAreThereCurrentOrInProgressFileLocks;\n\nstatic void mark_subvol_dirty(device_extension* Vcb, root* r);\n\nstatic NTSTATUS get_file_ids(PFILE_OBJECT FileObject, void* data, ULONG length) {\n    btrfs_get_file_ids* bgfi;\n    fcb* fcb;\n\n    if (length < sizeof(btrfs_get_file_ids))\n        return STATUS_BUFFER_OVERFLOW;\n\n    if (!FileObject)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    bgfi = data;\n\n    bgfi->subvol = fcb->subvol->id;\n    bgfi->inode = fcb->inode;\n    bgfi->top = fcb->Vcb->root_fileref->fcb == fcb ? true : false;\n\n    return STATUS_SUCCESS;\n}\n\nstatic void get_uuid(BTRFS_UUID* uuid) {\n    LARGE_INTEGER seed;\n    uint8_t i;\n\n    seed = KeQueryPerformanceCounter(NULL);\n\n    for (i = 0; i < 16; i+=2) {\n        ULONG rand = RtlRandomEx(&seed.LowPart);\n\n        uuid->uuid[i] = (rand & 0xff00) >> 8;\n        uuid->uuid[i+1] = rand & 0xff;\n    }\n}\n\nstatic NTSTATUS snapshot_tree_copy(device_extension* Vcb, uint64_t addr, root* subvol, uint64_t* newaddr, PIRP Irp, LIST_ENTRY* rollback) {\n    uint8_t* buf;\n    NTSTATUS Status;\n    write_data_context wtc;\n    LIST_ENTRY* le;\n    tree t;\n    tree_header* th;\n    chunk* c;\n\n    buf = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG);\n    if (!buf) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    wtc.parity1 = wtc.parity2 = wtc.scratch = NULL;\n    wtc.mdl = wtc.parity1_mdl = wtc.parity2_mdl = NULL;\n\n    Status = read_data(Vcb, addr, Vcb->superblock.node_size, NULL, true, buf, NULL, NULL, Irp, 0, false, NormalPagePriority);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"read_data returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    th = (tree_header*)buf;\n\n    RtlZeroMemory(&t, sizeof(tree));\n    t.root = subvol;\n    t.header.level = th->level;\n    t.header.tree_id = t.root->id;\n\n    Status = get_tree_new_address(Vcb, &t, Irp, rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"get_tree_new_address returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (!t.has_new_address) {\n        ERR(\"tree new address not set\\n\");\n        Status = STATUS_INTERNAL_ERROR;\n        goto end;\n    }\n\n    c = get_chunk_from_address(Vcb, t.new_address);\n\n    if (c)\n        c->used += Vcb->superblock.node_size;\n    else {\n        ERR(\"could not find chunk for address %I64x\\n\", t.new_address);\n        Status = STATUS_INTERNAL_ERROR;\n        goto end;\n    }\n\n    th->address = t.new_address;\n    th->tree_id = subvol->id;\n    th->generation = Vcb->superblock.generation;\n    th->fs_uuid = Vcb->superblock.metadata_uuid;\n\n    if (th->level == 0) {\n        uint32_t i;\n        leaf_node* ln = (leaf_node*)&th[1];\n\n        for (i = 0; i < th->num_items; i++) {\n            if (ln[i].key.obj_type == TYPE_EXTENT_DATA && ln[i].size >= sizeof(EXTENT_DATA) && ln[i].offset + ln[i].size <= Vcb->superblock.node_size - sizeof(tree_header)) {\n                EXTENT_DATA* ed = (EXTENT_DATA*)(((uint8_t*)&th[1]) + ln[i].offset);\n\n                if ((ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC) && ln[i].size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {\n                    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)&ed->data[0];\n\n                    if (ed2->size != 0) { // not sparse\n                        Status = increase_extent_refcount_data(Vcb, ed2->address, ed2->size, subvol->id, ln[i].key.obj_id, ln[i].key.offset - ed2->offset, 1, Irp);\n\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"increase_extent_refcount_data returned %08lx\\n\", Status);\n                            goto end;\n                        }\n                    }\n                }\n            }\n        }\n    } else {\n        uint32_t i;\n        internal_node* in = (internal_node*)&th[1];\n\n        for (i = 0; i < th->num_items; i++) {\n            TREE_BLOCK_REF tbr;\n\n            tbr.offset = subvol->id;\n\n            Status = increase_extent_refcount(Vcb, in[i].address, Vcb->superblock.node_size, TYPE_TREE_BLOCK_REF, &tbr, NULL, th->level - 1, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"increase_extent_refcount returned %08lx\\n\", Status);\n                goto end;\n            }\n        }\n    }\n\n    calc_tree_checksum(Vcb, th);\n\n    KeInitializeEvent(&wtc.Event, NotificationEvent, false);\n    InitializeListHead(&wtc.stripes);\n    wtc.stripes_left = 0;\n\n    Status = write_data(Vcb, t.new_address, buf, Vcb->superblock.node_size, &wtc, NULL, NULL, false, 0, NormalPagePriority);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"write_data returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (wtc.stripes.Flink != &wtc.stripes) {\n        bool need_wait = false;\n\n        // launch writes and wait\n        le = wtc.stripes.Flink;\n        while (le != &wtc.stripes) {\n            write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry);\n\n            if (stripe->status != WriteDataStatus_Ignore) {\n                need_wait = true;\n                IoCallDriver(stripe->device->devobj, stripe->Irp);\n            }\n\n            le = le->Flink;\n        }\n\n        if (need_wait)\n            KeWaitForSingleObject(&wtc.Event, Executive, KernelMode, false, NULL);\n\n        le = wtc.stripes.Flink;\n        while (le != &wtc.stripes) {\n            write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry);\n\n            if (stripe->status != WriteDataStatus_Ignore && !NT_SUCCESS(stripe->iosb.Status)) {\n                Status = stripe->iosb.Status;\n                log_device_error(Vcb, stripe->device, BTRFS_DEV_STAT_WRITE_ERRORS);\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        free_write_data_stripes(&wtc);\n        buf = NULL;\n    }\n\n    if (NT_SUCCESS(Status))\n        *newaddr = t.new_address;\n\nend:\n\n    if (buf)\n        ExFreePool(buf);\n\n    return Status;\n}\n\nvoid flush_subvol_fcbs(root* subvol) {\n    LIST_ENTRY* le = subvol->fcbs.Flink;\n\n    if (IsListEmpty(&subvol->fcbs))\n        return;\n\n    while (le != &subvol->fcbs) {\n        struct _fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry);\n        IO_STATUS_BLOCK iosb;\n\n        if (fcb->type != BTRFS_TYPE_DIRECTORY && !fcb->deleted)\n            CcFlushCache(&fcb->nonpaged->segment_object, NULL, 0, &iosb);\n\n        le = le->Flink;\n    }\n}\n\nstatic NTSTATUS do_create_snapshot(device_extension* Vcb, PFILE_OBJECT parent, fcb* subvol_fcb, PANSI_STRING utf8, PUNICODE_STRING name, bool readonly, PIRP Irp) {\n    LIST_ENTRY rollback;\n    uint64_t id;\n    NTSTATUS Status;\n    root *r, *subvol = subvol_fcb->subvol;\n    KEY searchkey;\n    traverse_ptr tp;\n    uint64_t address, *root_num;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n    fcb* fcb = parent->FsContext;\n    ccb* ccb = parent->FsContext2;\n    LIST_ENTRY* le;\n    file_ref *fileref, *fr;\n    dir_child* dc = NULL;\n\n    if (!ccb) {\n        ERR(\"error - ccb was NULL\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (!(ccb->access & FILE_ADD_SUBDIRECTORY)) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    fileref = ccb->fileref;\n\n    if (fileref->fcb == Vcb->dummy_fcb)\n        return STATUS_ACCESS_DENIED;\n\n    // flush open files on this subvol\n\n    flush_subvol_fcbs(subvol);\n\n    // flush metadata\n\n    if (Vcb->need_write)\n        Status = do_write(Vcb, Irp);\n    else\n        Status = STATUS_SUCCESS;\n\n    free_trees(Vcb);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"do_write returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    InitializeListHead(&rollback);\n\n    // create new root\n\n    id = InterlockedIncrement64(&Vcb->root_root->lastinode);\n    Status = create_root(Vcb, id, &r, true, Vcb->superblock.generation, Irp);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"create_root returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    r->lastinode = subvol->lastinode;\n\n    if (!Vcb->uuid_root) {\n        root* uuid_root;\n\n        TRACE(\"uuid root doesn't exist, creating it\\n\");\n\n        Status = create_root(Vcb, BTRFS_ROOT_UUID, &uuid_root, false, 0, Irp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"create_root returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        Vcb->uuid_root = uuid_root;\n    }\n\n    root_num = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t), ALLOC_TAG);\n    if (!root_num) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    tp.tree = NULL;\n\n    do {\n        get_uuid(&r->root_item.uuid);\n\n        RtlCopyMemory(&searchkey.obj_id, &r->root_item.uuid, sizeof(uint64_t));\n        searchkey.obj_type = TYPE_SUBVOL_UUID;\n        RtlCopyMemory(&searchkey.offset, &r->root_item.uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t));\n\n        Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp);\n    } while (NT_SUCCESS(Status) && !keycmp(searchkey, tp.item->key));\n\n    *root_num = r->id;\n\n    Status = insert_tree_item(Vcb, Vcb->uuid_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, root_num, sizeof(uint64_t), NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(root_num);\n        goto end;\n    }\n\n    searchkey.obj_id = r->id;\n    searchkey.obj_type = TYPE_ROOT_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    Status = snapshot_tree_copy(Vcb, subvol->root_item.block_number, r, &address, Irp, &rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"snapshot_tree_copy returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    r->root_item.inode.generation = 1;\n    r->root_item.inode.st_size = 3;\n    r->root_item.inode.st_blocks = subvol->root_item.inode.st_blocks;\n    r->root_item.inode.st_nlink = 1;\n    r->root_item.inode.st_mode = __S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; // 40755\n    r->root_item.inode.flags = 0x80000000; // FIXME - find out what these mean\n    r->root_item.inode.flags_ro = 0xffffffff; // FIXME - find out what these mean\n    r->root_item.generation = Vcb->superblock.generation;\n    r->root_item.objid = subvol->root_item.objid;\n    r->root_item.block_number = address;\n    r->root_item.bytes_used = subvol->root_item.bytes_used;\n    r->root_item.last_snapshot_generation = Vcb->superblock.generation;\n    r->root_item.root_level = subvol->root_item.root_level;\n    r->root_item.generation2 = Vcb->superblock.generation;\n    r->root_item.parent_uuid = subvol->root_item.uuid;\n    r->root_item.ctransid = subvol->root_item.ctransid;\n    r->root_item.otransid = Vcb->superblock.generation;\n    r->root_item.ctime = subvol->root_item.ctime;\n    r->root_item.otime = now;\n\n    if (readonly)\n        r->root_item.flags |= BTRFS_SUBVOL_READONLY;\n\n    r->treeholder.address = address;\n\n    // FIXME - do we need to copy over the send and receive fields too?\n\n    if (tp.item->key.obj_id != searchkey.obj_id || tp.item->key.obj_type != searchkey.obj_type) {\n        ERR(\"error - could not find ROOT_ITEM for subvol %I64x\\n\", r->id);\n        Status = STATUS_INTERNAL_ERROR;\n        goto end;\n    }\n\n    RtlCopyMemory(tp.item->data, &r->root_item, sizeof(ROOT_ITEM));\n\n    // update ROOT_ITEM of original subvol\n\n    subvol->root_item.last_snapshot_generation = Vcb->superblock.generation;\n\n    mark_subvol_dirty(Vcb, subvol);\n\n    // create fileref for entry in other subvolume\n\n    fr = create_fileref(Vcb);\n    if (!fr) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    Status = open_fcb(Vcb, r, r->root_item.objid, BTRFS_TYPE_DIRECTORY, utf8, false, fcb, &fr->fcb, PagedPool, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"open_fcb returned %08lx\\n\", Status);\n        free_fileref(fr);\n        goto end;\n    }\n\n    fr->parent = fileref;\n\n    Status = add_dir_child(fileref->fcb, r->id, true, utf8, name, BTRFS_TYPE_DIRECTORY, &dc);\n    if (!NT_SUCCESS(Status))\n        WARN(\"add_dir_child returned %08lx\\n\", Status);\n\n    fr->dc = dc;\n    dc->fileref = fr;\n\n    ExAcquireResourceExclusiveLite(&fileref->fcb->nonpaged->dir_children_lock, true);\n    InsertTailList(&fileref->children, &fr->list_entry);\n    ExReleaseResourceLite(&fileref->fcb->nonpaged->dir_children_lock);\n\n    increase_fileref_refcount(fileref);\n\n    fr->created = true;\n    mark_fileref_dirty(fr);\n\n    if (fr->fcb->type == BTRFS_TYPE_DIRECTORY)\n        fr->fcb->fileref = fr;\n\n    fr->fcb->subvol->parent = fileref->fcb->subvol->id;\n\n    free_fileref(fr);\n\n    // change fcb's INODE_ITEM\n\n    fcb->inode_item.transid = Vcb->superblock.generation;\n    fcb->inode_item.sequence++;\n    fcb->inode_item.st_size += utf8->Length * 2;\n\n    if (!ccb->user_set_change_time)\n        fcb->inode_item.st_ctime = now;\n\n    if (!ccb->user_set_write_time)\n        fcb->inode_item.st_mtime = now;\n\n    fcb->inode_item_changed = true;\n    mark_fcb_dirty(fcb);\n\n    fcb->subvol->root_item.ctime = now;\n    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;\n\n    send_notification_fileref(fr, FILE_NOTIFY_CHANGE_DIR_NAME, FILE_ACTION_ADDED, NULL);\n    send_notification_fileref(fr->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n\n    le = subvol->fcbs.Flink;\n    while (le != &subvol->fcbs) {\n        struct _fcb* fcb2 = CONTAINING_RECORD(le, struct _fcb, list_entry);\n        LIST_ENTRY* le2 = fcb2->extents.Flink;\n\n        while (le2 != &fcb2->extents) {\n            extent* ext = CONTAINING_RECORD(le2, extent, list_entry);\n\n            if (!ext->ignore)\n                ext->unique = false;\n\n            le2 = le2->Flink;\n        }\n\n        le = le->Flink;\n    }\n\n    Status = do_write(Vcb, Irp);\n\n    free_trees(Vcb);\n\n    if (!NT_SUCCESS(Status))\n        ERR(\"do_write returned %08lx\\n\", Status);\n\nend:\n    if (NT_SUCCESS(Status))\n        clear_rollback(&rollback);\n    else\n        do_rollback(Vcb, &rollback);\n\n    return Status;\n}\n\nstatic NTSTATUS create_snapshot(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG length, PIRP Irp) {\n    PFILE_OBJECT subvol_obj;\n    NTSTATUS Status;\n    btrfs_create_snapshot* bcs = data;\n    fcb* subvol_fcb;\n    HANDLE subvolh;\n    bool readonly, posix;\n    ANSI_STRING utf8;\n    UNICODE_STRING nameus;\n    ULONG len;\n    fcb* fcb;\n    ccb* ccb;\n    file_ref *fileref, *fr2;\n\n#if defined(_WIN64)\n    if (IoIs32bitProcess(Irp)) {\n        btrfs_create_snapshot32* bcs32 = data;\n\n        if (length < offsetof(btrfs_create_snapshot32, name))\n            return STATUS_INVALID_PARAMETER;\n\n        if (length < offsetof(btrfs_create_snapshot32, name) + bcs32->namelen)\n            return STATUS_INVALID_PARAMETER;\n\n        subvolh = Handle32ToHandle(bcs32->subvol);\n\n        nameus.Buffer = bcs32->name;\n        nameus.Length = nameus.MaximumLength = bcs32->namelen;\n\n        readonly = bcs32->readonly;\n        posix = bcs32->posix;\n    } else {\n#endif\n        if (length < offsetof(btrfs_create_snapshot, name))\n            return STATUS_INVALID_PARAMETER;\n\n        if (length < offsetof(btrfs_create_snapshot, name) + bcs->namelen)\n            return STATUS_INVALID_PARAMETER;\n\n        subvolh = bcs->subvol;\n\n        nameus.Buffer = bcs->name;\n        nameus.Length = nameus.MaximumLength = bcs->namelen;\n\n        readonly = bcs->readonly;\n        posix = bcs->posix;\n#if defined(_WIN64)\n    }\n#endif\n\n    if (!subvolh)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!FileObject || !FileObject->FsContext)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n    ccb = FileObject->FsContext2;\n\n    if (!fcb || !ccb || fcb->type != BTRFS_TYPE_DIRECTORY)\n        return STATUS_INVALID_PARAMETER;\n\n    fileref = ccb->fileref;\n\n    if (!fileref) {\n        ERR(\"fileref was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (!(ccb->access & FILE_ADD_SUBDIRECTORY)) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (Vcb->readonly)\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n    if (is_subvol_readonly(fcb->subvol, Irp))\n        return STATUS_ACCESS_DENIED;\n\n    Status = check_file_name_valid(&nameus, posix, false);\n    if (!NT_SUCCESS(Status))\n        return Status;\n\n    utf8.Buffer = NULL;\n\n    Status = utf16_to_utf8(NULL, 0, &len, nameus.Buffer, nameus.Length);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"utf16_to_utf8 failed with error %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (len == 0) {\n        ERR(\"utf16_to_utf8 returned a length of 0\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (len > 0xffff) {\n        ERR(\"len was too long\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    utf8.MaximumLength = utf8.Length = (USHORT)len;\n    utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.Length, ALLOC_TAG);\n\n    if (!utf8.Buffer) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    Status = utf16_to_utf8(utf8.Buffer, len, &len, nameus.Buffer, nameus.Length);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"utf16_to_utf8 failed with error %08lx\\n\", Status);\n        goto end2;\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    // no need for fcb_lock as we have tree_lock exclusively\n    Status = open_fileref(fcb->Vcb, &fr2, &nameus, fileref, false, NULL, NULL, PagedPool, ccb->case_sensitive || posix, Irp);\n\n    if (NT_SUCCESS(Status)) {\n        if (!fr2->deleted) {\n            WARN(\"file already exists\\n\");\n            free_fileref(fr2);\n            Status = STATUS_OBJECT_NAME_COLLISION;\n            goto end3;\n        } else\n            free_fileref(fr2);\n    } else if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_NOT_FOUND) {\n        ERR(\"open_fileref returned %08lx\\n\", Status);\n        goto end3;\n    }\n\n    Status = ObReferenceObjectByHandle(subvolh, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&subvol_obj, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ObReferenceObjectByHandle returned %08lx\\n\", Status);\n        goto end3;\n    }\n\n    if (subvol_obj->DeviceObject != FileObject->DeviceObject) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    subvol_fcb = subvol_obj->FsContext;\n    if (!subvol_fcb) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (subvol_fcb->inode != subvol_fcb->subvol->root_item.objid) {\n        WARN(\"handle inode was %I64x, expected %I64x\\n\", subvol_fcb->inode, subvol_fcb->subvol->root_item.objid);\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    ccb = subvol_obj->FsContext2;\n\n    if (!ccb) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (!(ccb->access & FILE_TRAVERSE)) {\n        WARN(\"insufficient privileges\\n\");\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    if (fcb == Vcb->dummy_fcb) {\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    // clear unique flag on extents of open files in subvol\n    if (!IsListEmpty(&subvol_fcb->subvol->fcbs)) {\n        LIST_ENTRY* le = subvol_fcb->subvol->fcbs.Flink;\n\n        while (le != &subvol_fcb->subvol->fcbs) {\n            struct _fcb* openfcb = CONTAINING_RECORD(le, struct _fcb, list_entry);\n            LIST_ENTRY* le2;\n\n            le2 = openfcb->extents.Flink;\n\n            while (le2 != &openfcb->extents) {\n                extent* ext = CONTAINING_RECORD(le2, extent, list_entry);\n\n                ext->unique = false;\n\n                le2 = le2->Flink;\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    Status = do_create_snapshot(Vcb, FileObject, subvol_fcb, &utf8, &nameus, readonly, Irp);\n\n    if (NT_SUCCESS(Status)) {\n        file_ref* fr;\n\n        Status = open_fileref(Vcb, &fr, &nameus, fileref, false, NULL, NULL, PagedPool, false, Irp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"open_fileref returned %08lx\\n\", Status);\n            Status = STATUS_SUCCESS;\n        } else {\n            send_notification_fileref(fr, FILE_NOTIFY_CHANGE_DIR_NAME, FILE_ACTION_ADDED, NULL);\n            free_fileref(fr);\n        }\n    }\n\nend:\n    ObDereferenceObject(subvol_obj);\n\nend3:\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\nend2:\n    ExFreePool(utf8.Buffer);\n\n    return Status;\n}\n\nstatic NTSTATUS create_subvol(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, PIRP Irp) {\n    btrfs_create_subvol* bcs;\n    fcb *fcb, *rootfcb = NULL;\n    ccb* ccb;\n    file_ref* fileref;\n    NTSTATUS Status;\n    uint64_t id;\n    root* r = NULL;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n    ULONG len;\n    uint16_t irsize;\n    UNICODE_STRING nameus;\n    ANSI_STRING utf8;\n    INODE_REF* ir;\n    KEY searchkey;\n    traverse_ptr tp;\n    SECURITY_SUBJECT_CONTEXT subjcont;\n    PSID owner;\n    BOOLEAN defaulted;\n    uint64_t* root_num;\n    file_ref *fr = NULL, *fr2;\n    dir_child* dc = NULL;\n\n    fcb = FileObject->FsContext;\n    if (!fcb) {\n        ERR(\"error - fcb was NULL\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    ccb = FileObject->FsContext2;\n    if (!ccb) {\n        ERR(\"error - ccb was NULL\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    fileref = ccb->fileref;\n\n    if (fcb->type != BTRFS_TYPE_DIRECTORY) {\n        ERR(\"parent FCB was not a directory\\n\");\n        return STATUS_NOT_A_DIRECTORY;\n    }\n\n    if (!fileref) {\n        ERR(\"fileref was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (fileref->deleted || fcb->deleted) {\n        ERR(\"parent has been deleted\\n\");\n        return STATUS_FILE_DELETED;\n    }\n\n    if (!(ccb->access & FILE_ADD_SUBDIRECTORY)) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (Vcb->readonly)\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n    if (is_subvol_readonly(fcb->subvol, Irp))\n        return STATUS_ACCESS_DENIED;\n\n    if (fcb == Vcb->dummy_fcb)\n        return STATUS_ACCESS_DENIED;\n\n    if (!data || datalen < sizeof(btrfs_create_subvol))\n        return STATUS_INVALID_PARAMETER;\n\n    bcs = (btrfs_create_subvol*)data;\n\n    if (offsetof(btrfs_create_subvol, name[0]) + bcs->namelen > datalen)\n        return STATUS_INVALID_PARAMETER;\n\n    nameus.Length = nameus.MaximumLength = bcs->namelen;\n    nameus.Buffer = bcs->name;\n\n    Status = check_file_name_valid(&nameus, bcs->posix, false);\n    if (!NT_SUCCESS(Status))\n        return Status;\n\n    utf8.Buffer = NULL;\n\n    Status = utf16_to_utf8(NULL, 0, &len, nameus.Buffer, nameus.Length);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"utf16_to_utf8 failed with error %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (len == 0) {\n        ERR(\"utf16_to_utf8 returned a length of 0\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (len > 0xffff) {\n        ERR(\"len was too long\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    utf8.MaximumLength = utf8.Length = (USHORT)len;\n    utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.Length, ALLOC_TAG);\n\n    if (!utf8.Buffer) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    Status = utf16_to_utf8(utf8.Buffer, len, &len, nameus.Buffer, nameus.Length);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"utf16_to_utf8 failed with error %08lx\\n\", Status);\n        goto end2;\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    // no need for fcb_lock as we have tree_lock exclusively\n    Status = open_fileref(fcb->Vcb, &fr2, &nameus, fileref, false, NULL, NULL, PagedPool, ccb->case_sensitive || bcs->posix, Irp);\n\n    if (NT_SUCCESS(Status)) {\n        if (!fr2->deleted) {\n            WARN(\"file already exists\\n\");\n            free_fileref(fr2);\n            Status = STATUS_OBJECT_NAME_COLLISION;\n            goto end;\n        } else\n            free_fileref(fr2);\n    } else if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_NOT_FOUND) {\n        ERR(\"open_fileref returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    id = InterlockedIncrement64(&Vcb->root_root->lastinode);\n    Status = create_root(Vcb, id, &r, false, 0, Irp);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"create_root returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    TRACE(\"created root %I64x\\n\", id);\n\n    if (!Vcb->uuid_root) {\n        root* uuid_root;\n\n        TRACE(\"uuid root doesn't exist, creating it\\n\");\n\n        Status = create_root(Vcb, BTRFS_ROOT_UUID, &uuid_root, false, 0, Irp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"create_root returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        Vcb->uuid_root = uuid_root;\n    }\n\n    root_num = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t), ALLOC_TAG);\n    if (!root_num) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    tp.tree = NULL;\n\n    do {\n        get_uuid(&r->root_item.uuid);\n\n        RtlCopyMemory(&searchkey.obj_id, &r->root_item.uuid, sizeof(uint64_t));\n        searchkey.obj_type = TYPE_SUBVOL_UUID;\n        RtlCopyMemory(&searchkey.offset, &r->root_item.uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t));\n\n        Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp);\n    } while (NT_SUCCESS(Status) && !keycmp(searchkey, tp.item->key));\n\n    *root_num = r->id;\n\n    Status = insert_tree_item(Vcb, Vcb->uuid_root, searchkey.obj_id, searchkey.obj_type, searchkey.offset, root_num, sizeof(uint64_t), NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(root_num);\n        goto end;\n    }\n\n    r->root_item.inode.generation = 1;\n    r->root_item.inode.st_size = 3;\n    r->root_item.inode.st_blocks = Vcb->superblock.node_size;\n    r->root_item.inode.st_nlink = 1;\n    r->root_item.inode.st_mode = __S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH; // 40755\n    r->root_item.inode.flags = 0x80000000; // FIXME - find out what these mean\n    r->root_item.inode.flags_ro = 0xffffffff; // FIXME - find out what these mean\n\n    if (bcs->readonly)\n        r->root_item.flags |= BTRFS_SUBVOL_READONLY;\n\n    r->root_item.objid = SUBVOL_ROOT_INODE;\n    r->root_item.bytes_used = Vcb->superblock.node_size;\n    r->root_item.ctransid = Vcb->superblock.generation;\n    r->root_item.otransid = Vcb->superblock.generation;\n    r->root_item.ctime = now;\n    r->root_item.otime = now;\n\n    // add .. inode to new subvol\n\n    rootfcb = create_fcb(Vcb, PagedPool);\n    if (!rootfcb) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    rootfcb->Vcb = Vcb;\n\n    rootfcb->subvol = r;\n    rootfcb->type = BTRFS_TYPE_DIRECTORY;\n    rootfcb->inode = SUBVOL_ROOT_INODE;\n    rootfcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&rootfcb->inode, sizeof(uint64_t)); // FIXME - we can hardcode this\n\n    rootfcb->inode_item.generation = Vcb->superblock.generation;\n    rootfcb->inode_item.transid = Vcb->superblock.generation;\n    rootfcb->inode_item.st_nlink = 1;\n    rootfcb->inode_item.st_mode = __S_IFDIR | inherit_mode(fileref->fcb, true);\n    rootfcb->inode_item.st_atime = rootfcb->inode_item.st_ctime = rootfcb->inode_item.st_mtime = rootfcb->inode_item.otime = now;\n    rootfcb->inode_item.st_gid = GID_NOBODY;\n\n    rootfcb->atts = get_file_attributes(Vcb, rootfcb->subvol, rootfcb->inode, rootfcb->type, false, true, Irp);\n\n    if (r->root_item.flags & BTRFS_SUBVOL_READONLY)\n        rootfcb->atts |= FILE_ATTRIBUTE_READONLY;\n\n    SeCaptureSubjectContext(&subjcont);\n\n    Status = SeAssignSecurity(fcb->sd, NULL, (void**)&rootfcb->sd, true, &subjcont, IoGetFileObjectGenericMapping(), PagedPool);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"SeAssignSecurity returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (!rootfcb->sd) {\n        ERR(\"SeAssignSecurity returned NULL security descriptor\\n\");\n        Status = STATUS_INTERNAL_ERROR;\n        goto end;\n    }\n\n    Status = RtlGetOwnerSecurityDescriptor(rootfcb->sd, &owner, &defaulted);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlGetOwnerSecurityDescriptor returned %08lx\\n\", Status);\n        rootfcb->inode_item.st_uid = UID_NOBODY;\n        rootfcb->sd_dirty = true;\n    } else {\n        rootfcb->inode_item.st_uid = sid_to_uid(owner);\n        rootfcb->sd_dirty = rootfcb->inode_item.st_uid == UID_NOBODY;\n    }\n\n    find_gid(rootfcb, fileref->fcb, &subjcont);\n\n    rootfcb->inode_item_changed = true;\n\n    acquire_fcb_lock_exclusive(Vcb);\n    add_fcb_to_subvol(rootfcb);\n    InsertTailList(&Vcb->all_fcbs, &rootfcb->list_entry_all);\n    r->fcbs_version++;\n    release_fcb_lock(Vcb);\n\n    rootfcb->Header.IsFastIoPossible = fast_io_possible(rootfcb);\n    rootfcb->Header.AllocationSize.QuadPart = 0;\n    rootfcb->Header.FileSize.QuadPart = 0;\n    rootfcb->Header.ValidDataLength.QuadPart = 0;\n\n    rootfcb->created = true;\n\n    if (fileref->fcb->inode_item.flags & BTRFS_INODE_COMPRESS)\n        rootfcb->inode_item.flags |= BTRFS_INODE_COMPRESS;\n\n    rootfcb->prop_compression = fileref->fcb->prop_compression;\n    rootfcb->prop_compression_changed = rootfcb->prop_compression != PropCompression_None;\n\n    r->lastinode = rootfcb->inode;\n\n    // add INODE_REF\n\n    irsize = (uint16_t)(offsetof(INODE_REF, name[0]) + sizeof(DOTDOT) - 1);\n    ir = ExAllocatePoolWithTag(PagedPool, irsize, ALLOC_TAG);\n    if (!ir) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    ir->index = 0;\n    ir->n = sizeof(DOTDOT) - 1;\n    RtlCopyMemory(ir->name, DOTDOT, ir->n);\n\n    Status = insert_tree_item(Vcb, r, r->root_item.objid, TYPE_INODE_REF, r->root_item.objid, ir, irsize, NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(ir);\n        goto end;\n    }\n\n    // create fileref for entry in other subvolume\n\n    fr = create_fileref(Vcb);\n    if (!fr) {\n        ERR(\"out of memory\\n\");\n\n        reap_fcb(rootfcb);\n\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    fr->fcb = rootfcb;\n\n    mark_fcb_dirty(rootfcb);\n\n    fr->parent = fileref;\n\n    Status = add_dir_child(fileref->fcb, r->id, true, &utf8, &nameus, BTRFS_TYPE_DIRECTORY, &dc);\n    if (!NT_SUCCESS(Status))\n        WARN(\"add_dir_child returned %08lx\\n\", Status);\n\n    fr->dc = dc;\n    dc->fileref = fr;\n\n    fr->fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);\n    if (!fr->fcb->hash_ptrs) {\n        ERR(\"out of memory\\n\");\n        free_fileref(fr);\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    RtlZeroMemory(fr->fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256);\n\n    fr->fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);\n    if (!fr->fcb->hash_ptrs_uc) {\n        ERR(\"out of memory\\n\");\n        free_fileref(fr);\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    RtlZeroMemory(fr->fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256);\n\n    ExAcquireResourceExclusiveLite(&fileref->fcb->nonpaged->dir_children_lock, true);\n    InsertTailList(&fileref->children, &fr->list_entry);\n    ExReleaseResourceLite(&fileref->fcb->nonpaged->dir_children_lock);\n\n    increase_fileref_refcount(fileref);\n\n    if (fr->fcb->type == BTRFS_TYPE_DIRECTORY)\n        fr->fcb->fileref = fr;\n\n    fr->created = true;\n    mark_fileref_dirty(fr);\n\n    // change fcb->subvol's ROOT_ITEM\n\n    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;\n    fcb->subvol->root_item.ctime = now;\n\n    // change fcb's INODE_ITEM\n\n    fcb->inode_item.transid = Vcb->superblock.generation;\n    fcb->inode_item.st_size += utf8.Length * 2;\n    fcb->inode_item.sequence++;\n\n    if (!ccb->user_set_change_time)\n        fcb->inode_item.st_ctime = now;\n\n    if (!ccb->user_set_write_time)\n        fcb->inode_item.st_mtime = now;\n\n    fcb->inode_item_changed = true;\n    mark_fcb_dirty(fcb);\n\n    fr->fcb->subvol->parent = fcb->subvol->id;\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (!NT_SUCCESS(Status)) {\n        if (fr) {\n            fr->deleted = true;\n            mark_fileref_dirty(fr);\n        } else if (rootfcb) {\n            rootfcb->deleted = true;\n            mark_fcb_dirty(rootfcb);\n        }\n\n        if (r) {\n            RemoveEntryList(&r->list_entry);\n            InsertTailList(&Vcb->drop_roots, &r->list_entry);\n        }\n    }\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    if (NT_SUCCESS(Status)) {\n        send_notification_fileref(fr, FILE_NOTIFY_CHANGE_DIR_NAME, FILE_ACTION_ADDED, NULL);\n        send_notification_fileref(fr->parent, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n    }\n\nend2:\n    if (fr)\n        free_fileref(fr);\n\n    return Status;\n}\n\nstatic NTSTATUS get_inode_info(PFILE_OBJECT FileObject, void* data, ULONG length) {\n    btrfs_inode_info* bii = data;\n    fcb* fcb;\n    ccb* ccb;\n    bool old_style;\n\n    if (length < offsetof(btrfs_inode_info, disk_size_zstd))\n        return STATUS_BUFFER_OVERFLOW;\n\n    if (!FileObject)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    ccb = FileObject->FsContext2;\n\n    if (!ccb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!(ccb->access & FILE_READ_ATTRIBUTES)) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (fcb->ads)\n        fcb = ccb->fileref->parent->fcb;\n\n    old_style = length < offsetof(btrfs_inode_info, sparse_size) + sizeof(((btrfs_inode_info*)NULL)->sparse_size);\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    bii->subvol = fcb->subvol->id;\n    bii->inode = fcb->inode;\n    bii->top = fcb->Vcb->root_fileref->fcb == fcb ? true : false;\n    bii->type = fcb->type;\n    bii->st_uid = fcb->inode_item.st_uid;\n    bii->st_gid = fcb->inode_item.st_gid;\n    bii->st_mode = fcb->inode_item.st_mode;\n\n    if (fcb->inode_item.st_rdev == 0)\n        bii->st_rdev = 0;\n    else\n        bii->st_rdev = makedev((fcb->inode_item.st_rdev & 0xFFFFFFFFFFF) >> 20, fcb->inode_item.st_rdev & 0xFFFFF);\n\n    bii->flags = fcb->inode_item.flags;\n\n    bii->inline_length = 0;\n    bii->disk_size_uncompressed = 0;\n    bii->disk_size_zlib = 0;\n    bii->disk_size_lzo = 0;\n\n    if (!old_style) {\n        bii->disk_size_zstd = 0;\n        bii->sparse_size = 0;\n    }\n\n    if (fcb->type != BTRFS_TYPE_DIRECTORY) {\n        uint64_t last_end = 0;\n        LIST_ENTRY* le;\n        bool extents_inline = false;\n\n        le = fcb->extents.Flink;\n        while (le != &fcb->extents) {\n            extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n            if (!ext->ignore) {\n                if (!old_style && ext->offset > last_end)\n                    bii->sparse_size += ext->offset - last_end;\n\n                if (ext->extent_data.type == EXTENT_TYPE_INLINE) {\n                    bii->inline_length += ext->datalen - (uint16_t)offsetof(EXTENT_DATA, data[0]);\n                    last_end = ext->offset + ext->extent_data.decoded_size;\n                    extents_inline = true;\n                } else {\n                    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n                    // FIXME - compressed extents with a hole in them are counted more than once\n                    if (ed2->size != 0) {\n                        switch (ext->extent_data.compression) {\n                            case BTRFS_COMPRESSION_NONE:\n                                bii->disk_size_uncompressed += ed2->num_bytes;\n                                break;\n\n                            case BTRFS_COMPRESSION_ZLIB:\n                                bii->disk_size_zlib += ed2->size;\n                                break;\n\n                            case BTRFS_COMPRESSION_LZO:\n                                bii->disk_size_lzo += ed2->size;\n                                break;\n\n                            case BTRFS_COMPRESSION_ZSTD:\n                                if (!old_style)\n                                    bii->disk_size_zstd += ed2->size;\n                                break;\n                        }\n                    }\n\n                    last_end = ext->offset + ed2->num_bytes;\n                }\n            }\n\n            le = le->Flink;\n        }\n\n        if (!extents_inline && !old_style && sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size) > last_end)\n            bii->sparse_size += sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size) - last_end;\n\n        if (length >= offsetof(btrfs_inode_info, num_extents) + sizeof(((btrfs_inode_info*)NULL)->num_extents)) {\n            EXTENT_DATA2* last_ed2 = NULL;\n\n            le = fcb->extents.Flink;\n\n            bii->num_extents = 0;\n\n            while (le != &fcb->extents) {\n                extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n                if (!ext->ignore && ext->extent_data.type != EXTENT_TYPE_INLINE) {\n                    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n                    if (ed2->size != 0) {\n                        if (!last_ed2 || ed2->offset != last_ed2->offset + last_ed2->num_bytes)\n                            bii->num_extents++;\n\n                        last_ed2 = ed2;\n                    } else\n                        last_ed2 = NULL;\n                }\n\n                le = le->Flink;\n            }\n        }\n    }\n\n    switch (fcb->prop_compression) {\n        case PropCompression_Zlib:\n            bii->compression_type = BTRFS_COMPRESSION_ZLIB;\n            break;\n\n        case PropCompression_LZO:\n            bii->compression_type = BTRFS_COMPRESSION_LZO;\n            break;\n\n        case PropCompression_ZSTD:\n            bii->compression_type = BTRFS_COMPRESSION_ZSTD;\n            break;\n\n        default:\n            bii->compression_type = BTRFS_COMPRESSION_ANY;\n            break;\n    }\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS set_inode_info(PFILE_OBJECT FileObject, void* data, ULONG length, PIRP Irp) {\n    btrfs_set_inode_info* bsii = data;\n    NTSTATUS Status;\n    fcb* fcb;\n    ccb* ccb;\n\n    if (length < sizeof(btrfs_set_inode_info))\n        return STATUS_INVALID_PARAMETER;\n\n    if (!FileObject)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    ccb = FileObject->FsContext2;\n\n    if (!ccb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (bsii->flags_changed && !(ccb->access & FILE_WRITE_ATTRIBUTES)) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if ((bsii->mode_changed || bsii->uid_changed || bsii->gid_changed) && !(ccb->access & WRITE_DAC)) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (bsii->compression_type_changed && bsii->compression_type > BTRFS_COMPRESSION_ZSTD)\n        return STATUS_INVALID_PARAMETER;\n\n    if (fcb->ads)\n        fcb = ccb->fileref->parent->fcb;\n\n    if (is_subvol_readonly(fcb->subvol, Irp)) {\n        WARN(\"trying to change inode on readonly subvolume\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    // nocow and compression are mutually exclusive\n    if (bsii->flags_changed && bsii->flags & BTRFS_INODE_NODATACOW && bsii->flags & BTRFS_INODE_COMPRESS)\n        return STATUS_INVALID_PARAMETER;\n\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (bsii->flags_changed) {\n        if (fcb->type != BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0 &&\n            (bsii->flags & BTRFS_INODE_NODATACOW) != (fcb->inode_item.flags & BTRFS_INODE_NODATACOW)) {\n            WARN(\"trying to change nocow flag on non-empty file\\n\");\n            Status = STATUS_INVALID_PARAMETER;\n            goto end;\n        }\n\n        fcb->inode_item.flags = bsii->flags;\n\n        if (fcb->inode_item.flags & BTRFS_INODE_NODATACOW)\n            fcb->inode_item.flags |= BTRFS_INODE_NODATASUM;\n        else\n            fcb->inode_item.flags &= ~(uint64_t)BTRFS_INODE_NODATASUM;\n    }\n\n    if (bsii->mode_changed) {\n        uint32_t allowed = S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IWOTH | S_IXOTH |\n                         S_ISGID | S_ISVTX;\n\n        if (ccb->access & WRITE_OWNER)\n            allowed |= S_ISUID;\n\n        fcb->inode_item.st_mode &= ~allowed;\n        fcb->inode_item.st_mode |= bsii->st_mode & allowed;\n    }\n\n    if (bsii->uid_changed && fcb->inode_item.st_uid != bsii->st_uid) {\n        fcb->inode_item.st_uid = bsii->st_uid;\n\n        fcb->sd_dirty = true;\n        fcb->sd_deleted = false;\n    }\n\n    if (bsii->gid_changed)\n        fcb->inode_item.st_gid = bsii->st_gid;\n\n    if (bsii->compression_type_changed) {\n        switch (bsii->compression_type) {\n            case BTRFS_COMPRESSION_ANY:\n                fcb->prop_compression = PropCompression_None;\n            break;\n\n            case BTRFS_COMPRESSION_ZLIB:\n                fcb->prop_compression = PropCompression_Zlib;\n            break;\n\n            case BTRFS_COMPRESSION_LZO:\n                fcb->prop_compression = PropCompression_LZO;\n            break;\n\n            case BTRFS_COMPRESSION_ZSTD:\n                fcb->prop_compression = PropCompression_ZSTD;\n            break;\n        }\n\n        fcb->prop_compression_changed = true;\n    }\n\n    if (bsii->flags_changed || bsii->mode_changed || bsii->uid_changed || bsii->gid_changed || bsii->compression_type_changed) {\n        fcb->inode_item_changed = true;\n        mark_fcb_dirty(fcb);\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    return Status;\n}\n\nstatic NTSTATUS get_devices(device_extension* Vcb, void* data, ULONG length) {\n    btrfs_device* dev = NULL;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    le = Vcb->devices.Flink;\n    while (le != &Vcb->devices) {\n        device* dev2 = CONTAINING_RECORD(le, device, list_entry);\n        ULONG structlen;\n\n        if (length < sizeof(btrfs_device) - sizeof(WCHAR)) {\n            Status = STATUS_BUFFER_OVERFLOW;\n            goto end;\n        }\n\n        if (!dev)\n            dev = data;\n        else {\n            dev->next_entry = sizeof(btrfs_device) - sizeof(WCHAR) + dev->namelen;\n            dev = (btrfs_device*)((uint8_t*)dev + dev->next_entry);\n        }\n\n        structlen = length - offsetof(btrfs_device, namelen);\n\n        if (dev2->devobj) {\n            Status = dev_ioctl(dev2->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &dev->namelen, structlen, true, NULL);\n            if (!NT_SUCCESS(Status))\n                goto end;\n\n            dev->missing = false;\n        } else {\n            dev->namelen = 0;\n            dev->missing = true;\n        }\n\n        dev->next_entry = 0;\n        dev->dev_id = dev2->devitem.dev_id;\n        dev->readonly = (Vcb->readonly || dev2->readonly) ? true : false;\n        dev->device_number = dev2->disk_num;\n        dev->partition_number = dev2->part_num;\n        dev->size = dev2->devitem.num_bytes;\n\n        if (dev2->devobj) {\n            GET_LENGTH_INFORMATION gli;\n\n            Status = dev_ioctl(dev2->devobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli), true, NULL);\n            if (!NT_SUCCESS(Status))\n                goto end;\n\n            dev->max_size = gli.Length.QuadPart;\n        } else\n            dev->max_size = dev->size;\n\n        RtlCopyMemory(dev->stats, dev2->stats, sizeof(uint64_t) * 5);\n\n        length -= sizeof(btrfs_device) - sizeof(WCHAR) + dev->namelen;\n\n        le = le->Flink;\n    }\n\nend:\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS get_usage(device_extension* Vcb, void* data, ULONG length, PIRP Irp) {\n    btrfs_usage* usage = (btrfs_usage*)data;\n    btrfs_usage* lastbue = NULL;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n\n    if (length < sizeof(btrfs_usage))\n        return STATUS_BUFFER_OVERFLOW;\n\n    if (!Vcb->chunk_usage_found) {\n        ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n        if (!Vcb->chunk_usage_found)\n            Status = find_chunk_usage(Vcb, Irp);\n        else\n            Status = STATUS_SUCCESS;\n\n        ExReleaseResourceLite(&Vcb->tree_lock);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_chunk_usage returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    length -= offsetof(btrfs_usage, devices);\n\n    ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        bool addnew = false;\n\n        chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (!lastbue) // first entry\n            addnew = true;\n        else {\n            btrfs_usage* bue = usage;\n\n            addnew = true;\n\n            while (true) {\n                if (bue->type == c->chunk_item->type) {\n                    addnew = false;\n                    break;\n                }\n\n                if (bue->next_entry == 0)\n                    break;\n                else\n                    bue = (btrfs_usage*)((uint8_t*)bue + bue->next_entry);\n            }\n        }\n\n        if (addnew) {\n            btrfs_usage* bue;\n            LIST_ENTRY* le2;\n            uint64_t factor;\n\n            if (!lastbue) {\n                bue = usage;\n            } else {\n                if (length < offsetof(btrfs_usage, devices)) {\n                    Status = STATUS_BUFFER_OVERFLOW;\n                    goto end;\n                }\n\n                length -= offsetof(btrfs_usage, devices);\n\n                lastbue->next_entry = offsetof(btrfs_usage, devices) + (ULONG)(lastbue->num_devices * sizeof(btrfs_usage_device));\n\n                bue = (btrfs_usage*)((uint8_t*)lastbue + lastbue->next_entry);\n            }\n\n            bue->next_entry = 0;\n            bue->type = c->chunk_item->type;\n            bue->size = 0;\n            bue->used = 0;\n            bue->num_devices = 0;\n\n            if (c->chunk_item->type & BLOCK_FLAG_RAID0)\n                factor = c->chunk_item->num_stripes;\n            else if (c->chunk_item->type & BLOCK_FLAG_RAID10)\n                factor = c->chunk_item->num_stripes / c->chunk_item->sub_stripes;\n            else if (c->chunk_item->type & BLOCK_FLAG_RAID5)\n                factor = c->chunk_item->num_stripes - 1;\n            else if (c->chunk_item->type & BLOCK_FLAG_RAID6)\n                factor = c->chunk_item->num_stripes - 2;\n            else\n                factor = 1;\n\n            le2 = le;\n            while (le2 != &Vcb->chunks) {\n                chunk* c2 = CONTAINING_RECORD(le2, chunk, list_entry);\n\n                if (c2->chunk_item->type == c->chunk_item->type) {\n                    uint16_t i;\n                    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c2->chunk_item[1];\n                    uint64_t stripesize;\n\n                    bue->size += c2->chunk_item->size;\n                    bue->used += c2->used;\n\n                    stripesize = c2->chunk_item->size / factor;\n\n                    for (i = 0; i < c2->chunk_item->num_stripes; i++) {\n                        uint64_t j;\n                        bool found = false;\n\n                        for (j = 0; j < bue->num_devices; j++) {\n                            if (bue->devices[j].dev_id == cis[i].dev_id) {\n                                bue->devices[j].alloc += stripesize;\n                                found = true;\n                                break;\n                            }\n                        }\n\n                        if (!found) {\n                            if (length < sizeof(btrfs_usage_device)) {\n                                Status = STATUS_BUFFER_OVERFLOW;\n                                goto end;\n                            }\n\n                            length -= sizeof(btrfs_usage_device);\n\n                            bue->devices[bue->num_devices].dev_id = cis[i].dev_id;\n                            bue->devices[bue->num_devices].alloc = stripesize;\n                            bue->num_devices++;\n                        }\n                    }\n                }\n\n                le2 = le2->Flink;\n            }\n\n            lastbue = bue;\n        }\n\n        le = le->Flink;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&Vcb->chunk_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS is_volume_mounted(device_extension* Vcb, PIRP Irp) {\n    NTSTATUS Status;\n    ULONG cc;\n    IO_STATUS_BLOCK iosb;\n    bool verify = false;\n    LIST_ENTRY* le;\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    le = Vcb->devices.Flink;\n    while (le != &Vcb->devices) {\n        device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n        if (dev->devobj && dev->removable) {\n            Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, &cc, sizeof(ULONG), false, &iosb);\n\n            if (iosb.Information != sizeof(ULONG))\n                cc = 0;\n\n            if (Status == STATUS_VERIFY_REQUIRED || (NT_SUCCESS(Status) && cc != dev->change_count)) {\n                dev->devobj->Flags |= DO_VERIFY_VOLUME;\n                verify = true;\n            }\n\n            if (NT_SUCCESS(Status) && iosb.Information == sizeof(ULONG))\n                dev->change_count = cc;\n\n            if (!NT_SUCCESS(Status) || verify) {\n                IoSetHardErrorOrVerifyDevice(Irp, dev->devobj);\n                ExReleaseResourceLite(&Vcb->tree_lock);\n\n                return verify ? STATUS_VERIFY_REQUIRED : Status;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fs_get_statistics(void* buffer, DWORD buflen, ULONG_PTR* retlen) {\n    FILESYSTEM_STATISTICS* fss;\n\n    WARN(\"STUB: FSCTL_FILESYSTEM_GET_STATISTICS\\n\");\n\n    // This is hideously wrong, but at least it stops SMB from breaking\n\n    if (buflen < sizeof(FILESYSTEM_STATISTICS))\n        return STATUS_BUFFER_TOO_SMALL;\n\n    fss = buffer;\n    RtlZeroMemory(fss, sizeof(FILESYSTEM_STATISTICS));\n\n    fss->Version = 1;\n    fss->FileSystemType = FILESYSTEM_STATISTICS_TYPE_NTFS;\n    fss->SizeOfCompleteStructure = sizeof(FILESYSTEM_STATISTICS);\n\n    *retlen = sizeof(FILESYSTEM_STATISTICS);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS set_sparse(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG length, PIRP Irp) {\n    FILE_SET_SPARSE_BUFFER* fssb = data;\n    NTSTATUS Status;\n    bool set;\n    fcb* fcb;\n    ccb* ccb = FileObject->FsContext2;\n    file_ref* fileref = ccb ? ccb->fileref : NULL;\n\n    if (data && length < sizeof(FILE_SET_SPARSE_BUFFER))\n        return STATUS_INVALID_PARAMETER;\n\n    if (!FileObject) {\n        ERR(\"FileObject was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb) {\n        ERR(\"FCB was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (!ccb) {\n        ERR(\"CCB was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (!fileref) {\n        ERR(\"no fileref\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (fcb->ads) {\n        fileref = fileref->parent;\n        fcb = fileref->fcb;\n    }\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (fcb->type != BTRFS_TYPE_FILE) {\n        WARN(\"FileObject did not point to a file\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (fssb)\n        set = fssb->SetSparse;\n    else\n        set = true;\n\n    if (set) {\n        fcb->atts |= FILE_ATTRIBUTE_SPARSE_FILE;\n        fcb->atts_changed = true;\n    } else {\n        ULONG defda;\n\n        fcb->atts &= ~FILE_ATTRIBUTE_SPARSE_FILE;\n        fcb->atts_changed = true;\n\n        defda = get_file_attributes(Vcb, fcb->subvol, fcb->inode, fcb->type,\n                                    fileref && fileref->dc && fileref->dc->name.Length >= sizeof(WCHAR) && fileref->dc->name.Buffer[0] == '.', true, Irp);\n\n        fcb->atts_deleted = defda == fcb->atts;\n    }\n\n    mark_fcb_dirty(fcb);\n    queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(fcb->Header.Resource);\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS zero_data(device_extension* Vcb, fcb* fcb, uint64_t start, uint64_t length, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    bool make_inline, compress;\n    uint64_t start_data, end_data;\n    ULONG buf_head;\n    uint8_t* data;\n\n    make_inline = fcb->inode_item.st_size <= Vcb->options.max_inline || fcb_is_inline(fcb);\n\n    if (!make_inline)\n        compress = write_fcb_compressed(fcb);\n\n    if (make_inline) {\n        start_data = 0;\n        end_data = fcb->inode_item.st_size;\n        buf_head = (ULONG)offsetof(EXTENT_DATA, data[0]);\n    } else if (compress) {\n        start_data = start & ~(uint64_t)(COMPRESSED_EXTENT_SIZE - 1);\n        end_data = min(sector_align(start + length, COMPRESSED_EXTENT_SIZE),\n                       sector_align(fcb->inode_item.st_size, Vcb->superblock.sector_size));\n        buf_head = 0;\n    } else {\n        start_data = start & ~(uint64_t)(Vcb->superblock.sector_size - 1);\n        end_data = sector_align(start + length, Vcb->superblock.sector_size);\n        buf_head = 0;\n    }\n\n    data = ExAllocatePoolWithTag(PagedPool, (ULONG)(buf_head + end_data - start_data), ALLOC_TAG);\n    if (!data) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(data + buf_head, (ULONG)(end_data - start_data));\n\n    if (start > start_data || start + length < end_data) {\n        Status = read_file(fcb, data + buf_head, start_data, end_data - start_data, NULL, Irp);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"read_file returned %08lx\\n\", Status);\n            ExFreePool(data);\n            return Status;\n        }\n    }\n\n    RtlZeroMemory(data + buf_head + start - start_data, (ULONG)length);\n\n    if (make_inline) {\n        uint16_t edsize;\n        EXTENT_DATA* ed = (EXTENT_DATA*)data;\n\n        Status = excise_extents(Vcb, fcb, 0, sector_align(end_data, Vcb->superblock.sector_size), Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"excise_extents returned %08lx\\n\", Status);\n            ExFreePool(data);\n            return Status;\n        }\n\n        edsize = (uint16_t)(offsetof(EXTENT_DATA, data[0]) + end_data);\n\n        ed->generation = Vcb->superblock.generation;\n        ed->decoded_size = end_data;\n        ed->compression = BTRFS_COMPRESSION_NONE;\n        ed->encryption = BTRFS_ENCRYPTION_NONE;\n        ed->encoding = BTRFS_ENCODING_NONE;\n        ed->type = EXTENT_TYPE_INLINE;\n\n        Status = add_extent_to_fcb(fcb, 0, ed, edsize, false, NULL, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_extent_to_fcb returned %08lx\\n\", Status);\n            ExFreePool(data);\n            return Status;\n        }\n\n        ExFreePool(data);\n\n        fcb->inode_item.st_blocks += end_data;\n    } else if (compress) {\n        Status = write_compressed(fcb, start_data, end_data, data, Irp, rollback);\n\n        ExFreePool(data);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"write_compressed returned %08lx\\n\", Status);\n            return Status;\n        }\n    } else {\n        Status = do_write_file(fcb, start_data, end_data, data, Irp, false, 0, rollback);\n\n        ExFreePool(data);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"do_write_file returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS set_zero_data(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG length, PIRP Irp) {\n    FILE_ZERO_DATA_INFORMATION* fzdi = data;\n    NTSTATUS Status;\n    fcb* fcb;\n    ccb* ccb;\n    file_ref* fileref;\n    LIST_ENTRY rollback, *le;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n    uint64_t start, end;\n    extent* ext;\n    IO_STATUS_BLOCK iosb;\n\n    if (!data || length < sizeof(FILE_ZERO_DATA_INFORMATION))\n        return STATUS_INVALID_PARAMETER;\n\n    if (!FileObject) {\n        ERR(\"FileObject was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (fzdi->BeyondFinalZero.QuadPart <= fzdi->FileOffset.QuadPart) {\n        WARN(\"BeyondFinalZero was less than or equal to FileOffset (%I64x <= %I64x)\\n\", fzdi->BeyondFinalZero.QuadPart, fzdi->FileOffset.QuadPart);\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb) {\n        ERR(\"FCB was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    ccb = FileObject->FsContext2;\n\n    if (!ccb) {\n        ERR(\"ccb was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_DATA)) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    fileref = ccb->fileref;\n\n    if (!fileref) {\n        ERR(\"fileref was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    InitializeListHead(&rollback);\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    CcFlushCache(FileObject->SectionObjectPointer, NULL, 0, &iosb);\n\n    if (fcb->type != BTRFS_TYPE_FILE) {\n        WARN(\"FileObject did not point to a file\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (fcb->ads) {\n        ERR(\"FileObject is stream\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if ((uint64_t)fzdi->FileOffset.QuadPart >= fcb->inode_item.st_size) {\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    ext = NULL;\n    le = fcb->extents.Flink;\n    while (le != &fcb->extents) {\n        extent* ext2 = CONTAINING_RECORD(le, extent, list_entry);\n\n        if (!ext2->ignore) {\n            ext = ext2;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!ext) {\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    if (ext->extent_data.type == EXTENT_TYPE_INLINE) {\n        Status = zero_data(Vcb, fcb, fzdi->FileOffset.QuadPart, fzdi->BeyondFinalZero.QuadPart - fzdi->FileOffset.QuadPart, Irp, &rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"zero_data returned %08lx\\n\", Status);\n            goto end;\n        }\n    } else {\n        start = sector_align(fzdi->FileOffset.QuadPart, Vcb->superblock.sector_size);\n\n        if ((uint64_t)fzdi->BeyondFinalZero.QuadPart > fcb->inode_item.st_size)\n            end = sector_align(fcb->inode_item.st_size, Vcb->superblock.sector_size);\n        else\n            end = (fzdi->BeyondFinalZero.QuadPart >> Vcb->sector_shift) << Vcb->sector_shift;\n\n        if (end <= start) {\n            Status = zero_data(Vcb, fcb, fzdi->FileOffset.QuadPart, fzdi->BeyondFinalZero.QuadPart - fzdi->FileOffset.QuadPart, Irp, &rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"zero_data returned %08lx\\n\", Status);\n                goto end;\n            }\n        } else {\n            if (start > (uint64_t)fzdi->FileOffset.QuadPart) {\n                Status = zero_data(Vcb, fcb, fzdi->FileOffset.QuadPart, start - fzdi->FileOffset.QuadPart, Irp, &rollback);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"zero_data returned %08lx\\n\", Status);\n                    goto end;\n                }\n            }\n\n            if (end < (uint64_t)fzdi->BeyondFinalZero.QuadPart) {\n                Status = zero_data(Vcb, fcb, end, fzdi->BeyondFinalZero.QuadPart - end, Irp, &rollback);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"zero_data returned %08lx\\n\", Status);\n                    goto end;\n                }\n            }\n\n            if (end > start) {\n                Status = excise_extents(Vcb, fcb, start, end, Irp, &rollback);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"excise_extents returned %08lx\\n\", Status);\n                    goto end;\n                }\n            }\n        }\n    }\n\n    CcPurgeCacheSection(FileObject->SectionObjectPointer, &fzdi->FileOffset, (ULONG)(fzdi->BeyondFinalZero.QuadPart - fzdi->FileOffset.QuadPart), false);\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    fcb->inode_item.transid = Vcb->superblock.generation;\n    fcb->inode_item.sequence++;\n\n    if (!ccb->user_set_change_time)\n        fcb->inode_item.st_ctime = now;\n\n    if (!ccb->user_set_write_time)\n        fcb->inode_item.st_mtime = now;\n\n    fcb->extents_changed = true;\n    fcb->inode_item_changed = true;\n    mark_fcb_dirty(fcb);\n\n    queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n\n    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;\n    fcb->subvol->root_item.ctime = now;\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (!NT_SUCCESS(Status))\n        do_rollback(Vcb, &rollback);\n    else\n        clear_rollback(&rollback);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS query_ranges(PFILE_OBJECT FileObject, FILE_ALLOCATED_RANGE_BUFFER* inbuf, ULONG inbuflen, void* outbuf, ULONG outbuflen, ULONG_PTR* retlen) {\n    NTSTATUS Status;\n    fcb* fcb;\n    LIST_ENTRY* le;\n    FILE_ALLOCATED_RANGE_BUFFER* ranges = outbuf;\n    ULONG i = 0;\n    uint64_t last_start, last_end;\n\n    TRACE(\"FSCTL_QUERY_ALLOCATED_RANGES\\n\");\n\n    if (!FileObject) {\n        ERR(\"FileObject was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (!inbuf || inbuflen < sizeof(FILE_ALLOCATED_RANGE_BUFFER) || !outbuf)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb) {\n        ERR(\"FCB was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    // If file is not marked as sparse, claim the whole thing as an allocated range\n\n    if (!(fcb->atts & FILE_ATTRIBUTE_SPARSE_FILE)) {\n        if (fcb->inode_item.st_size == 0)\n            Status = STATUS_SUCCESS;\n        else if (outbuflen < sizeof(FILE_ALLOCATED_RANGE_BUFFER))\n            Status = STATUS_BUFFER_TOO_SMALL;\n        else {\n            ranges[i].FileOffset.QuadPart = 0;\n            ranges[i].Length.QuadPart = fcb->inode_item.st_size;\n            i++;\n            Status = STATUS_SUCCESS;\n        }\n\n        goto end;\n\n    }\n\n    le = fcb->extents.Flink;\n\n    last_start = 0;\n    last_end = 0;\n\n    while (le != &fcb->extents) {\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if (!ext->ignore) {\n            EXTENT_DATA2* ed2 = (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC) ? (EXTENT_DATA2*)ext->extent_data.data : NULL;\n            uint64_t len = ed2 ? ed2->num_bytes : ext->extent_data.decoded_size;\n\n            if (ext->offset > last_end) { // first extent after a hole\n                if (last_end > last_start) {\n                    if ((i + 1) * sizeof(FILE_ALLOCATED_RANGE_BUFFER) <= outbuflen) {\n                        ranges[i].FileOffset.QuadPart = last_start;\n                        ranges[i].Length.QuadPart = min(fcb->inode_item.st_size, last_end) - last_start;\n                        i++;\n                    } else {\n                        Status = STATUS_BUFFER_TOO_SMALL;\n                        goto end;\n                    }\n                }\n\n                last_start = ext->offset;\n            }\n\n            last_end = ext->offset + len;\n        }\n\n        le = le->Flink;\n    }\n\n    if (last_end > last_start) {\n        if ((i + 1) * sizeof(FILE_ALLOCATED_RANGE_BUFFER) <= outbuflen) {\n            ranges[i].FileOffset.QuadPart = last_start;\n            ranges[i].Length.QuadPart = min(fcb->inode_item.st_size, last_end) - last_start;\n            i++;\n        } else {\n            Status = STATUS_BUFFER_TOO_SMALL;\n            goto end;\n        }\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    *retlen = i * sizeof(FILE_ALLOCATED_RANGE_BUFFER);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    return Status;\n}\n\nstatic NTSTATUS get_object_id(PFILE_OBJECT FileObject, FILE_OBJECTID_BUFFER* buf, ULONG buflen, ULONG_PTR* retlen) {\n    fcb* fcb;\n\n    TRACE(\"(%p, %p, %lx, %p)\\n\", FileObject, buf, buflen, retlen);\n\n    if (!FileObject) {\n        ERR(\"FileObject was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (!buf || buflen < sizeof(FILE_OBJECTID_BUFFER))\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb) {\n        ERR(\"FCB was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    RtlCopyMemory(&buf->ObjectId[0], &fcb->inode, sizeof(uint64_t));\n    RtlCopyMemory(&buf->ObjectId[sizeof(uint64_t)], &fcb->subvol->id, sizeof(uint64_t));\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    RtlZeroMemory(&buf->ExtendedInfo, sizeof(buf->ExtendedInfo));\n\n    *retlen = sizeof(FILE_OBJECTID_BUFFER);\n\n    return STATUS_SUCCESS;\n}\n\nstatic void flush_fcb_caches(device_extension* Vcb) {\n    LIST_ENTRY* le;\n\n    le = Vcb->all_fcbs.Flink;\n    while (le != &Vcb->all_fcbs) {\n        struct _fcb* fcb = CONTAINING_RECORD(le, struct _fcb, list_entry_all);\n        IO_STATUS_BLOCK iosb;\n\n        if (fcb->type != BTRFS_TYPE_DIRECTORY && !fcb->deleted)\n            CcFlushCache(&fcb->nonpaged->segment_object, NULL, 0, &iosb);\n\n        le = le->Flink;\n    }\n}\n\nstatic NTSTATUS lock_volume(device_extension* Vcb, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    NTSTATUS Status;\n    KIRQL irql;\n    bool lock_paused_balance = false;\n\n    TRACE(\"FSCTL_LOCK_VOLUME\\n\");\n\n    if (Vcb->scrub.thread) {\n        WARN(\"cannot lock while scrub running\\n\");\n        return STATUS_DEVICE_NOT_READY;\n    }\n\n    if (Vcb->balance.thread) {\n        WARN(\"cannot lock while balance running\\n\");\n        return STATUS_DEVICE_NOT_READY;\n    }\n\n    TRACE(\"locking volume\\n\");\n\n    FsRtlNotifyVolumeEvent(IrpSp->FileObject, FSRTL_VOLUME_LOCK);\n\n    if (Vcb->locked)\n        return STATUS_SUCCESS;\n\n    ExAcquireResourceExclusiveLite(&Vcb->fileref_lock, true);\n\n    if (Vcb->root_fileref && Vcb->root_fileref->fcb && (Vcb->root_fileref->open_count > 0 || has_open_children(Vcb->root_fileref))) {\n        Status = STATUS_ACCESS_DENIED;\n        ExReleaseResourceLite(&Vcb->fileref_lock);\n        goto end;\n    }\n\n    ExReleaseResourceLite(&Vcb->fileref_lock);\n\n    if (Vcb->balance.thread && KeReadStateEvent(&Vcb->balance.event)) {\n        ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n        KeClearEvent(&Vcb->balance.event);\n        ExReleaseResourceLite(&Vcb->tree_lock);\n\n        lock_paused_balance = true;\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    flush_fcb_caches(Vcb);\n\n    if (Vcb->need_write && !Vcb->readonly)\n        Status = do_write(Vcb, Irp);\n    else\n        Status = STATUS_SUCCESS;\n\n    free_trees(Vcb);\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"do_write returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    IoAcquireVpbSpinLock(&irql);\n\n    if (!(Vcb->Vpb->Flags & VPB_LOCKED)) {\n        Vcb->Vpb->Flags |= VPB_LOCKED;\n        Vcb->locked = true;\n        Vcb->locked_fileobj = IrpSp->FileObject;\n        Vcb->lock_paused_balance = lock_paused_balance;\n    } else {\n        Status = STATUS_ACCESS_DENIED;\n        IoReleaseVpbSpinLock(irql);\n\n        if (lock_paused_balance)\n            KeSetEvent(&Vcb->balance.event, 0, false);\n\n        goto end;\n    }\n\n    IoReleaseVpbSpinLock(irql);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (!NT_SUCCESS(Status))\n        FsRtlNotifyVolumeEvent(IrpSp->FileObject, FSRTL_VOLUME_LOCK_FAILED);\n\n    return Status;\n}\n\nvoid do_unlock_volume(device_extension* Vcb) {\n    KIRQL irql;\n\n    IoAcquireVpbSpinLock(&irql);\n\n    Vcb->locked = false;\n    Vcb->Vpb->Flags &= ~VPB_LOCKED;\n    Vcb->locked_fileobj = NULL;\n\n    IoReleaseVpbSpinLock(irql);\n\n    if (Vcb->lock_paused_balance)\n        KeSetEvent(&Vcb->balance.event, 0, false);\n}\n\nstatic NTSTATUS unlock_volume(device_extension* Vcb, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n    TRACE(\"FSCTL_UNLOCK_VOLUME\\n\");\n\n    if (!Vcb->locked || IrpSp->FileObject != Vcb->locked_fileobj)\n        return STATUS_NOT_LOCKED;\n\n    TRACE(\"unlocking volume\\n\");\n\n    do_unlock_volume(Vcb);\n\n    FsRtlNotifyVolumeEvent(IrpSp->FileObject, FSRTL_VOLUME_UNLOCK);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS invalidate_volumes(PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    LUID TcbPrivilege = {SE_TCB_PRIVILEGE, 0};\n    NTSTATUS Status;\n    HANDLE h;\n    PFILE_OBJECT fileobj;\n    PDEVICE_OBJECT devobj;\n    LIST_ENTRY* le;\n\n    TRACE(\"FSCTL_INVALIDATE_VOLUMES\\n\");\n\n    if (!SeSinglePrivilegeCheck(TcbPrivilege, Irp->RequestorMode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n#if defined(_WIN64)\n    if (IoIs32bitProcess(Irp)) {\n        if (IrpSp->Parameters.FileSystemControl.InputBufferLength != sizeof(uint32_t))\n            return STATUS_INVALID_PARAMETER;\n\n        h = (HANDLE)LongToHandle((*(uint32_t*)Irp->AssociatedIrp.SystemBuffer));\n    } else {\n#endif\n        if (IrpSp->Parameters.FileSystemControl.InputBufferLength != sizeof(HANDLE))\n            return STATUS_INVALID_PARAMETER;\n\n        h = *(PHANDLE)Irp->AssociatedIrp.SystemBuffer;\n#if defined(_WIN64)\n    }\n#endif\n\n    Status = ObReferenceObjectByHandle(h, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ObReferenceObjectByHandle returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    devobj = fileobj->DeviceObject;\n\n    ExAcquireResourceSharedLite(&global_loading_lock, true);\n\n    le = VcbList.Flink;\n\n    while (le != &VcbList) {\n        device_extension* Vcb = CONTAINING_RECORD(le, device_extension, list_entry);\n\n        if (Vcb->Vpb && Vcb->Vpb->RealDevice == devobj) {\n            if (Vcb->Vpb == devobj->Vpb) {\n                KIRQL irql;\n                PVPB newvpb;\n                bool free_newvpb = false;\n\n                newvpb = ExAllocatePoolWithTag(NonPagedPool, sizeof(VPB), ALLOC_TAG);\n                if (!newvpb) {\n                    ERR(\"out of memory\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto end;\n                }\n\n                RtlZeroMemory(newvpb, sizeof(VPB));\n\n                ObReferenceObject(devobj);\n\n                ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n                Vcb->removing = true;\n\n                ExReleaseResourceLite(&Vcb->tree_lock);\n\n                CcWaitForCurrentLazyWriterActivity();\n\n                ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n                flush_fcb_caches(Vcb);\n\n                if (Vcb->need_write && !Vcb->readonly)\n                    Status = do_write(Vcb, Irp);\n                else\n                    Status = STATUS_SUCCESS;\n\n                free_trees(Vcb);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"do_write returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(&Vcb->tree_lock);\n                    ExFreePool(newvpb);\n                    ObDereferenceObject(devobj);\n                    goto end;\n                }\n\n                flush_fcb_caches(Vcb);\n\n                ExReleaseResourceLite(&Vcb->tree_lock);\n\n                IoAcquireVpbSpinLock(&irql);\n\n                if (devobj->Vpb->Flags & VPB_MOUNTED) {\n                    newvpb->Type = IO_TYPE_VPB;\n                    newvpb->Size = sizeof(VPB);\n                    newvpb->RealDevice = devobj;\n                    newvpb->Flags = devobj->Vpb->Flags & VPB_REMOVE_PENDING;\n\n                    devobj->Vpb = newvpb;\n                } else\n                    free_newvpb = true;\n\n                IoReleaseVpbSpinLock(irql);\n\n                if (free_newvpb)\n                    ExFreePool(newvpb);\n\n                if (Vcb->open_files == 0)\n                    uninit(Vcb);\n\n                ObDereferenceObject(devobj);\n            }\n\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&global_loading_lock);\n\n    ObDereferenceObject(fileobj);\n\n    return Status;\n}\n\nstatic NTSTATUS is_volume_dirty(device_extension* Vcb, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    ULONG* volstate;\n\n    if (Irp->AssociatedIrp.SystemBuffer) {\n        volstate = Irp->AssociatedIrp.SystemBuffer;\n    } else if (Irp->MdlAddress != NULL) {\n        volstate = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, LowPagePriority);\n\n        if (!volstate)\n            return STATUS_INSUFFICIENT_RESOURCES;\n    } else\n        return STATUS_INVALID_USER_BUFFER;\n\n    if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < sizeof(ULONG))\n        return STATUS_INVALID_PARAMETER;\n\n    *volstate = 0;\n\n    if (IrpSp->FileObject->FsContext != Vcb->volume_fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    Irp->IoStatus.Information = sizeof(ULONG);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS get_compression(PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    USHORT* compression;\n\n    TRACE(\"FSCTL_GET_COMPRESSION\\n\");\n\n    if (Irp->AssociatedIrp.SystemBuffer) {\n        compression = Irp->AssociatedIrp.SystemBuffer;\n    } else if (Irp->MdlAddress != NULL) {\n        compression = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, LowPagePriority);\n\n        if (!compression)\n            return STATUS_INSUFFICIENT_RESOURCES;\n    } else\n        return STATUS_INVALID_USER_BUFFER;\n\n    if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < sizeof(USHORT))\n        return STATUS_INVALID_PARAMETER;\n\n    *compression = COMPRESSION_FORMAT_NONE;\n\n    Irp->IoStatus.Information = sizeof(USHORT);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS set_compression(PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    USHORT* compression;\n\n    TRACE(\"FSCTL_SET_COMPRESSION\\n\");\n\n    if (IrpSp->Parameters.FileSystemControl.InputBufferLength < sizeof(USHORT))\n        return STATUS_INVALID_PARAMETER;\n\n    compression = Irp->AssociatedIrp.SystemBuffer;\n\n    if (*compression != COMPRESSION_FORMAT_NONE)\n        return STATUS_INVALID_PARAMETER;\n\n    return STATUS_SUCCESS;\n}\n\nstatic void update_volumes(device_extension* Vcb) {\n    LIST_ENTRY* le;\n    volume_device_extension* vde = Vcb->vde;\n    pdo_device_extension* pdode = vde->pdode;\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    ExAcquireResourceExclusiveLite(&pdode->child_lock, true);\n\n    le = pdode->children.Flink;\n    while (le != &pdode->children) {\n        volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n        vc->generation = Vcb->superblock.generation - 1;\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n}\n\nNTSTATUS dismount_volume(device_extension* Vcb, bool shutdown, PIRP Irp) {\n    NTSTATUS Status;\n    bool open_files;\n\n    TRACE(\"FSCTL_DISMOUNT_VOLUME\\n\");\n\n    if (!(Vcb->Vpb->Flags & VPB_MOUNTED))\n        return STATUS_SUCCESS;\n\n    if (!shutdown) {\n        if (Vcb->disallow_dismount || Vcb->page_file_count != 0) {\n            WARN(\"attempting to dismount boot volume or one containing a pagefile\\n\");\n            return STATUS_ACCESS_DENIED;\n        }\n\n        Status = FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_DISMOUNT);\n        if (!NT_SUCCESS(Status)) {\n            WARN(\"FsRtlNotifyVolumeEvent returned %08lx\\n\", Status);\n        }\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    if (!Vcb->locked) {\n        flush_fcb_caches(Vcb);\n\n        if (Vcb->need_write && !Vcb->readonly) {\n            Status = do_write(Vcb, Irp);\n\n            if (!NT_SUCCESS(Status))\n                ERR(\"do_write returned %08lx\\n\", Status);\n        }\n    }\n\n    free_trees(Vcb);\n\n    Vcb->removing = true;\n\n    open_files = Vcb->open_files > 0;\n\n    if (Vcb->vde) {\n        update_volumes(Vcb);\n        Vcb->vde->mounted_device = NULL;\n    }\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    if (!open_files)\n        uninit(Vcb);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS is_device_part_of_mounted_btrfs_raid(PDEVICE_OBJECT devobj, PFILE_OBJECT fileobj) {\n    NTSTATUS Status;\n    ULONG to_read;\n    superblock* sb;\n    BTRFS_UUID fsuuid, devuuid;\n    LIST_ENTRY* le;\n\n    to_read = devobj->SectorSize == 0 ? sizeof(superblock) : (ULONG)sector_align(sizeof(superblock), devobj->SectorSize);\n\n    sb = ExAllocatePoolWithTag(PagedPool, to_read, ALLOC_TAG);\n    if (!sb) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    Status = sync_read_phys(devobj, fileobj, superblock_addrs[0], to_read, (uint8_t*)sb, true);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"sync_read_phys returned %08lx\\n\", Status);\n        ExFreePool(sb);\n        return Status;\n    }\n\n    if (sb->magic != BTRFS_MAGIC) {\n        TRACE(\"device is not Btrfs\\n\");\n        ExFreePool(sb);\n        return STATUS_SUCCESS;\n    }\n\n    if (!check_superblock_checksum(sb)) {\n        TRACE(\"device has Btrfs magic, but invalid superblock checksum\\n\");\n        ExFreePool(sb);\n        return STATUS_SUCCESS;\n    }\n\n    fsuuid = sb->uuid;\n    devuuid = sb->dev_item.device_uuid;\n\n    ExFreePool(sb);\n\n    ExAcquireResourceSharedLite(&global_loading_lock, true);\n\n    le = VcbList.Flink;\n\n    while (le != &VcbList) {\n        device_extension* Vcb = CONTAINING_RECORD(le, device_extension, list_entry);\n\n        if (RtlCompareMemory(&Vcb->superblock.uuid, &fsuuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n            LIST_ENTRY* le2;\n\n            ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n            if (Vcb->superblock.num_devices > 1) {\n                le2 = Vcb->devices.Flink;\n                while (le2 != &Vcb->devices) {\n                    device* dev = CONTAINING_RECORD(le2, device, list_entry);\n\n                    if (RtlCompareMemory(&dev->devitem.device_uuid, &devuuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n                        ExReleaseResourceLite(&Vcb->tree_lock);\n                        ExReleaseResourceLite(&global_loading_lock);\n                        return STATUS_DEVICE_NOT_READY;\n                    }\n\n                    le2 = le2->Flink;\n                }\n            }\n\n            ExReleaseResourceLite(&Vcb->tree_lock);\n            ExReleaseResourceLite(&global_loading_lock);\n            return STATUS_SUCCESS;\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&global_loading_lock);\n\n    return STATUS_SUCCESS;\n}\n\nvoid trim_whole_device(device* dev) {\n    DEVICE_MANAGE_DATA_SET_ATTRIBUTES dmdsa;\n    NTSTATUS Status;\n\n    // FIXME - avoid \"bootloader area\"??\n\n    dmdsa.Size = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES);\n    dmdsa.Action = DeviceDsmAction_Trim;\n    dmdsa.Flags = DEVICE_DSM_FLAG_ENTIRE_DATA_SET_RANGE | DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED;\n    dmdsa.ParameterBlockOffset = 0;\n    dmdsa.ParameterBlockLength = 0;\n    dmdsa.DataSetRangesOffset = 0;\n    dmdsa.DataSetRangesLength = 0;\n\n    Status = dev_ioctl(dev->devobj, IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES, &dmdsa, sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), NULL, 0, true, NULL);\n    if (!NT_SUCCESS(Status))\n        WARN(\"IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES returned %08lx\\n\", Status);\n}\n\nstatic NTSTATUS add_device(device_extension* Vcb, PIRP Irp, KPROCESSOR_MODE processor_mode) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    NTSTATUS Status;\n    PFILE_OBJECT fileobj, mountmgrfo;\n    PDEVICE_OBJECT DeviceObject;\n    HANDLE h;\n    LIST_ENTRY* le;\n    device* dev;\n    DEV_ITEM* di;\n    uint64_t dev_id, size;\n    uint8_t* mb;\n    uint64_t* stats;\n    UNICODE_STRING mmdevpath, pnp_name, pnp_name2;\n    volume_child* vc;\n    PDEVICE_OBJECT mountmgr;\n    KEY searchkey;\n    traverse_ptr tp;\n    STORAGE_DEVICE_NUMBER sdn;\n    volume_device_extension* vde;\n    pdo_device_extension* pdode;\n    const GUID* pnp_guid;\n    GET_LENGTH_INFORMATION gli;\n\n    pnp_name.Buffer = NULL;\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (!Vcb->vde) {\n        WARN(\"not allowing second device to be added to non-PNP device\\n\");\n        return STATUS_NOT_SUPPORTED;\n    }\n\n    if (Vcb->readonly) // FIXME - handle adding R/W device to seeding device\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n#if defined(_WIN64)\n    if (IoIs32bitProcess(Irp)) {\n        if (IrpSp->Parameters.FileSystemControl.InputBufferLength != sizeof(uint32_t))\n            return STATUS_INVALID_PARAMETER;\n\n        h = (HANDLE)LongToHandle((*(uint32_t*)Irp->AssociatedIrp.SystemBuffer));\n    } else {\n#endif\n        if (IrpSp->Parameters.FileSystemControl.InputBufferLength != sizeof(HANDLE))\n            return STATUS_INVALID_PARAMETER;\n\n        h = *(PHANDLE)Irp->AssociatedIrp.SystemBuffer;\n#if defined(_WIN64)\n    }\n#endif\n\n    Status = ObReferenceObjectByHandle(h, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ObReferenceObjectByHandle returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    DeviceObject = fileobj->DeviceObject;\n\n    Status = get_device_pnp_name(DeviceObject, &pnp_name, &pnp_guid);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"get_device_pnp_name returned %08lx\\n\", Status);\n        ObDereferenceObject(fileobj);\n        return Status;\n    }\n\n    // If this is a disk, we have been handed the PDO, so need to go up to find something we can use\n    if (RtlCompareMemory(pnp_guid, &GUID_DEVINTERFACE_DISK, sizeof(GUID)) == sizeof(GUID) && DeviceObject->AttachedDevice)\n        DeviceObject = DeviceObject->AttachedDevice;\n\n    Status = dev_ioctl(DeviceObject, IOCTL_DISK_IS_WRITABLE, NULL, 0, NULL, 0, true, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IOCTL_DISK_IS_WRITABLE returned %08lx\\n\", Status);\n        ObDereferenceObject(fileobj);\n        return Status;\n    }\n\n    Status = is_device_part_of_mounted_btrfs_raid(DeviceObject, fileobj);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"is_device_part_of_mounted_btrfs_raid returned %08lx\\n\", Status);\n        ObDereferenceObject(fileobj);\n        return Status;\n    }\n\n    // if disk, check it has no partitions\n    if (RtlCompareMemory(pnp_guid, &GUID_DEVINTERFACE_DISK, sizeof(GUID)) == sizeof(GUID)) {\n        ULONG dlisize;\n        DRIVE_LAYOUT_INFORMATION_EX* dli = NULL;\n\n        dlisize = 0;\n\n        do {\n            dlisize += 1024;\n\n            if (dli)\n                ExFreePool(dli);\n\n            dli = ExAllocatePoolWithTag(PagedPool, dlisize, ALLOC_TAG);\n            if (!dli) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end2;\n            }\n\n            Status = dev_ioctl(DeviceObject, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0, dli, dlisize, true, NULL);\n        } while (Status == STATUS_BUFFER_TOO_SMALL);\n\n        if (NT_SUCCESS(Status) && dli->PartitionCount > 0) {\n            ExFreePool(dli);\n            ERR(\"not adding disk which has partitions\\n\");\n            Status = STATUS_DEVICE_NOT_READY;\n            goto end2;\n        }\n\n        ExFreePool(dli);\n    }\n\n    Status = dev_ioctl(DeviceObject, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0,\n                       &sdn, sizeof(STORAGE_DEVICE_NUMBER), true, NULL);\n    if (NT_SUCCESS(Status)) {\n        if (sdn.DeviceType != FILE_DEVICE_DISK) { // FIXME - accept floppies and CDs?\n            WARN(\"device was not disk\\n\");\n            ObDereferenceObject(fileobj);\n            return STATUS_INVALID_PARAMETER;\n        }\n    } else {\n        sdn.DeviceNumber = 0xffffffff;\n        sdn.PartitionNumber = 0xffffffff;\n    }\n\n    Status = dev_ioctl(DeviceObject, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0,\n                        &gli, sizeof(gli), true, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error reading length information: %08lx\\n\", Status);\n        ObDereferenceObject(fileobj);\n        return Status;\n    }\n\n    size = gli.Length.QuadPart;\n\n    if (size < 0x100000) {\n        ERR(\"device was not large enough to hold FS (%I64x bytes, need at least 1 MB)\\n\", size);\n        ObDereferenceObject(fileobj);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    volume_removal(&pnp_name);\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    if (Vcb->need_write)\n        Status = do_write(Vcb, Irp);\n    else\n        Status = STATUS_SUCCESS;\n\n    free_trees(Vcb);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"do_write returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    dev = ExAllocatePoolWithTag(NonPagedPool, sizeof(device), ALLOC_TAG);\n    if (!dev) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    RtlZeroMemory(dev, sizeof(device));\n\n    dev->devobj = DeviceObject;\n    dev->fileobj = fileobj;\n    dev->seeding = false;\n    init_device(Vcb, dev, true);\n\n    InitializeListHead(&dev->space);\n\n    if (size > 0x100000) { // add disk hole - the first MB is marked as used\n        Status = add_space_entry(&dev->space, NULL, 0x100000, size - 0x100000);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_space_entry returned %08lx\\n\", Status);\n            goto end;\n        }\n    }\n\n    dev_id = 0;\n\n    le = Vcb->devices.Flink;\n    while (le != &Vcb->devices) {\n        device* dev2 = CONTAINING_RECORD(le, device, list_entry);\n\n        if (dev2->devitem.dev_id > dev_id)\n            dev_id = dev2->devitem.dev_id;\n\n        le = le->Flink;\n    }\n\n    dev_id++;\n\n    dev->devitem.dev_id = dev_id;\n    dev->devitem.num_bytes = size;\n    dev->devitem.bytes_used = 0;\n    dev->devitem.optimal_io_align = Vcb->superblock.sector_size;\n    dev->devitem.optimal_io_width = Vcb->superblock.sector_size;\n    dev->devitem.minimal_io_size = Vcb->superblock.sector_size;\n    dev->devitem.type = 0;\n    dev->devitem.generation = 0;\n    dev->devitem.start_offset = 0;\n    dev->devitem.dev_group = 0;\n    dev->devitem.seek_speed = 0;\n    dev->devitem.bandwidth = 0;\n    get_uuid(&dev->devitem.device_uuid);\n    dev->devitem.fs_uuid = Vcb->superblock.uuid;\n\n    di = ExAllocatePoolWithTag(PagedPool, sizeof(DEV_ITEM), ALLOC_TAG);\n    if (!di) {\n        ERR(\"out of memory\\n\");\n        goto end;\n    }\n\n    RtlCopyMemory(di, &dev->devitem, sizeof(DEV_ITEM));\n\n    Status = insert_tree_item(Vcb, Vcb->chunk_root, 1, TYPE_DEV_ITEM, di->dev_id, di, sizeof(DEV_ITEM), NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(di);\n        goto end;\n    }\n\n    // add stats entry to dev tree\n    stats = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t) * 5, ALLOC_TAG);\n    if (!stats) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    RtlZeroMemory(stats, sizeof(uint64_t) * 5);\n\n    searchkey.obj_id = 0;\n    searchkey.obj_type = TYPE_DEV_STATS;\n    searchkey.offset = di->dev_id;\n\n    Status = find_item(Vcb, Vcb->dev_root, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        ExFreePool(stats);\n        goto end;\n    }\n\n    if (!keycmp(tp.item->key, searchkey)) {\n        Status = delete_tree_item(Vcb, &tp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"delete_tree_item returned %08lx\\n\", Status);\n            ExFreePool(stats);\n            goto end;\n        }\n    }\n\n    Status = insert_tree_item(Vcb, Vcb->dev_root, 0, TYPE_DEV_STATS, di->dev_id, stats, sizeof(uint64_t) * 5, NULL, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"insert_tree_item returned %08lx\\n\", Status);\n        ExFreePool(stats);\n        goto end;\n    }\n\n    if (dev->trim && !dev->readonly && !Vcb->options.no_trim)\n        trim_whole_device(dev);\n\n    // We clear the first megabyte of the device, so Windows doesn't identify it as another FS\n    mb = ExAllocatePoolWithTag(PagedPool, 0x100000, ALLOC_TAG);\n    if (!mb) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    RtlZeroMemory(mb, 0x100000);\n\n    Status = write_data_phys(DeviceObject, fileobj, 0, mb, 0x100000);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"write_data_phys returned %08lx\\n\", Status);\n        ExFreePool(mb);\n        goto end;\n    }\n\n    ExFreePool(mb);\n\n    vde = Vcb->vde;\n    pdode = vde->pdode;\n\n    vc = ExAllocatePoolWithTag(NonPagedPool, sizeof(volume_child), ALLOC_TAG);\n    if (!vc) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    vc->uuid = dev->devitem.device_uuid;\n    vc->devid = dev_id;\n    vc->generation = Vcb->superblock.generation;\n    vc->devobj = DeviceObject;\n    vc->fileobj = fileobj;\n    vc->notification_entry = NULL;\n    vc->boot_volume = false;\n\n    Status = IoRegisterPlugPlayNotification(EventCategoryTargetDeviceChange, 0, fileobj,\n                                            drvobj, pnp_removal, vde->pdode, &vc->notification_entry);\n    if (!NT_SUCCESS(Status))\n        WARN(\"IoRegisterPlugPlayNotification returned %08lx\\n\", Status);\n\n    pnp_name2 = pnp_name;\n\n    if (pnp_name.Length > 4 * sizeof(WCHAR) && pnp_name.Buffer[0] == '\\\\' && (pnp_name.Buffer[1] == '\\\\' || pnp_name.Buffer[1] == '?') &&\n        pnp_name.Buffer[2] == '?' && pnp_name.Buffer[3] == '\\\\') {\n        pnp_name2.Buffer = &pnp_name2.Buffer[3];\n        pnp_name2.Length -= 3 * sizeof(WCHAR);\n        pnp_name2.MaximumLength -= 3 * sizeof(WCHAR);\n    }\n\n    vc->pnp_name.Length = vc->pnp_name.MaximumLength = pnp_name2.Length;\n\n    if (pnp_name2.Length == 0)\n        vc->pnp_name.Buffer = NULL;\n    else {\n        vc->pnp_name.Buffer = ExAllocatePoolWithTag(PagedPool, pnp_name2.Length, ALLOC_TAG);\n        if (!vc->pnp_name.Buffer) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        RtlCopyMemory(vc->pnp_name.Buffer, pnp_name2.Buffer, pnp_name2.Length);\n    }\n\n    vc->size = size;\n    vc->seeding = false;\n    vc->disk_num = sdn.DeviceNumber;\n    vc->part_num = sdn.PartitionNumber;\n    vc->had_drive_letter = false;\n\n    ExAcquireResourceExclusiveLite(&pdode->child_lock, true);\n    InsertTailList(&pdode->children, &vc->list_entry);\n    pdode->num_children++;\n    pdode->children_loaded++;\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);\n    Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &mountmgrfo, &mountmgr);\n    if (!NT_SUCCESS(Status))\n        ERR(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n    else {\n        Status = remove_drive_letter(mountmgr, &pnp_name);\n        if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND)\n            WARN(\"remove_drive_letter returned %08lx\\n\", Status);\n\n        vc->had_drive_letter = NT_SUCCESS(Status);\n\n        ObDereferenceObject(mountmgrfo);\n    }\n\n    Vcb->superblock.num_devices++;\n    Vcb->superblock.total_bytes += size;\n    Vcb->devices_loaded++;\n    InsertTailList(&Vcb->devices, &dev->list_entry);\n\n    // FIXME - send notification that volume size has increased\n\n    ObReferenceObject(DeviceObject); // for Vcb\n\n    Status = do_write(Vcb, Irp);\n    if (!NT_SUCCESS(Status))\n        ERR(\"do_write returned %08lx\\n\", Status);\n\n    ObReferenceObject(fileobj);\n\nend:\n    free_trees(Vcb);\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\nend2:\n    ObDereferenceObject(fileobj);\n\n    if (pnp_name.Buffer)\n        ExFreePool(pnp_name.Buffer);\n\n    if (NT_SUCCESS(Status))\n        FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_CHANGE_SIZE);\n\n    return Status;\n}\n\nstatic NTSTATUS allow_extended_dasd_io(device_extension* Vcb, PFILE_OBJECT FileObject) {\n    fcb* fcb;\n    ccb* ccb;\n\n    TRACE(\"FSCTL_ALLOW_EXTENDED_DASD_IO\\n\");\n\n    if (!FileObject)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n    ccb = FileObject->FsContext2;\n\n    if (!fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (fcb != Vcb->volume_fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!ccb)\n        return STATUS_INVALID_PARAMETER;\n\n    ccb->allow_extended_dasd_io = true;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS query_uuid(device_extension* Vcb, void* data, ULONG length) {\n    if (length < sizeof(BTRFS_UUID))\n        return STATUS_BUFFER_OVERFLOW;\n\n    RtlCopyMemory(data, &Vcb->superblock.uuid, sizeof(BTRFS_UUID));\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS reset_stats(device_extension* Vcb, void* data, ULONG length, KPROCESSOR_MODE processor_mode) {\n    uint64_t devid;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n\n    if (length < sizeof(uint64_t))\n        return STATUS_INVALID_PARAMETER;\n\n    if (Vcb->readonly)\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    devid = *((uint64_t*)data);\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    le = Vcb->devices.Flink;\n\n    while (le != &Vcb->devices) {\n        device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n        if (dev->devitem.dev_id == devid) {\n            RtlZeroMemory(dev->stats, sizeof(uint64_t) * 5);\n            dev->stats_changed = true;\n            Vcb->stats_changed = true;\n            Vcb->need_write = true;\n            Status = STATUS_SUCCESS;\n            goto end;\n        }\n\n        le = le->Flink;\n    }\n\n    WARN(\"device %I64x not found\\n\", devid);\n    Status = STATUS_INVALID_PARAMETER;\n\nend:\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS get_integrity_information(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen) {\n    FSCTL_GET_INTEGRITY_INFORMATION_BUFFER* fgiib = (FSCTL_GET_INTEGRITY_INFORMATION_BUFFER*)data;\n\n    TRACE(\"FSCTL_GET_INTEGRITY_INFORMATION\\n\");\n\n    // STUB\n\n    if (!FileObject)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!data || datalen < sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER))\n        return STATUS_INVALID_PARAMETER;\n\n    fgiib->ChecksumAlgorithm = 0;\n    fgiib->Reserved = 0;\n    fgiib->Flags = 0;\n    fgiib->ChecksumChunkSizeInBytes = Vcb->superblock.sector_size;\n    fgiib->ClusterSizeInBytes = Vcb->superblock.sector_size;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS set_integrity_information(PFILE_OBJECT FileObject, void* data, ULONG datalen) {\n    TRACE(\"FSCTL_SET_INTEGRITY_INFORMATION\\n\");\n\n    // STUB\n\n    if (!FileObject)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!data || datalen < sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER))\n        return STATUS_INVALID_PARAMETER;\n\n    return STATUS_SUCCESS;\n}\n\nbool fcb_is_inline(fcb* fcb) {\n    LIST_ENTRY* le;\n\n    le = fcb->extents.Flink;\n    while (le != &fcb->extents) {\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if (!ext->ignore)\n            return ext->extent_data.type == EXTENT_TYPE_INLINE;\n\n        le = le->Flink;\n    }\n\n    return false;\n}\n\nstatic NTSTATUS duplicate_extents(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, PIRP Irp) {\n    DUPLICATE_EXTENTS_DATA* ded = (DUPLICATE_EXTENTS_DATA*)data;\n    fcb *fcb = FileObject ? FileObject->FsContext : NULL, *sourcefcb;\n    ccb *ccb = FileObject ? FileObject->FsContext2 : NULL, *sourceccb;\n    NTSTATUS Status;\n    PFILE_OBJECT sourcefo;\n    uint64_t sourcelen, nbytes = 0;\n    LIST_ENTRY rollback, *le, newexts;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n    bool make_inline;\n\n    if (!ded || datalen < sizeof(DUPLICATE_EXTENTS_DATA))\n        return STATUS_BUFFER_TOO_SMALL;\n\n    if (Vcb->readonly)\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n    if (ded->ByteCount.QuadPart == 0)\n        return STATUS_SUCCESS;\n\n    if (!fcb || !ccb || fcb == Vcb->volume_fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (is_subvol_readonly(fcb->subvol, Irp))\n        return STATUS_ACCESS_DENIED;\n\n    if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_DATA)) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (!fcb->ads && fcb->type != BTRFS_TYPE_FILE && fcb->type != BTRFS_TYPE_SYMLINK)\n        return STATUS_INVALID_PARAMETER;\n\n    Status = ObReferenceObjectByHandle(ded->FileHandle, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&sourcefo, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ObReferenceObjectByHandle returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (sourcefo->DeviceObject != FileObject->DeviceObject) {\n        WARN(\"source and destination are on different volumes\\n\");\n        ObDereferenceObject(sourcefo);\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    sourcefcb = sourcefo->FsContext;\n    sourceccb = sourcefo->FsContext2;\n\n    if (!sourcefcb || !sourceccb || sourcefcb == Vcb->volume_fcb) {\n        ObDereferenceObject(sourcefo);\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (!sourcefcb->ads && !fcb->ads) {\n        if ((ded->SourceFileOffset.QuadPart & (Vcb->superblock.sector_size - 1)) || (ded->TargetFileOffset.QuadPart & (Vcb->superblock.sector_size - 1))) {\n            ObDereferenceObject(sourcefo);\n            return STATUS_INVALID_PARAMETER;\n        }\n\n        if (ded->ByteCount.QuadPart & (Vcb->superblock.sector_size - 1)) {\n            ObDereferenceObject(sourcefo);\n            return STATUS_INVALID_PARAMETER;\n        }\n    }\n\n    if (Irp->RequestorMode == UserMode && (!(sourceccb->access & FILE_READ_DATA) || !(sourceccb->access & FILE_READ_ATTRIBUTES))) {\n        WARN(\"insufficient privileges\\n\");\n        ObDereferenceObject(sourcefo);\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (!sourcefcb->ads && sourcefcb->type != BTRFS_TYPE_FILE && sourcefcb->type != BTRFS_TYPE_SYMLINK) {\n        ObDereferenceObject(sourcefo);\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    sourcelen = sourcefcb->ads ? sourcefcb->adsdata.Length : sourcefcb->inode_item.st_size;\n\n    if (sector_align(sourcelen, Vcb->superblock.sector_size) < (uint64_t)ded->SourceFileOffset.QuadPart + (uint64_t)ded->ByteCount.QuadPart) {\n        ObDereferenceObject(sourcefo);\n        return STATUS_NOT_SUPPORTED;\n    }\n\n    if (fcb == sourcefcb &&\n        ((ded->SourceFileOffset.QuadPart >= ded->TargetFileOffset.QuadPart && ded->SourceFileOffset.QuadPart < ded->TargetFileOffset.QuadPart + ded->ByteCount.QuadPart) ||\n        (ded->TargetFileOffset.QuadPart >= ded->SourceFileOffset.QuadPart && ded->TargetFileOffset.QuadPart < ded->SourceFileOffset.QuadPart + ded->ByteCount.QuadPart))) {\n        WARN(\"source and destination are the same, and the ranges overlap\\n\");\n        ObDereferenceObject(sourcefo);\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    // fail if nocsum flag set on one file but not the other\n    if (!fcb->ads && !sourcefcb->ads && (fcb->inode_item.flags & BTRFS_INODE_NODATASUM) != (sourcefcb->inode_item.flags & BTRFS_INODE_NODATASUM)) {\n        ObDereferenceObject(sourcefo);\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    InitializeListHead(&rollback);\n    InitializeListHead(&newexts);\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (fcb != sourcefcb)\n        ExAcquireResourceSharedLite(sourcefcb->Header.Resource, true);\n\n    if (!FsRtlFastCheckLockForWrite(&fcb->lock, &ded->TargetFileOffset, &ded->ByteCount, 0, FileObject, PsGetCurrentProcess())) {\n        Status = STATUS_FILE_LOCK_CONFLICT;\n        goto end;\n    }\n\n    if (!FsRtlFastCheckLockForRead(&sourcefcb->lock, &ded->SourceFileOffset, &ded->ByteCount, 0, FileObject, PsGetCurrentProcess())) {\n        Status = STATUS_FILE_LOCK_CONFLICT;\n        goto end;\n    }\n\n    make_inline = fcb->ads ? false : (fcb->inode_item.st_size <= Vcb->options.max_inline || fcb_is_inline(fcb));\n\n    if (fcb->ads || sourcefcb->ads || make_inline || fcb_is_inline(sourcefcb)) {\n        uint8_t* data2;\n        ULONG bytes_read, dataoff, datalen2;\n\n        if (make_inline) {\n            dataoff = (ULONG)ded->TargetFileOffset.QuadPart;\n            datalen2 = (ULONG)fcb->inode_item.st_size;\n        } else if (fcb->ads) {\n            dataoff = 0;\n            datalen2 = (ULONG)ded->ByteCount.QuadPart;\n        } else {\n            dataoff = ded->TargetFileOffset.QuadPart & (Vcb->superblock.sector_size - 1);\n            datalen2 = (ULONG)sector_align(ded->ByteCount.QuadPart + dataoff, Vcb->superblock.sector_size);\n        }\n\n        data2 = ExAllocatePoolWithTag(PagedPool, datalen2, ALLOC_TAG);\n        if (!data2) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        if (dataoff > 0) {\n            if (make_inline)\n                Status = read_file(fcb, data2, 0, datalen2, NULL, Irp);\n            else\n                Status = read_file(fcb, data2, ded->TargetFileOffset.QuadPart - dataoff, dataoff, NULL, Irp);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"read_file returned %08lx\\n\", Status);\n                ExFreePool(data2);\n                goto end;\n            }\n        }\n\n        if (sourcefcb->ads) {\n            Status = read_stream(sourcefcb, data2 + dataoff, ded->SourceFileOffset.QuadPart, (ULONG)ded->ByteCount.QuadPart, &bytes_read);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"read_stream returned %08lx\\n\", Status);\n                ExFreePool(data2);\n                goto end;\n            }\n        } else {\n            Status = read_file(sourcefcb, data2 + dataoff, ded->SourceFileOffset.QuadPart, ded->ByteCount.QuadPart, &bytes_read, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"read_file returned %08lx\\n\", Status);\n                ExFreePool(data2);\n                goto end;\n            }\n        }\n\n        if (dataoff + bytes_read < datalen2)\n            RtlZeroMemory(data2 + dataoff + bytes_read, datalen2 - bytes_read);\n\n        if (fcb->ads)\n            RtlCopyMemory(&fcb->adsdata.Buffer[ded->TargetFileOffset.QuadPart], data2, (USHORT)min(ded->ByteCount.QuadPart, fcb->adsdata.Length - ded->TargetFileOffset.QuadPart));\n        else if (make_inline) {\n            uint16_t edsize;\n            EXTENT_DATA* ed;\n\n            Status = excise_extents(Vcb, fcb, 0, sector_align(fcb->inode_item.st_size, Vcb->superblock.sector_size), Irp, &rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"excise_extents returned %08lx\\n\", Status);\n                ExFreePool(data2);\n                goto end;\n            }\n\n            edsize = (uint16_t)(offsetof(EXTENT_DATA, data[0]) + datalen2);\n\n            ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG);\n            if (!ed) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(data2);\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            ed->generation = Vcb->superblock.generation;\n            ed->decoded_size = fcb->inode_item.st_size;\n            ed->compression = BTRFS_COMPRESSION_NONE;\n            ed->encryption = BTRFS_ENCRYPTION_NONE;\n            ed->encoding = BTRFS_ENCODING_NONE;\n            ed->type = EXTENT_TYPE_INLINE;\n\n            RtlCopyMemory(ed->data, data2, datalen2);\n\n            Status = add_extent_to_fcb(fcb, 0, ed, edsize, false, NULL, &rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"add_extent_to_fcb returned %08lx\\n\", Status);\n                ExFreePool(data2);\n                goto end;\n            }\n\n            fcb->inode_item.st_blocks += datalen2;\n        } else {\n            uint64_t start = ded->TargetFileOffset.QuadPart - (ded->TargetFileOffset.QuadPart & (Vcb->superblock.sector_size - 1));\n\n            Status = do_write_file(fcb, start, start + datalen2, data2, Irp, false, 0, &rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"do_write_file returned %08lx\\n\", Status);\n                ExFreePool(data2);\n                goto end;\n            }\n        }\n\n        ExFreePool(data2);\n    } else {\n        LIST_ENTRY* lastextle;\n\n        le = sourcefcb->extents.Flink;\n        while (le != &sourcefcb->extents) {\n            extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n            if (!ext->ignore) {\n                if (ext->offset >= (uint64_t)ded->SourceFileOffset.QuadPart + (uint64_t)ded->ByteCount.QuadPart)\n                    break;\n\n                if (ext->extent_data.type != EXTENT_TYPE_INLINE) {\n                    ULONG extlen = offsetof(extent, extent_data) + sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2);\n                    extent* ext2;\n                    EXTENT_DATA2 *ed2s, *ed2d;\n                    chunk* c;\n\n                    ed2s = (EXTENT_DATA2*)ext->extent_data.data;\n\n                    if (ext->offset + ed2s->num_bytes <= (uint64_t)ded->SourceFileOffset.QuadPart) {\n                        le = le->Flink;\n                        continue;\n                    }\n\n                    ext2 = ExAllocatePoolWithTag(PagedPool, extlen, ALLOC_TAG);\n                    if (!ext2) {\n                        ERR(\"out of memory\\n\");\n                        Status = STATUS_INSUFFICIENT_RESOURCES;\n                        goto end;\n                    }\n\n                    if (ext->offset < (uint64_t)ded->SourceFileOffset.QuadPart)\n                        ext2->offset = ded->TargetFileOffset.QuadPart;\n                    else\n                        ext2->offset = ext->offset - ded->SourceFileOffset.QuadPart + ded->TargetFileOffset.QuadPart;\n\n                    ext2->datalen = sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2);\n                    ext2->unique = false;\n                    ext2->ignore = false;\n                    ext2->inserted = true;\n\n                    ext2->extent_data.generation = Vcb->superblock.generation;\n                    ext2->extent_data.decoded_size = ext->extent_data.decoded_size;\n                    ext2->extent_data.compression = ext->extent_data.compression;\n                    ext2->extent_data.encryption = ext->extent_data.encryption;\n                    ext2->extent_data.encoding = ext->extent_data.encoding;\n                    ext2->extent_data.type = ext->extent_data.type;\n\n                    ed2d = (EXTENT_DATA2*)ext2->extent_data.data;\n\n                    ed2d->address = ed2s->address;\n                    ed2d->size = ed2s->size;\n\n                    if (ext->offset < (uint64_t)ded->SourceFileOffset.QuadPart) {\n                        ed2d->offset = ed2s->offset + ded->SourceFileOffset.QuadPart - ext->offset;\n                        ed2d->num_bytes = min((uint64_t)ded->ByteCount.QuadPart, ed2s->num_bytes + ext->offset - ded->SourceFileOffset.QuadPart);\n                    } else {\n                        ed2d->offset = ed2s->offset;\n                        ed2d->num_bytes = min(ded->SourceFileOffset.QuadPart + ded->ByteCount.QuadPart - ext->offset, ed2s->num_bytes);\n                    }\n\n                    if (ext->csum) {\n                        if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE) {\n                            ext2->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ed2d->num_bytes * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG);\n                            if (!ext2->csum) {\n                                ERR(\"out of memory\\n\");\n                                Status = STATUS_INSUFFICIENT_RESOURCES;\n                                ExFreePool(ext2);\n                                goto end;\n                            }\n\n                            RtlCopyMemory(ext2->csum, (uint8_t*)ext->csum + (((ed2d->offset - ed2s->offset) * Vcb->csum_size) >> Vcb->sector_shift),\n                                          (ULONG)((ed2d->num_bytes * Vcb->csum_size) >> Vcb->sector_shift));\n                        } else {\n                            ext2->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ed2d->size * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG);\n                            if (!ext2->csum) {\n                                ERR(\"out of memory\\n\");\n                                Status = STATUS_INSUFFICIENT_RESOURCES;\n                                ExFreePool(ext2);\n                                goto end;\n                            }\n\n                            RtlCopyMemory(ext2->csum, ext->csum, (ULONG)((ed2s->size * Vcb->csum_size) >> Vcb->sector_shift));\n                        }\n                    } else\n                        ext2->csum = NULL;\n\n                    InsertTailList(&newexts, &ext2->list_entry);\n\n                    c = get_chunk_from_address(Vcb, ed2s->address);\n                    if (!c) {\n                        ERR(\"get_chunk_from_address(%I64x) failed\\n\", ed2s->address);\n                        Status = STATUS_INTERNAL_ERROR;\n                        goto end;\n                    }\n\n                    Status = update_changed_extent_ref(Vcb, c, ed2s->address, ed2s->size, fcb->subvol->id, fcb->inode, ext2->offset - ed2d->offset,\n                                                    1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                        goto end;\n                    }\n\n                    nbytes += ed2d->num_bytes;\n                }\n            }\n\n            le = le->Flink;\n        }\n\n        Status = excise_extents(Vcb, fcb, ded->TargetFileOffset.QuadPart, ded->TargetFileOffset.QuadPart + ded->ByteCount.QuadPart, Irp, &rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"excise_extents returned %08lx\\n\", Status);\n\n            while (!IsListEmpty(&newexts)) {\n                extent* ext = CONTAINING_RECORD(RemoveHeadList(&newexts), extent, list_entry);\n                ExFreePool(ext);\n            }\n\n            goto end;\n        }\n\n        // clear unique flags in source fcb\n        le = sourcefcb->extents.Flink;\n        while (le != &sourcefcb->extents) {\n            extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n            if (!ext->ignore && ext->unique && (ext->extent_data.type == EXTENT_TYPE_REGULAR || ext->extent_data.type == EXTENT_TYPE_PREALLOC)) {\n                EXTENT_DATA2* ed2s = (EXTENT_DATA2*)ext->extent_data.data;\n                LIST_ENTRY* le2;\n\n                le2 = newexts.Flink;\n                while (le2 != &newexts) {\n                    extent* ext2 = CONTAINING_RECORD(le2, extent, list_entry);\n\n                    if (ext2->extent_data.type == EXTENT_TYPE_REGULAR || ext2->extent_data.type == EXTENT_TYPE_PREALLOC) {\n                        EXTENT_DATA2* ed2d = (EXTENT_DATA2*)ext2->extent_data.data;\n\n                        if (ed2d->address == ed2s->address && ed2d->size == ed2s->size) {\n                            ext->unique = false;\n                            break;\n                        }\n                    }\n\n                    le2 = le2->Flink;\n                }\n            }\n\n            le = le->Flink;\n        }\n\n        lastextle = &fcb->extents;\n        while (!IsListEmpty(&newexts)) {\n            extent* ext = CONTAINING_RECORD(RemoveHeadList(&newexts), extent, list_entry);\n\n            add_extent(fcb, lastextle, ext);\n            lastextle = &ext->list_entry;\n        }\n    }\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    if (fcb->ads) {\n        ccb->fileref->parent->fcb->inode_item.sequence++;\n\n        if (!ccb->user_set_change_time)\n            ccb->fileref->parent->fcb->inode_item.st_ctime = now;\n\n        ccb->fileref->parent->fcb->inode_item_changed = true;\n        mark_fcb_dirty(ccb->fileref->parent->fcb);\n    } else {\n        fcb->inode_item.st_blocks += nbytes;\n        fcb->inode_item.sequence++;\n\n        if (!ccb->user_set_change_time)\n            fcb->inode_item.st_ctime = now;\n\n        if (!ccb->user_set_write_time) {\n            fcb->inode_item.st_mtime = now;\n            queue_notification_fcb(ccb->fileref, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n        }\n\n        fcb->inode_item_changed = true;\n        fcb->extents_changed = true;\n    }\n\n    mark_fcb_dirty(fcb);\n\n    if (FileObject->SectionObjectPointer->DataSectionObject)\n        CcPurgeCacheSection(FileObject->SectionObjectPointer, &ded->TargetFileOffset, (ULONG)ded->ByteCount.QuadPart, false);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ObDereferenceObject(sourcefo);\n\n    if (NT_SUCCESS(Status))\n        clear_rollback(&rollback);\n    else\n        do_rollback(Vcb, &rollback);\n\n    if (fcb != sourcefcb)\n        ExReleaseResourceLite(sourcefcb->Header.Resource);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS check_inode_used(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb,\n                                 _In_ root* subvol, _In_ uint64_t inode, _In_ uint32_t hash, _In_opt_ PIRP Irp) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n    uint8_t c = hash >> 24;\n\n    if (subvol->fcbs_ptrs[c]) {\n        LIST_ENTRY* le = subvol->fcbs_ptrs[c];\n\n        while (le != &subvol->fcbs) {\n            struct _fcb* fcb2 = CONTAINING_RECORD(le, struct _fcb, list_entry);\n\n            if (fcb2->inode == inode)\n                return STATUS_SUCCESS;\n            else if (fcb2->hash > hash)\n                break;\n\n            le = le->Flink;\n        }\n    }\n\n    searchkey.obj_id = inode;\n    searchkey.obj_type = TYPE_INODE_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, subvol, &tp, &searchkey, false, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type)\n        return STATUS_SUCCESS;\n\n    return STATUS_NOT_FOUND;\n}\n\nstatic NTSTATUS mknod(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, PIRP Irp) {\n    NTSTATUS Status;\n    btrfs_mknod* bmn;\n    fcb *parfcb, *fcb;\n    ccb* parccb;\n    file_ref *parfileref, *fileref;\n    UNICODE_STRING name;\n    root* subvol;\n    uint64_t inode;\n    dir_child* dc;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n    ANSI_STRING utf8;\n    ULONG len, i;\n    SECURITY_SUBJECT_CONTEXT subjcont;\n    PSID owner;\n    BOOLEAN defaulted;\n\n    TRACE(\"(%p, %p, %p, %lu)\\n\", Vcb, FileObject, data, datalen);\n\n    if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (Vcb->readonly)\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n    parfcb = FileObject->FsContext;\n\n    if (parfcb->type != BTRFS_TYPE_DIRECTORY) {\n        WARN(\"trying to create file in something other than a directory\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (is_subvol_readonly(parfcb->subvol, Irp))\n        return STATUS_ACCESS_DENIED;\n\n    parccb = FileObject->FsContext2;\n    parfileref = parccb->fileref;\n\n    if (!parfileref)\n        return STATUS_INVALID_PARAMETER;\n\n    if (datalen < sizeof(btrfs_mknod))\n        return STATUS_INVALID_PARAMETER;\n\n    bmn = (btrfs_mknod*)data;\n\n    if (datalen < offsetof(btrfs_mknod, name[0]) + bmn->namelen || bmn->namelen < sizeof(WCHAR))\n        return STATUS_INVALID_PARAMETER;\n\n    if (bmn->type == BTRFS_TYPE_UNKNOWN || bmn->type > BTRFS_TYPE_SYMLINK)\n        return STATUS_INVALID_PARAMETER;\n\n    if ((bmn->type == BTRFS_TYPE_DIRECTORY && !(parccb->access & FILE_ADD_SUBDIRECTORY)) ||\n        (bmn->type != BTRFS_TYPE_DIRECTORY && !(parccb->access & FILE_ADD_FILE))) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (bmn->inode != 0) {\n        if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode))\n            return STATUS_PRIVILEGE_NOT_HELD;\n    }\n\n    for (i = 0; i < bmn->namelen / sizeof(WCHAR); i++) {\n        if (bmn->name[i] == 0 || bmn->name[i] == '/')\n            return STATUS_OBJECT_NAME_INVALID;\n    }\n\n    // don't allow files called . or ..\n    if (bmn->name[0] == '.' && (bmn->namelen == sizeof(WCHAR) || (bmn->namelen == 2 * sizeof(WCHAR) && bmn->name[1] == '.')))\n        return STATUS_OBJECT_NAME_INVALID;\n\n    Status = utf16_to_utf8(NULL, 0, &len, bmn->name, bmn->namelen);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"utf16_to_utf8 returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (len == 0) {\n        ERR(\"utf16_to_utf8 returned a length of 0\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (len > 0xffff) {\n        ERR(\"len was too long (%lx)\\n\", len);\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    utf8.MaximumLength = utf8.Length = (USHORT)len;\n    utf8.Buffer = ExAllocatePoolWithTag(PagedPool, utf8.MaximumLength, ALLOC_TAG);\n\n    if (!utf8.Buffer) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    Status = utf16_to_utf8(utf8.Buffer, len, &len, bmn->name, bmn->namelen);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"utf16_to_utf8 failed with error %08lx\\n\", Status);\n        ExFreePool(utf8.Buffer);\n        return Status;\n    }\n\n    name.Length = name.MaximumLength = bmn->namelen;\n    name.Buffer = bmn->name;\n\n    Status = find_file_in_dir(&name, parfcb, &subvol, &inode, &dc, true);\n    if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_NOT_FOUND) {\n        ERR(\"find_file_in_dir returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (NT_SUCCESS(Status)) {\n        WARN(\"filename already exists\\n\");\n        Status = STATUS_OBJECT_NAME_COLLISION;\n        goto end;\n    }\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    fcb = create_fcb(Vcb, PagedPool);\n    if (!fcb) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    fcb->Vcb = Vcb;\n\n    fcb->inode_item.generation = Vcb->superblock.generation;\n    fcb->inode_item.transid = Vcb->superblock.generation;\n    fcb->inode_item.st_size = 0;\n    fcb->inode_item.st_blocks = 0;\n    fcb->inode_item.block_group = 0;\n    fcb->inode_item.st_nlink = 1;\n    fcb->inode_item.st_uid = UID_NOBODY;\n    fcb->inode_item.st_gid = GID_NOBODY;\n    fcb->inode_item.st_mode = inherit_mode(parfcb, bmn->type == BTRFS_TYPE_DIRECTORY);\n\n    if (bmn->type == BTRFS_TYPE_BLOCKDEV || bmn->type == BTRFS_TYPE_CHARDEV)\n        fcb->inode_item.st_rdev = (minor(bmn->st_rdev) & 0xFFFFF) | ((major(bmn->st_rdev) & 0xFFFFFFFFFFF) << 20);\n    else\n        fcb->inode_item.st_rdev = 0;\n\n    fcb->inode_item.flags = 0;\n    fcb->inode_item.sequence = 1;\n    fcb->inode_item.st_atime = now;\n    fcb->inode_item.st_ctime = now;\n    fcb->inode_item.st_mtime = now;\n    fcb->inode_item.otime = now;\n\n    if (bmn->type == BTRFS_TYPE_DIRECTORY)\n        fcb->inode_item.st_mode |= __S_IFDIR;\n    else if (bmn->type == BTRFS_TYPE_CHARDEV)\n        fcb->inode_item.st_mode |= __S_IFCHR;\n    else if (bmn->type == BTRFS_TYPE_BLOCKDEV)\n        fcb->inode_item.st_mode |= __S_IFBLK;\n    else if (bmn->type == BTRFS_TYPE_FIFO)\n        fcb->inode_item.st_mode |= __S_IFIFO;\n    else if (bmn->type == BTRFS_TYPE_SOCKET)\n        fcb->inode_item.st_mode |= __S_IFSOCK;\n    else if (bmn->type == BTRFS_TYPE_SYMLINK)\n        fcb->inode_item.st_mode |= __S_IFLNK;\n    else\n        fcb->inode_item.st_mode |= __S_IFREG;\n\n    if (bmn->type != BTRFS_TYPE_DIRECTORY)\n        fcb->inode_item.st_mode &= ~(S_IXUSR | S_IXGRP | S_IXOTH); // remove executable bit if not directory\n\n    // inherit nodatacow flag from parent directory\n    if (parfcb->inode_item.flags & BTRFS_INODE_NODATACOW) {\n        fcb->inode_item.flags |= BTRFS_INODE_NODATACOW;\n\n        if (bmn->type != BTRFS_TYPE_DIRECTORY)\n            fcb->inode_item.flags |= BTRFS_INODE_NODATASUM;\n    }\n\n    if (parfcb->inode_item.flags & BTRFS_INODE_COMPRESS)\n        fcb->inode_item.flags |= BTRFS_INODE_COMPRESS;\n\n    fcb->prop_compression = parfcb->prop_compression;\n    fcb->prop_compression_changed = fcb->prop_compression != PropCompression_None;\n\n    fcb->inode_item_changed = true;\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n    fcb->Header.AllocationSize.QuadPart = 0;\n    fcb->Header.FileSize.QuadPart = 0;\n    fcb->Header.ValidDataLength.QuadPart = 0;\n\n    fcb->atts = 0;\n\n    if (bmn->name[0] == '.')\n        fcb->atts |= FILE_ATTRIBUTE_HIDDEN;\n\n    if (bmn->type == BTRFS_TYPE_DIRECTORY)\n        fcb->atts |= FILE_ATTRIBUTE_DIRECTORY;\n\n    fcb->atts_changed = false;\n\n    InterlockedIncrement(&parfcb->refcount);\n    fcb->subvol = parfcb->subvol;\n\n    SeCaptureSubjectContext(&subjcont);\n\n    Status = SeAssignSecurityEx(parfileref ? parfileref->fcb->sd : NULL, NULL, (void**)&fcb->sd, NULL, fcb->type == BTRFS_TYPE_DIRECTORY,\n                                SEF_SACL_AUTO_INHERIT, &subjcont, IoGetFileObjectGenericMapping(), PagedPool);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"SeAssignSecurityEx returned %08lx\\n\", Status);\n        reap_fcb(fcb);\n        goto end;\n    }\n\n    Status = RtlGetOwnerSecurityDescriptor(fcb->sd, &owner, &defaulted);\n    if (!NT_SUCCESS(Status)) {\n        WARN(\"RtlGetOwnerSecurityDescriptor returned %08lx\\n\", Status);\n        fcb->sd_dirty = true;\n    } else {\n        fcb->inode_item.st_uid = sid_to_uid(owner);\n        fcb->sd_dirty = fcb->inode_item.st_uid == UID_NOBODY;\n    }\n\n    find_gid(fcb, parfcb, &subjcont);\n\n    ExAcquireResourceExclusiveLite(&Vcb->fileref_lock, true);\n    acquire_fcb_lock_exclusive(Vcb);\n\n    if (bmn->inode == 0) {\n        fcb->inode = InterlockedIncrement64(&parfcb->subvol->lastinode);\n        fcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&fcb->inode, sizeof(uint64_t));\n    } else {\n        if (bmn->inode > (uint64_t)parfcb->subvol->lastinode) {\n            fcb->inode = parfcb->subvol->lastinode = bmn->inode;\n            fcb->hash = calc_crc32c(0xffffffff, (uint8_t*)&fcb->inode, sizeof(uint64_t));\n        } else {\n            uint32_t hash = calc_crc32c(0xffffffff, (uint8_t*)&bmn->inode, sizeof(uint64_t));\n\n            Status = check_inode_used(Vcb, subvol, bmn->inode, hash, Irp);\n            if (NT_SUCCESS(Status)) { // STATUS_SUCCESS means inode found\n                release_fcb_lock(Vcb);\n                ExReleaseResourceLite(&Vcb->fileref_lock);\n\n                WARN(\"inode collision\\n\");\n                Status = STATUS_INVALID_PARAMETER;\n                goto end;\n            } else if (Status != STATUS_NOT_FOUND) {\n                ERR(\"check_inode_used returned %08lx\\n\", Status);\n\n                release_fcb_lock(Vcb);\n                ExReleaseResourceLite(&Vcb->fileref_lock);\n                goto end;\n            }\n\n            fcb->inode = bmn->inode;\n            fcb->hash = hash;\n        }\n    }\n\n    fcb->inode = inode;\n    fcb->type = bmn->type;\n\n    fileref = create_fileref(Vcb);\n    if (!fileref) {\n        release_fcb_lock(Vcb);\n        ExReleaseResourceLite(&Vcb->fileref_lock);\n\n        ERR(\"out of memory\\n\");\n        reap_fcb(fcb);\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    fileref->fcb = fcb;\n\n    fcb->created = true;\n    fileref->created = true;\n\n    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;\n    fcb->subvol->root_item.ctime = now;\n\n    fileref->parent = parfileref;\n\n    mark_fcb_dirty(fcb);\n    mark_fileref_dirty(fileref);\n\n    Status = add_dir_child(fileref->parent->fcb, fcb->inode, false, &utf8, &name, fcb->type, &dc);\n    if (!NT_SUCCESS(Status))\n        WARN(\"add_dir_child returned %08lx\\n\", Status);\n\n    fileref->dc = dc;\n    dc->fileref = fileref;\n\n    ExAcquireResourceExclusiveLite(&parfileref->fcb->nonpaged->dir_children_lock, true);\n    InsertTailList(&parfileref->children, &fileref->list_entry);\n    ExReleaseResourceLite(&parfileref->fcb->nonpaged->dir_children_lock);\n\n    increase_fileref_refcount(parfileref);\n\n    if (fcb->type == BTRFS_TYPE_DIRECTORY) {\n        fcb->hash_ptrs = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);\n        if (!fcb->hash_ptrs) {\n            release_fcb_lock(Vcb);\n            ExReleaseResourceLite(&Vcb->fileref_lock);\n\n            ERR(\"out of memory\\n\");\n            free_fileref(fileref);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        RtlZeroMemory(fcb->hash_ptrs, sizeof(LIST_ENTRY*) * 256);\n\n        fcb->hash_ptrs_uc = ExAllocatePoolWithTag(PagedPool, sizeof(LIST_ENTRY*) * 256, ALLOC_TAG);\n        if (!fcb->hash_ptrs_uc) {\n            release_fcb_lock(Vcb);\n            ExReleaseResourceLite(&Vcb->fileref_lock);\n\n            ERR(\"out of memory\\n\");\n            free_fileref(fileref);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        RtlZeroMemory(fcb->hash_ptrs_uc, sizeof(LIST_ENTRY*) * 256);\n    }\n\n    add_fcb_to_subvol(fcb);\n    InsertTailList(&Vcb->all_fcbs, &fcb->list_entry_all);\n\n    if (bmn->type == BTRFS_TYPE_DIRECTORY)\n        fileref->fcb->fileref = fileref;\n\n    ExAcquireResourceExclusiveLite(parfcb->Header.Resource, true);\n    parfcb->inode_item.st_size += utf8.Length * 2;\n    parfcb->inode_item.transid = Vcb->superblock.generation;\n    parfcb->inode_item.sequence++;\n\n    if (!parccb->user_set_change_time)\n        parfcb->inode_item.st_ctime = now;\n\n    if (!parccb->user_set_write_time)\n        parfcb->inode_item.st_mtime = now;\n\n    parfcb->subvol->fcbs_version++;\n\n    ExReleaseResourceLite(parfcb->Header.Resource);\n    release_fcb_lock(Vcb);\n    ExReleaseResourceLite(&Vcb->fileref_lock);\n\n    parfcb->inode_item_changed = true;\n    mark_fcb_dirty(parfcb);\n\n    send_notification_fileref(fileref, bmn->type == BTRFS_TYPE_DIRECTORY ? FILE_NOTIFY_CHANGE_DIR_NAME : FILE_NOTIFY_CHANGE_FILE_NAME, FILE_ACTION_ADDED, NULL);\n\n    if (!parccb->user_set_write_time)\n        queue_notification_fcb(parfileref, FILE_NOTIFY_CHANGE_LAST_WRITE, FILE_ACTION_MODIFIED, NULL);\n\n    Status = STATUS_SUCCESS;\n\nend:\n\n    ExFreePool(utf8.Buffer);\n\n    return Status;\n}\n\nstatic void mark_subvol_dirty(device_extension* Vcb, root* r) {\n    if (!r->dirty) {\n        r->dirty = true;\n\n        ExAcquireResourceExclusiveLite(&Vcb->dirty_subvols_lock, true);\n        InsertTailList(&Vcb->dirty_subvols, &r->list_entry_dirty);\n        ExReleaseResourceLite(&Vcb->dirty_subvols_lock);\n    }\n\n    Vcb->need_write = true;\n}\n\nstatic NTSTATUS recvd_subvol(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, KPROCESSOR_MODE processor_mode) {\n    btrfs_received_subvol* brs = (btrfs_received_subvol*)data;\n    fcb* fcb;\n    NTSTATUS Status;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n\n    TRACE(\"(%p, %p, %p, %lu)\\n\", Vcb, FileObject, data, datalen);\n\n    if (!data || datalen < sizeof(btrfs_received_subvol))\n        return STATUS_INVALID_PARAMETER;\n\n    if (!FileObject || !FileObject->FsContext || FileObject->FsContext == Vcb->volume_fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb->subvol)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    if (fcb->subvol->root_item.rtransid != 0) {\n        WARN(\"subvol already has received information set\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    RtlCopyMemory(&fcb->subvol->root_item.received_uuid, &brs->uuid, sizeof(BTRFS_UUID));\n    fcb->subvol->root_item.stransid = brs->generation;\n    fcb->subvol->root_item.rtransid = Vcb->superblock.generation;\n    fcb->subvol->root_item.rtime = now;\n\n    fcb->subvol->received = true;\n    mark_subvol_dirty(Vcb, fcb->subvol);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS fsctl_get_xattrs(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, KPROCESSOR_MODE processor_mode) {\n    LIST_ENTRY* le;\n    btrfs_set_xattr* bsxa;\n    ULONG reqlen = (ULONG)offsetof(btrfs_set_xattr, data[0]);\n    fcb* fcb;\n    ccb* ccb;\n\n    if (!data || datalen < reqlen)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n    ccb = FileObject->FsContext2;\n\n    if (!(ccb->access & (FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES)) && processor_mode == UserMode) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    le = fcb->xattrs.Flink;\n    while (le != &fcb->xattrs) {\n        xattr* xa = CONTAINING_RECORD(le, xattr, list_entry);\n\n        if (xa->valuelen > 0)\n            reqlen += (ULONG)offsetof(btrfs_set_xattr, data[0]) + xa->namelen + xa->valuelen;\n\n        le = le->Flink;\n    }\n\n    if (datalen < reqlen) {\n        ExReleaseResourceLite(fcb->Header.Resource);\n        return STATUS_BUFFER_OVERFLOW;\n    }\n\n    bsxa = (btrfs_set_xattr*)data;\n\n    if (reqlen > 0) {\n        le = fcb->xattrs.Flink;\n        while (le != &fcb->xattrs) {\n            xattr* xa = CONTAINING_RECORD(le, xattr, list_entry);\n\n            if (xa->valuelen > 0) {\n                bsxa->namelen = xa->namelen;\n                bsxa->valuelen = xa->valuelen;\n                memcpy(bsxa->data, xa->data, xa->namelen + xa->valuelen);\n\n                bsxa = (btrfs_set_xattr*)&bsxa->data[xa->namelen + xa->valuelen];\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    bsxa->namelen = 0;\n    bsxa->valuelen = 0;\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS fsctl_set_xattr(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, PIRP Irp) {\n    NTSTATUS Status;\n    btrfs_set_xattr* bsxa;\n    xattr* xa;\n    fcb* fcb;\n    ccb* ccb;\n    LIST_ENTRY* le;\n\n    static const char stream_pref[] = \"user.\";\n\n    TRACE(\"(%p, %p, %p, %lu)\\n\", Vcb, FileObject, data, datalen);\n\n    if (!data || datalen < sizeof(btrfs_set_xattr))\n        return STATUS_INVALID_PARAMETER;\n\n    bsxa = (btrfs_set_xattr*)data;\n\n    if (datalen < offsetof(btrfs_set_xattr, data[0]) + bsxa->namelen + bsxa->valuelen)\n        return STATUS_INVALID_PARAMETER;\n\n    if (bsxa->namelen + bsxa->valuelen + sizeof(tree_header) + sizeof(leaf_node) + offsetof(DIR_ITEM, name[0]) > Vcb->superblock.node_size)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (Vcb->readonly)\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n    fcb = FileObject->FsContext;\n    ccb = FileObject->FsContext2;\n\n    if (is_subvol_readonly(fcb->subvol, Irp))\n        return STATUS_ACCESS_DENIED;\n\n    if (!(ccb->access & FILE_WRITE_ATTRIBUTES) && Irp->RequestorMode == UserMode) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (bsxa->namelen == sizeof(EA_NTACL) - 1 && RtlCompareMemory(bsxa->data, EA_NTACL, sizeof(EA_NTACL) - 1) == sizeof(EA_NTACL) - 1) {\n        if ((!(ccb->access & WRITE_DAC) || !(ccb->access & WRITE_OWNER)) && Irp->RequestorMode == UserMode) {\n            WARN(\"insufficient privileges\\n\");\n            Status = STATUS_ACCESS_DENIED;\n            goto end;\n        }\n\n        if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode)) {\n            Status = STATUS_PRIVILEGE_NOT_HELD;\n            goto end;\n        }\n\n        if (fcb->sd)\n            ExFreePool(fcb->sd);\n\n        if (bsxa->valuelen > 0 && RtlValidRelativeSecurityDescriptor(bsxa->data + bsxa->namelen, bsxa->valuelen, 0)) {\n            fcb->sd = ExAllocatePoolWithTag(PagedPool, bsxa->valuelen, ALLOC_TAG);\n            if (!fcb->sd) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            RtlCopyMemory(fcb->sd, bsxa->data + bsxa->namelen, bsxa->valuelen);\n        } else if (fcb->sd)\n            fcb->sd = NULL;\n\n        fcb->sd_dirty = true;\n\n        if (!fcb->sd) {\n            fcb_get_sd(fcb, ccb->fileref->parent->fcb, false, Irp);\n            fcb->sd_deleted = true;\n        }\n\n        mark_fcb_dirty(fcb);\n\n        Status = STATUS_SUCCESS;\n        goto end;\n    } else if (bsxa->namelen == sizeof(EA_DOSATTRIB) - 1 && RtlCompareMemory(bsxa->data, EA_DOSATTRIB, sizeof(EA_DOSATTRIB) - 1) == sizeof(EA_DOSATTRIB) - 1) {\n        ULONG atts;\n\n        if (bsxa->valuelen > 0 && get_file_attributes_from_xattr(bsxa->data + bsxa->namelen, bsxa->valuelen, &atts)) {\n            fcb->atts = atts;\n\n            if (fcb->type == BTRFS_TYPE_DIRECTORY)\n                fcb->atts |= FILE_ATTRIBUTE_DIRECTORY;\n            else if (fcb->type == BTRFS_TYPE_SYMLINK)\n                fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT;\n\n            if (fcb->inode == SUBVOL_ROOT_INODE) {\n                if (fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY)\n                    fcb->atts |= FILE_ATTRIBUTE_READONLY;\n                else\n                    fcb->atts &= ~FILE_ATTRIBUTE_READONLY;\n            }\n\n            fcb->atts_deleted = false;\n        } else {\n            bool hidden = ccb->fileref && ccb->fileref->dc && ccb->fileref->dc->utf8.Buffer && ccb->fileref->dc->utf8.Buffer[0] == '.';\n\n            fcb->atts = get_file_attributes(Vcb, fcb->subvol, fcb->inode, fcb->type, hidden, true, Irp);\n            fcb->atts_deleted = true;\n        }\n\n        fcb->atts_changed = true;\n        mark_fcb_dirty(fcb);\n\n        Status = STATUS_SUCCESS;\n        goto end;\n    } else if (bsxa->namelen == sizeof(EA_REPARSE) - 1 && RtlCompareMemory(bsxa->data, EA_REPARSE, sizeof(EA_REPARSE) - 1) == sizeof(EA_REPARSE) - 1) {\n        if (fcb->reparse_xattr.Buffer) {\n            ExFreePool(fcb->reparse_xattr.Buffer);\n            fcb->reparse_xattr.Buffer = NULL;\n            fcb->reparse_xattr.Length = fcb->reparse_xattr.MaximumLength = 0;\n        }\n\n        if (bsxa->valuelen > 0) {\n            fcb->reparse_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, bsxa->valuelen, ALLOC_TAG);\n            if (!fcb->reparse_xattr.Buffer) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            RtlCopyMemory(fcb->reparse_xattr.Buffer, bsxa->data + bsxa->namelen, bsxa->valuelen);\n            fcb->reparse_xattr.Length = fcb->reparse_xattr.MaximumLength = bsxa->valuelen;\n        }\n\n        fcb->reparse_xattr_changed = true;\n        mark_fcb_dirty(fcb);\n\n        Status = STATUS_SUCCESS;\n        goto end;\n    } else if (bsxa->namelen == sizeof(EA_EA) - 1 && RtlCompareMemory(bsxa->data, EA_EA, sizeof(EA_EA) - 1) == sizeof(EA_EA) - 1) {\n        if (!(ccb->access & FILE_WRITE_EA) && Irp->RequestorMode == UserMode) {\n            WARN(\"insufficient privileges\\n\");\n            Status = STATUS_ACCESS_DENIED;\n            goto end;\n        }\n\n        if (fcb->ea_xattr.Buffer) {\n            ExFreePool(fcb->ea_xattr.Buffer);\n            fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = 0;\n            fcb->ea_xattr.Buffer = NULL;\n        }\n\n        fcb->ealen = 0;\n\n        if (bsxa->valuelen > 0) {\n            ULONG offset;\n\n            Status = IoCheckEaBufferValidity((FILE_FULL_EA_INFORMATION*)(bsxa->data + bsxa->namelen), bsxa->valuelen, &offset);\n\n            if (!NT_SUCCESS(Status))\n                WARN(\"IoCheckEaBufferValidity returned %08lx (error at offset %lu)\\n\", Status, offset);\n            else {\n                FILE_FULL_EA_INFORMATION* eainfo;\n\n                fcb->ea_xattr.Buffer = ExAllocatePoolWithTag(PagedPool, bsxa->valuelen, ALLOC_TAG);\n                if (!fcb->ea_xattr.Buffer) {\n                    ERR(\"out of memory\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto end;\n                }\n\n                RtlCopyMemory(fcb->ea_xattr.Buffer, bsxa->data + bsxa->namelen, bsxa->valuelen);\n\n                fcb->ea_xattr.Length = fcb->ea_xattr.MaximumLength = bsxa->valuelen;\n\n                fcb->ealen = 4;\n\n                // calculate ealen\n                eainfo = (FILE_FULL_EA_INFORMATION*)(bsxa->data + bsxa->namelen);\n                do {\n                    fcb->ealen += 5 + eainfo->EaNameLength + eainfo->EaValueLength;\n\n                    if (eainfo->NextEntryOffset == 0)\n                        break;\n\n                    eainfo = (FILE_FULL_EA_INFORMATION*)(((uint8_t*)eainfo) + eainfo->NextEntryOffset);\n                } while (true);\n            }\n        }\n\n        fcb->ea_changed = true;\n        mark_fcb_dirty(fcb);\n\n        Status = STATUS_SUCCESS;\n        goto end;\n    } else if (bsxa->namelen == sizeof(EA_CASE_SENSITIVE) - 1 && RtlCompareMemory(bsxa->data, EA_CASE_SENSITIVE, sizeof(EA_CASE_SENSITIVE) - 1) == sizeof(EA_CASE_SENSITIVE) - 1) {\n        if (bsxa->valuelen > 0 && bsxa->data[bsxa->namelen] == '1') {\n            fcb->case_sensitive = true;\n            mark_fcb_dirty(fcb);\n        }\n\n        Status = STATUS_SUCCESS;\n        goto end;\n    } else if (bsxa->namelen == sizeof(EA_PROP_COMPRESSION) - 1 && RtlCompareMemory(bsxa->data, EA_PROP_COMPRESSION, sizeof(EA_PROP_COMPRESSION) - 1) == sizeof(EA_PROP_COMPRESSION) - 1) {\n        static const char lzo[] = \"lzo\";\n        static const char zlib[] = \"zlib\";\n        static const char zstd[] = \"zstd\";\n\n        if (bsxa->valuelen == sizeof(zstd) - 1 && RtlCompareMemory(bsxa->data + bsxa->namelen, zstd, bsxa->valuelen) == bsxa->valuelen)\n            fcb->prop_compression = PropCompression_ZSTD;\n        else if (bsxa->valuelen == sizeof(lzo) - 1 && RtlCompareMemory(bsxa->data + bsxa->namelen, lzo, bsxa->valuelen) == bsxa->valuelen)\n            fcb->prop_compression = PropCompression_LZO;\n        else if (bsxa->valuelen == sizeof(zlib) - 1 && RtlCompareMemory(bsxa->data + bsxa->namelen, zlib, bsxa->valuelen) == bsxa->valuelen)\n            fcb->prop_compression = PropCompression_Zlib;\n        else\n            fcb->prop_compression = PropCompression_None;\n\n        if (fcb->prop_compression != PropCompression_None) {\n            fcb->inode_item.flags |= BTRFS_INODE_COMPRESS;\n            fcb->inode_item_changed = true;\n        }\n\n        fcb->prop_compression_changed = true;\n        mark_fcb_dirty(fcb);\n\n        Status = STATUS_SUCCESS;\n        goto end;\n    } else if (bsxa->namelen >= (sizeof(stream_pref) - 1) && RtlCompareMemory(bsxa->data, stream_pref, sizeof(stream_pref) - 1) == sizeof(stream_pref) - 1) {\n        // don't allow xattrs beginning with user., as these appear as streams instead\n        Status = STATUS_OBJECT_NAME_INVALID;\n        goto end;\n    }\n\n    xa = ExAllocatePoolWithTag(PagedPool, offsetof(xattr, data[0]) + bsxa->namelen + bsxa->valuelen, ALLOC_TAG);\n    if (!xa) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    le = fcb->xattrs.Flink;\n    while (le != &fcb->xattrs) {\n        xattr* xa2 = CONTAINING_RECORD(le, xattr, list_entry);\n\n        if (xa2->namelen == bsxa->namelen && RtlCompareMemory(xa2->data, bsxa->data, xa2->namelen) == xa2->namelen) {\n            RemoveEntryList(&xa2->list_entry);\n            ExFreePool(xa2);\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    xa->namelen = bsxa->namelen;\n    xa->valuelen = bsxa->valuelen;\n    xa->dirty = true;\n    RtlCopyMemory(xa->data, bsxa->data, bsxa->namelen + bsxa->valuelen);\n\n    InsertTailList(&fcb->xattrs, &xa->list_entry);\n\n    fcb->xattrs_changed = true;\n    mark_fcb_dirty(fcb);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS reserve_subvol(device_extension* Vcb, PFILE_OBJECT FileObject, PIRP Irp) {\n    fcb* fcb;\n    ccb* ccb;\n\n    TRACE(\"(%p, %p)\\n\", Vcb, FileObject);\n\n    // \"Reserving\" a readonly subvol allows the calling process to write into it until the handle is closed.\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n    ccb = FileObject->FsContext2;\n\n    if (!(fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY))\n        return STATUS_INVALID_PARAMETER;\n\n    if (fcb->subvol->reserved)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb->subvol->reserved = PsGetCurrentProcess();\n    ccb->reserving = true;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS get_subvol_path(device_extension* Vcb, uint64_t id, WCHAR* out, ULONG outlen, PIRP Irp) {\n    LIST_ENTRY* le;\n    root* r = NULL;\n    NTSTATUS Status;\n    file_ref* fr;\n    UNICODE_STRING us;\n\n    le = Vcb->roots.Flink;\n    while (le != &Vcb->roots) {\n        root* r2 = CONTAINING_RECORD(le, root, list_entry);\n\n        if (r2->id == id) {\n            r = r2;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!r) {\n        ERR(\"couldn't find subvol %I64x\\n\", id);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->fileref_lock, true);\n\n    Status = open_fileref_by_inode(Vcb, r, r->root_item.objid, &fr, Irp);\n    if (!NT_SUCCESS(Status)) {\n        ExReleaseResourceLite(&Vcb->fileref_lock);\n        ERR(\"open_fileref_by_inode returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    us.Buffer = out;\n    us.Length = 0;\n    us.MaximumLength = (USHORT)min(0xffff, outlen) - sizeof(WCHAR);\n\n    Status = fileref_get_filename(fr, &us, NULL, NULL);\n\n    if (NT_SUCCESS(Status) || Status == STATUS_BUFFER_OVERFLOW)\n        out[us.Length / sizeof(WCHAR)] = 0;\n    else\n        ERR(\"fileref_get_filename returned %08lx\\n\", Status);\n\n    free_fileref(fr);\n\n    ExReleaseResourceLite(&Vcb->fileref_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS find_subvol(device_extension* Vcb, void* in, ULONG inlen, void* out, ULONG outlen, PIRP Irp) {\n    btrfs_find_subvol* bfs;\n    NTSTATUS Status;\n    traverse_ptr tp;\n    KEY searchkey;\n\n    if (!in || inlen < sizeof(btrfs_find_subvol))\n        return STATUS_INVALID_PARAMETER;\n\n    if (!out || outlen < sizeof(WCHAR))\n        return STATUS_INVALID_PARAMETER;\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    bfs = (btrfs_find_subvol*)in;\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    if (!Vcb->uuid_root) {\n        ERR(\"couldn't find uuid root\\n\");\n        Status = STATUS_NOT_FOUND;\n        goto end;\n    }\n\n    RtlCopyMemory(&searchkey.obj_id, &bfs->uuid, sizeof(uint64_t));\n    searchkey.obj_type = TYPE_SUBVOL_UUID;\n    RtlCopyMemory(&searchkey.offset, &bfs->uuid.uuid[sizeof(uint64_t)], sizeof(uint64_t));\n\n    Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (!keycmp(searchkey, tp.item->key) && tp.item->size >= sizeof(uint64_t)) {\n        uint64_t* id = (uint64_t*)tp.item->data;\n\n        if (bfs->ctransid != 0) {\n            KEY searchkey2;\n            traverse_ptr tp2;\n\n            searchkey2.obj_id = *id;\n            searchkey2.obj_type = TYPE_ROOT_ITEM;\n            searchkey2.offset = 0xffffffffffffffff;\n\n            Status = find_item(Vcb, Vcb->root_root, &tp2, &searchkey2, false, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"find_item returned %08lx\\n\", Status);\n                goto end;\n            }\n\n            if (tp2.item->key.obj_id == searchkey2.obj_id && tp2.item->key.obj_type == searchkey2.obj_type &&\n                tp2.item->size >= offsetof(ROOT_ITEM, otransid)) {\n                ROOT_ITEM* ri = (ROOT_ITEM*)tp2.item->data;\n\n                if (ri->ctransid == bfs->ctransid) {\n                    TRACE(\"found subvol %I64x\\n\", *id);\n                    Status = get_subvol_path(Vcb, *id, out, outlen, Irp);\n                    goto end;\n                }\n            }\n        } else {\n            TRACE(\"found subvol %I64x\\n\", *id);\n            Status = get_subvol_path(Vcb, *id, out, outlen, Irp);\n            goto end;\n        }\n    }\n\n    searchkey.obj_type = TYPE_SUBVOL_REC_UUID;\n\n    Status = find_item(Vcb, Vcb->uuid_root, &tp, &searchkey, false, Irp);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (!keycmp(searchkey, tp.item->key) && tp.item->size >= sizeof(uint64_t)) {\n        uint64_t* ids = (uint64_t*)tp.item->data;\n        ULONG i;\n\n        for (i = 0; i < tp.item->size / sizeof(uint64_t); i++) {\n            if (bfs->ctransid != 0) {\n                KEY searchkey2;\n                traverse_ptr tp2;\n\n                searchkey2.obj_id = ids[i];\n                searchkey2.obj_type = TYPE_ROOT_ITEM;\n                searchkey2.offset = 0xffffffffffffffff;\n\n                Status = find_item(Vcb, Vcb->root_root, &tp2, &searchkey2, false, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"find_item returned %08lx\\n\", Status);\n                    goto end;\n                }\n\n                if (tp2.item->key.obj_id == searchkey2.obj_id && tp2.item->key.obj_type == searchkey2.obj_type &&\n                    tp2.item->size >= offsetof(ROOT_ITEM, otransid)) {\n                    ROOT_ITEM* ri = (ROOT_ITEM*)tp2.item->data;\n\n                    if (ri->ctransid == bfs->ctransid) {\n                        TRACE(\"found subvol %I64x\\n\", ids[i]);\n                        Status = get_subvol_path(Vcb, ids[i], out, outlen, Irp);\n                        goto end;\n                    }\n                }\n            } else {\n                TRACE(\"found subvol %I64x\\n\", ids[i]);\n                Status = get_subvol_path(Vcb, ids[i], out, outlen, Irp);\n                goto end;\n            }\n        }\n    }\n\n    Status = STATUS_NOT_FOUND;\n\nend:\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS resize_device(device_extension* Vcb, void* data, ULONG len, PIRP Irp) {\n    btrfs_resize* br = (btrfs_resize*)data;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    device* dev = NULL;\n\n    TRACE(\"(%p, %p, %lu)\\n\", Vcb, data, len);\n\n    if (!data || len < sizeof(btrfs_resize) || (br->size & (Vcb->superblock.sector_size - 1)) != 0)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (Vcb->readonly)\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    le = Vcb->devices.Flink;\n    while (le != &Vcb->devices) {\n        device* dev2 = CONTAINING_RECORD(le, device, list_entry);\n\n        if (dev2->devitem.dev_id == br->device) {\n            dev = dev2;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!dev) {\n        ERR(\"could not find device %I64x\\n\", br->device);\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (!dev->devobj) {\n        ERR(\"trying to resize missing device\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (dev->readonly) {\n        ERR(\"trying to resize readonly device\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (br->size > 0 && dev->devitem.num_bytes == br->size) {\n        TRACE(\"size unchanged, returning STATUS_SUCCESS\\n\");\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    if (br->size > 0 && dev->devitem.num_bytes > br->size) { // shrink device\n        bool need_balance = true;\n        uint64_t old_size, delta;\n\n        le = dev->space.Flink;\n        while (le != &dev->space) {\n            space* s = CONTAINING_RECORD(le, space, list_entry);\n\n            if (s->address <= br->size && s->address + s->size >= dev->devitem.num_bytes) {\n                need_balance = false;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        delta = dev->devitem.num_bytes - br->size;\n\n        if (need_balance) {\n            OBJECT_ATTRIBUTES oa;\n            int i;\n\n            if (Vcb->balance.thread) {\n                WARN(\"balance already running\\n\");\n                Status = STATUS_DEVICE_NOT_READY;\n                goto end;\n            }\n\n            RtlZeroMemory(Vcb->balance.opts, sizeof(btrfs_balance_opts) * 3);\n\n            for (i = 0; i < 3; i++) {\n                Vcb->balance.opts[i].flags = BTRFS_BALANCE_OPTS_ENABLED | BTRFS_BALANCE_OPTS_DEVID | BTRFS_BALANCE_OPTS_DRANGE;\n                Vcb->balance.opts[i].devid = dev->devitem.dev_id;\n                Vcb->balance.opts[i].drange_start = br->size;\n                Vcb->balance.opts[i].drange_end = dev->devitem.num_bytes;\n            }\n\n            Vcb->balance.paused = false;\n            Vcb->balance.shrinking = true;\n            Vcb->balance.status = STATUS_SUCCESS;\n            KeInitializeEvent(&Vcb->balance.event, NotificationEvent, !Vcb->balance.paused);\n\n            space_list_subtract2(&dev->space, NULL, br->size, delta, NULL, NULL);\n\n            InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);\n\n            Status = PsCreateSystemThread(&Vcb->balance.thread, 0, &oa, NULL, NULL, balance_thread, Vcb);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"PsCreateSystemThread returned %08lx\\n\", Status);\n                goto end;\n            }\n\n            Status = STATUS_MORE_PROCESSING_REQUIRED;\n\n            goto end;\n        }\n\n        old_size = dev->devitem.num_bytes;\n        dev->devitem.num_bytes = br->size;\n\n        Status = update_dev_item(Vcb, dev, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"update_dev_item returned %08lx\\n\", Status);\n            dev->devitem.num_bytes = old_size;\n            goto end;\n        }\n\n        space_list_subtract2(&dev->space, NULL, br->size, delta, NULL, NULL);\n\n        Vcb->superblock.total_bytes -= delta;\n    } else { // extend device\n        GET_LENGTH_INFORMATION gli;\n        uint64_t old_size, delta;\n\n        Status = dev_ioctl(dev->devobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0,\n                           &gli, sizeof(gli), true, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"IOCTL_DISK_GET_LENGTH_INFO returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        if (br->size == 0) {\n            br->size = gli.Length.QuadPart;\n\n            if (dev->devitem.num_bytes == br->size) {\n                TRACE(\"size unchanged, returning STATUS_SUCCESS\\n\");\n                Status = STATUS_SUCCESS;\n                goto end;\n            }\n\n            if (br->size == 0) {\n                ERR(\"IOCTL_DISK_GET_LENGTH_INFO returned 0 length\\n\");\n                Status = STATUS_INTERNAL_ERROR;\n                goto end;\n            }\n        } else if ((uint64_t)gli.Length.QuadPart < br->size) {\n            ERR(\"device was %I64x bytes, trying to extend to %I64x\\n\", gli.Length.QuadPart, br->size);\n            Status = STATUS_INVALID_PARAMETER;\n            goto end;\n        }\n\n        delta = br->size - dev->devitem.num_bytes;\n\n        old_size = dev->devitem.num_bytes;\n        dev->devitem.num_bytes = br->size;\n\n        Status = update_dev_item(Vcb, dev, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"update_dev_item returned %08lx\\n\", Status);\n            dev->devitem.num_bytes = old_size;\n            goto end;\n        }\n\n        space_list_add2(&dev->space, NULL, dev->devitem.num_bytes, delta, NULL, NULL);\n\n        Vcb->superblock.total_bytes += delta;\n    }\n\n    Status = STATUS_SUCCESS;\n    Vcb->need_write = true;\n\nend:\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    if (NT_SUCCESS(Status))\n        FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_CHANGE_SIZE);\n\n    return Status;\n}\n\nstatic NTSTATUS fsctl_oplock(device_extension* Vcb, PIRP* Pirp) {\n    NTSTATUS Status;\n    PIRP Irp = *Pirp;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    uint32_t fsctl = IrpSp->Parameters.FileSystemControl.FsControlCode;\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    fcb* fcb = FileObject ? FileObject->FsContext : NULL;\n    ccb* ccb = FileObject ? FileObject->FsContext2 : NULL;\n    file_ref* fileref = ccb ? ccb->fileref : NULL;\n    PREQUEST_OPLOCK_INPUT_BUFFER buf = NULL;\n    bool oplock_request = false, oplock_ack = false;\n    ULONG oplock_count = 0;\n\n    if (!fcb) {\n        ERR(\"fcb was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (!fileref) {\n        ERR(\"fileref was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (fcb->type != BTRFS_TYPE_FILE && fcb->type != BTRFS_TYPE_DIRECTORY)\n        return STATUS_INVALID_PARAMETER;\n\n    if (fsctl == FSCTL_REQUEST_OPLOCK) {\n        if (IrpSp->Parameters.FileSystemControl.InputBufferLength < sizeof(REQUEST_OPLOCK_INPUT_BUFFER))\n            return STATUS_BUFFER_TOO_SMALL;\n\n        if (IrpSp->Parameters.FileSystemControl.OutputBufferLength < sizeof(REQUEST_OPLOCK_OUTPUT_BUFFER))\n            return STATUS_BUFFER_TOO_SMALL;\n\n        buf = Irp->AssociatedIrp.SystemBuffer;\n\n        // flags are mutually exclusive\n        if (buf->Flags & REQUEST_OPLOCK_INPUT_FLAG_REQUEST && buf->Flags & REQUEST_OPLOCK_INPUT_FLAG_ACK)\n            return STATUS_INVALID_PARAMETER;\n\n        oplock_request = buf->Flags & REQUEST_OPLOCK_INPUT_FLAG_REQUEST;\n        oplock_ack = buf->Flags & REQUEST_OPLOCK_INPUT_FLAG_ACK;\n\n        if (!oplock_request && !oplock_ack)\n            return STATUS_INVALID_PARAMETER;\n    }\n\n    bool shared_request = (fsctl == FSCTL_REQUEST_OPLOCK_LEVEL_2) || (fsctl == FSCTL_REQUEST_OPLOCK && !(buf->RequestedOplockLevel & OPLOCK_LEVEL_CACHE_WRITE));\n\n    if (fcb->type == BTRFS_TYPE_DIRECTORY && (fsctl != FSCTL_REQUEST_OPLOCK || !shared_request)) {\n        WARN(\"oplock requests on directories can only be for read or read-handle oplocks\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (fsctl == FSCTL_REQUEST_OPLOCK_LEVEL_1 || fsctl == FSCTL_REQUEST_BATCH_OPLOCK || fsctl == FSCTL_REQUEST_FILTER_OPLOCK ||\n        fsctl == FSCTL_REQUEST_OPLOCK_LEVEL_2 || oplock_request) {\n        if (shared_request) {\n            if (fcb->type == BTRFS_TYPE_FILE) {\n                if (fFsRtlCheckLockForOplockRequest)\n                    oplock_count = !fFsRtlCheckLockForOplockRequest(&fcb->lock, &fcb->Header.AllocationSize);\n                else if (fFsRtlAreThereCurrentOrInProgressFileLocks)\n                    oplock_count = fFsRtlAreThereCurrentOrInProgressFileLocks(&fcb->lock);\n                else\n                    oplock_count = FsRtlAreThereCurrentFileLocks(&fcb->lock);\n            }\n        } else\n            oplock_count = fileref->open_count;\n    }\n\n    if ((fsctl == FSCTL_REQUEST_FILTER_OPLOCK || fsctl == FSCTL_REQUEST_BATCH_OPLOCK ||\n        (fsctl == FSCTL_REQUEST_OPLOCK && buf->RequestedOplockLevel & OPLOCK_LEVEL_CACHE_HANDLE)) &&\n        fileref->delete_on_close) {\n        ExReleaseResourceLite(fcb->Header.Resource);\n        ExReleaseResourceLite(&Vcb->tree_lock);\n        return STATUS_DELETE_PENDING;\n    }\n\n    Status = FsRtlOplockFsctrl(fcb_oplock(fcb), Irp, oplock_count);\n\n    *Pirp = NULL;\n\n    fcb->Header.IsFastIoPossible = fast_io_possible(fcb);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS get_retrieval_pointers(device_extension* Vcb, PFILE_OBJECT FileObject, STARTING_VCN_INPUT_BUFFER* in,\n                                       ULONG inlen, RETRIEVAL_POINTERS_BUFFER* out, ULONG outlen, ULONG_PTR* retlen) {\n    NTSTATUS Status;\n    fcb* fcb;\n\n    TRACE(\"get_retrieval_pointers(%p, %p, %p, %lx, %p, %lx, %p)\\n\", Vcb, FileObject, in, inlen,\n                                                                    out, outlen, retlen);\n\n    if (!FileObject)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (inlen < sizeof(STARTING_VCN_INPUT_BUFFER) || in->StartingVcn.QuadPart < 0)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!out)\n        return STATUS_INVALID_PARAMETER;\n\n    if (outlen < offsetof(RETRIEVAL_POINTERS_BUFFER, Extents[0]))\n        return STATUS_BUFFER_TOO_SMALL;\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    try {\n        LIST_ENTRY* le = fcb->extents.Flink;\n        extent* first_ext = NULL;\n        unsigned int num_extents = 0, first_extent_num = 0, i;\n        uint64_t num_sectors, last_off = 0;\n\n        num_sectors = (fcb->inode_item.st_size + Vcb->superblock.sector_size - 1) >> Vcb->sector_shift;\n\n        while (le != &fcb->extents) {\n            extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n            if (ext->ignore || ext->extent_data.type == EXTENT_TYPE_INLINE) {\n                le = le->Flink;\n                continue;\n            }\n\n            if (ext->offset > last_off)\n                num_extents++;\n\n            if ((ext->offset >> Vcb->sector_shift) <= (uint64_t)in->StartingVcn.QuadPart &&\n                (ext->offset + ext->extent_data.decoded_size) >> Vcb->sector_shift > (uint64_t)in->StartingVcn.QuadPart) {\n                first_ext = ext;\n                first_extent_num = num_extents;\n            }\n\n            num_extents++;\n\n            last_off = ext->offset + ext->extent_data.decoded_size;\n\n            le = le->Flink;\n        }\n\n        if (num_sectors > last_off >> Vcb->sector_shift)\n            num_extents++;\n\n        if (!first_ext) {\n            Status = STATUS_END_OF_FILE;\n            leave;\n        }\n\n        out->ExtentCount = num_extents - first_extent_num;\n        out->StartingVcn.QuadPart = first_ext->offset >> Vcb->sector_shift;\n        outlen -= offsetof(RETRIEVAL_POINTERS_BUFFER, Extents[0]);\n        *retlen = offsetof(RETRIEVAL_POINTERS_BUFFER, Extents[0]);\n\n        le = &first_ext->list_entry;\n        i = 0;\n        last_off = 0;\n\n        while (le != &fcb->extents) {\n            extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n            if (ext->ignore || ext->extent_data.type == EXTENT_TYPE_INLINE) {\n                le = le->Flink;\n                continue;\n            }\n\n            if (ext->offset > last_off) {\n                if (outlen < sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER)) {\n                    Status = STATUS_BUFFER_OVERFLOW;\n                    leave;\n                }\n\n                out->Extents[i].NextVcn.QuadPart = ext->offset >> Vcb->sector_shift;\n                out->Extents[i].Lcn.QuadPart = -1;\n\n                outlen -= sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER);\n                *retlen += sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER);\n                i++;\n            }\n\n            if (outlen < sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER)) {\n                Status = STATUS_BUFFER_OVERFLOW;\n                leave;\n            }\n\n            out->Extents[i].NextVcn.QuadPart = (ext->offset + ext->extent_data.decoded_size) >> Vcb->sector_shift;\n\n            if (ext->extent_data.compression == BTRFS_COMPRESSION_NONE) {\n                EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n                out->Extents[i].Lcn.QuadPart = (ed2->address + ed2->offset) >> Vcb->sector_shift;\n            } else\n                out->Extents[i].Lcn.QuadPart = -1;\n\n            outlen -= sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER);\n            *retlen += sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER);\n            i++;\n\n            le = le->Flink;\n        }\n\n        if (num_sectors << Vcb->sector_shift > last_off) {\n            if (outlen < sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER)) {\n                Status = STATUS_BUFFER_OVERFLOW;\n                leave;\n            }\n\n            out->Extents[i].NextVcn.QuadPart = num_sectors;\n            out->Extents[i].Lcn.QuadPart = -1;\n\n            outlen -= sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER);\n            *retlen += sizeof(LARGE_INTEGER) + sizeof(LARGE_INTEGER);\n        }\n\n        Status = STATUS_SUCCESS;\n    } finally {\n        ExReleaseResourceLite(fcb->Header.Resource);\n    }\n\n    return Status;\n}\n\nstatic NTSTATUS add_csum_sparse_extents(device_extension* Vcb, uint64_t sparse_extents, uint8_t** ptr, bool found, void* hash_ptr) {\n    if (!found) {\n        uint8_t* sector = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.sector_size, ALLOC_TAG);\n\n        if (!sector) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        memset(sector, 0, Vcb->superblock.sector_size);\n\n        get_sector_csum(Vcb, sector, hash_ptr);\n\n        ExFreePool(sector);\n    }\n\n    switch (Vcb->superblock.csum_type) {\n        case CSUM_TYPE_CRC32C: {\n            uint32_t* csum = (uint32_t*)*ptr;\n            uint32_t sparse_hash = *(uint32_t*)hash_ptr;\n\n            for (uint64_t i = 0; i < sparse_extents; i++) {\n                csum[i] = sparse_hash;\n            }\n\n            break;\n        }\n\n        case CSUM_TYPE_XXHASH: {\n            uint64_t* csum = (uint64_t*)*ptr;\n            uint64_t sparse_hash = *(uint64_t*)hash_ptr;\n\n            for (uint64_t i = 0; i < sparse_extents; i++) {\n                csum[i] = sparse_hash;\n            }\n\n            break;\n        }\n\n        case CSUM_TYPE_SHA256:\n        case CSUM_TYPE_BLAKE2: {\n            uint8_t* csum = (uint8_t*)*ptr;\n\n            for (uint64_t i = 0; i < sparse_extents; i++) {\n                memcpy(csum, hash_ptr, 32);\n                csum += 32;\n            }\n\n            break;\n        }\n\n        default:\n            ERR(\"unrecognized hash type %x\\n\", Vcb->superblock.csum_type);\n            return STATUS_INTERNAL_ERROR;\n    }\n\n    *ptr += sparse_extents * Vcb->csum_size;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS get_csum_info(device_extension* Vcb, PFILE_OBJECT FileObject, btrfs_csum_info* buf, ULONG buflen,\n                              ULONG_PTR* retlen, KPROCESSOR_MODE processor_mode) {\n    NTSTATUS Status;\n    fcb* fcb;\n    ccb* ccb;\n\n    TRACE(\"get_csum_info(%p, %p, %p, %lx, %p, %x)\\n\", Vcb, FileObject, buf, buflen, retlen, processor_mode);\n\n    if (!FileObject)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n    ccb = FileObject->FsContext2;\n\n    if (!fcb || !ccb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!buf)\n        return STATUS_INVALID_PARAMETER;\n\n    if (buflen < offsetof(btrfs_csum_info, data[0]))\n        return STATUS_BUFFER_TOO_SMALL;\n\n\n    if (processor_mode == UserMode && !(ccb->access & (FILE_READ_DATA | FILE_WRITE_DATA))) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    try {\n        LIST_ENTRY* le;\n        uint8_t* ptr;\n        uint64_t last_off;\n        uint8_t sparse_hash[MAX_HASH_SIZE];\n        bool sparse_hash_found = false;\n\n        if (fcb->ads) {\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            leave;\n        }\n\n        if (fcb->type == BTRFS_TYPE_DIRECTORY) {\n            Status = STATUS_FILE_IS_A_DIRECTORY;\n            leave;\n        }\n\n        if (fcb->inode_item.flags & BTRFS_INODE_NODATASUM) {\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            leave;\n        }\n\n        buf->csum_type = Vcb->superblock.csum_type;\n        buf->csum_length = Vcb->csum_size;\n\n        le = fcb->extents.Flink;\n        while (le != &fcb->extents) {\n            extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n            if (ext->ignore) {\n                le = le->Flink;\n                continue;\n            }\n\n            if (ext->extent_data.type == EXTENT_TYPE_INLINE) {\n                buf->num_sectors = 0;\n                *retlen = offsetof(btrfs_csum_info, data[0]);\n                Status = STATUS_SUCCESS;\n                leave;\n            }\n\n            le = le->Flink;\n        }\n\n        buf->num_sectors = (fcb->inode_item.st_size + Vcb->superblock.sector_size - 1) >> Vcb->sector_shift;\n\n        if (buflen < offsetof(btrfs_csum_info, data[0]) + (buf->csum_length * buf->num_sectors)) {\n            Status = STATUS_BUFFER_OVERFLOW;\n            *retlen = offsetof(btrfs_csum_info, data[0]);\n            leave;\n        }\n\n        ptr = buf->data;\n        last_off = 0;\n\n        le = fcb->extents.Flink;\n        while (le != &fcb->extents) {\n            extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n            EXTENT_DATA2* ed2;\n\n            if (ext->ignore || ext->extent_data.type == EXTENT_TYPE_INLINE) {\n                le = le->Flink;\n                continue;\n            }\n\n            if (ext->offset > last_off) {\n                uint64_t sparse_extents = (ext->offset - last_off) >> Vcb->sector_shift;\n\n                add_csum_sparse_extents(Vcb, sparse_extents, &ptr, sparse_hash_found, sparse_hash);\n                sparse_hash_found = true;\n            }\n\n            ed2 = (EXTENT_DATA2*)ext->extent_data.data;\n\n            if (ext->extent_data.compression != BTRFS_COMPRESSION_NONE)\n                memset(ptr, 0, (ed2->num_bytes >> Vcb->sector_shift) * Vcb->csum_size); // dummy value for compressed extents\n            else {\n                if (ext->csum)\n                    memcpy(ptr, ext->csum, (ed2->num_bytes >> Vcb->sector_shift) * Vcb->csum_size);\n                else\n                    memset(ptr, 0, (ed2->num_bytes >> Vcb->sector_shift) * Vcb->csum_size);\n\n                ptr += (ed2->num_bytes >> Vcb->sector_shift) * Vcb->csum_size;\n            }\n\n            last_off = ext->offset + ed2->num_bytes;\n\n            le = le->Flink;\n        }\n\n        if (buf->num_sectors > last_off >> Vcb->sector_shift) {\n            uint64_t sparse_extents = buf->num_sectors - (last_off >> Vcb->sector_shift);\n\n            add_csum_sparse_extents(Vcb, sparse_extents, &ptr, sparse_hash_found, sparse_hash);\n        }\n\n        *retlen = offsetof(btrfs_csum_info, data[0]) + (buf->csum_length * buf->num_sectors);\n        Status = STATUS_SUCCESS;\n    } finally {\n        ExReleaseResourceLite(fcb->Header.Resource);\n    }\n\n    return Status;\n}\n\nNTSTATUS fsctl_request(PDEVICE_OBJECT DeviceObject, PIRP* Pirp, uint32_t type) {\n    PIRP Irp = *Pirp;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    NTSTATUS Status;\n\n    if (IrpSp->FileObject && IrpSp->FileObject->FsContext) {\n        device_extension* Vcb = DeviceObject->DeviceExtension;\n\n        if (Vcb->type == VCB_TYPE_FS)\n            FsRtlCheckOplock(fcb_oplock(IrpSp->FileObject->FsContext), Irp, NULL, NULL, NULL);\n    }\n\n    switch (type) {\n        case FSCTL_REQUEST_OPLOCK_LEVEL_1:\n        case FSCTL_REQUEST_OPLOCK_LEVEL_2:\n        case FSCTL_REQUEST_BATCH_OPLOCK:\n        case FSCTL_OPLOCK_BREAK_ACKNOWLEDGE:\n        case FSCTL_OPBATCH_ACK_CLOSE_PENDING:\n        case FSCTL_OPLOCK_BREAK_NOTIFY:\n        case FSCTL_OPLOCK_BREAK_ACK_NO_2:\n        case FSCTL_REQUEST_FILTER_OPLOCK:\n        case FSCTL_REQUEST_OPLOCK:\n            Status = fsctl_oplock(DeviceObject->DeviceExtension, Pirp);\n            break;\n\n        case FSCTL_LOCK_VOLUME:\n            Status = lock_volume(DeviceObject->DeviceExtension, Irp);\n            break;\n\n        case FSCTL_UNLOCK_VOLUME:\n            Status = unlock_volume(DeviceObject->DeviceExtension, Irp);\n            break;\n\n        case FSCTL_DISMOUNT_VOLUME:\n            Status = dismount_volume(DeviceObject->DeviceExtension, false, Irp);\n            break;\n\n        case FSCTL_IS_VOLUME_MOUNTED:\n            Status = is_volume_mounted(DeviceObject->DeviceExtension, Irp);\n            break;\n\n        case FSCTL_IS_PATHNAME_VALID:\n            WARN(\"STUB: FSCTL_IS_PATHNAME_VALID\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_MARK_VOLUME_DIRTY:\n            WARN(\"STUB: FSCTL_MARK_VOLUME_DIRTY\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_QUERY_RETRIEVAL_POINTERS:\n            WARN(\"STUB: FSCTL_QUERY_RETRIEVAL_POINTERS\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_GET_COMPRESSION:\n            Status = get_compression(Irp);\n            break;\n\n        case FSCTL_SET_COMPRESSION:\n            Status = set_compression(Irp);\n            break;\n\n        case FSCTL_SET_BOOTLOADER_ACCESSED:\n            WARN(\"STUB: FSCTL_SET_BOOTLOADER_ACCESSED\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_INVALIDATE_VOLUMES:\n            Status = invalidate_volumes(Irp);\n            break;\n\n        case FSCTL_QUERY_FAT_BPB:\n            WARN(\"STUB: FSCTL_QUERY_FAT_BPB\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_FILESYSTEM_GET_STATISTICS:\n            Status = fs_get_statistics(Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, &Irp->IoStatus.Information);\n            break;\n\n        case FSCTL_GET_NTFS_VOLUME_DATA:\n            WARN(\"STUB: FSCTL_GET_NTFS_VOLUME_DATA\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_GET_NTFS_FILE_RECORD:\n            WARN(\"STUB: FSCTL_GET_NTFS_FILE_RECORD\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_GET_VOLUME_BITMAP:\n            WARN(\"STUB: FSCTL_GET_VOLUME_BITMAP\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_GET_RETRIEVAL_POINTERS:\n            Status = get_retrieval_pointers(DeviceObject->DeviceExtension, IrpSp->FileObject,\n                                            IrpSp->Parameters.FileSystemControl.Type3InputBuffer,\n                                            IrpSp->Parameters.FileSystemControl.InputBufferLength,\n                                            Irp->UserBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength,\n                                            &Irp->IoStatus.Information);\n            break;\n\n        case FSCTL_MOVE_FILE:\n            WARN(\"STUB: FSCTL_MOVE_FILE\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_IS_VOLUME_DIRTY:\n            Status = is_volume_dirty(DeviceObject->DeviceExtension, Irp);\n            break;\n\n        case FSCTL_ALLOW_EXTENDED_DASD_IO:\n            Status = allow_extended_dasd_io(DeviceObject->DeviceExtension, IrpSp->FileObject);\n            break;\n\n        case FSCTL_FIND_FILES_BY_SID:\n            WARN(\"STUB: FSCTL_FIND_FILES_BY_SID\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SET_OBJECT_ID:\n            WARN(\"STUB: FSCTL_SET_OBJECT_ID\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_GET_OBJECT_ID:\n            Status = get_object_id(IrpSp->FileObject, Irp->UserBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength,\n                                   &Irp->IoStatus.Information);\n            break;\n\n        case FSCTL_DELETE_OBJECT_ID:\n            WARN(\"STUB: FSCTL_DELETE_OBJECT_ID\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SET_REPARSE_POINT:\n            Status = set_reparse_point(Irp);\n            break;\n\n        case FSCTL_GET_REPARSE_POINT:\n            Status = get_reparse_point(IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer,\n                                       IrpSp->Parameters.FileSystemControl.OutputBufferLength, &Irp->IoStatus.Information);\n            break;\n\n        case FSCTL_DELETE_REPARSE_POINT:\n            Status = delete_reparse_point(Irp);\n            break;\n\n        case FSCTL_ENUM_USN_DATA:\n            WARN(\"STUB: FSCTL_ENUM_USN_DATA\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SECURITY_ID_CHECK:\n            WARN(\"STUB: FSCTL_SECURITY_ID_CHECK\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_READ_USN_JOURNAL:\n            WARN(\"STUB: FSCTL_READ_USN_JOURNAL\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SET_OBJECT_ID_EXTENDED:\n            WARN(\"STUB: FSCTL_SET_OBJECT_ID_EXTENDED\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_CREATE_OR_GET_OBJECT_ID:\n            Status = get_object_id(IrpSp->FileObject, Irp->UserBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength,\n                                   &Irp->IoStatus.Information);\n            break;\n\n        case FSCTL_SET_SPARSE:\n            Status = set_sparse(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer,\n                                IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp);\n            break;\n\n        case FSCTL_SET_ZERO_DATA:\n            Status = set_zero_data(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer,\n                                   IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp);\n            break;\n\n        case FSCTL_QUERY_ALLOCATED_RANGES:\n            Status = query_ranges(IrpSp->FileObject, IrpSp->Parameters.FileSystemControl.Type3InputBuffer,\n                                  IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->UserBuffer,\n                                  IrpSp->Parameters.FileSystemControl.OutputBufferLength, &Irp->IoStatus.Information);\n            break;\n\n        case FSCTL_ENABLE_UPGRADE:\n            WARN(\"STUB: FSCTL_ENABLE_UPGRADE\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SET_ENCRYPTION:\n            WARN(\"STUB: FSCTL_SET_ENCRYPTION\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_ENCRYPTION_FSCTL_IO:\n            WARN(\"STUB: FSCTL_ENCRYPTION_FSCTL_IO\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_WRITE_RAW_ENCRYPTED:\n            WARN(\"STUB: FSCTL_WRITE_RAW_ENCRYPTED\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_READ_RAW_ENCRYPTED:\n            WARN(\"STUB: FSCTL_READ_RAW_ENCRYPTED\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_CREATE_USN_JOURNAL:\n            WARN(\"STUB: FSCTL_CREATE_USN_JOURNAL\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_READ_FILE_USN_DATA:\n            WARN(\"STUB: FSCTL_READ_FILE_USN_DATA\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_WRITE_USN_CLOSE_RECORD:\n            WARN(\"STUB: FSCTL_WRITE_USN_CLOSE_RECORD\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_EXTEND_VOLUME:\n            WARN(\"STUB: FSCTL_EXTEND_VOLUME\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_QUERY_USN_JOURNAL:\n            WARN(\"STUB: FSCTL_QUERY_USN_JOURNAL\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_DELETE_USN_JOURNAL:\n            WARN(\"STUB: FSCTL_DELETE_USN_JOURNAL\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_MARK_HANDLE:\n            WARN(\"STUB: FSCTL_MARK_HANDLE\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SIS_COPYFILE:\n            WARN(\"STUB: FSCTL_SIS_COPYFILE\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SIS_LINK_FILES:\n            WARN(\"STUB: FSCTL_SIS_LINK_FILES\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_RECALL_FILE:\n            WARN(\"STUB: FSCTL_RECALL_FILE\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_READ_FROM_PLEX:\n            WARN(\"STUB: FSCTL_READ_FROM_PLEX\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_FILE_PREFETCH:\n            WARN(\"STUB: FSCTL_FILE_PREFETCH\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n#if _WIN32_WINNT >= 0x0600\n        case FSCTL_MAKE_MEDIA_COMPATIBLE:\n            WARN(\"STUB: FSCTL_MAKE_MEDIA_COMPATIBLE\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SET_DEFECT_MANAGEMENT:\n            WARN(\"STUB: FSCTL_SET_DEFECT_MANAGEMENT\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_QUERY_SPARING_INFO:\n            WARN(\"STUB: FSCTL_QUERY_SPARING_INFO\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_QUERY_ON_DISK_VOLUME_INFO:\n            WARN(\"STUB: FSCTL_QUERY_ON_DISK_VOLUME_INFO\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SET_VOLUME_COMPRESSION_STATE:\n            WARN(\"STUB: FSCTL_SET_VOLUME_COMPRESSION_STATE\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_MODIFY_RM:\n            WARN(\"STUB: FSCTL_TXFS_MODIFY_RM\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_QUERY_RM_INFORMATION:\n            WARN(\"STUB: FSCTL_TXFS_QUERY_RM_INFORMATION\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_ROLLFORWARD_REDO:\n            WARN(\"STUB: FSCTL_TXFS_ROLLFORWARD_REDO\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_ROLLFORWARD_UNDO:\n            WARN(\"STUB: FSCTL_TXFS_ROLLFORWARD_UNDO\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_START_RM:\n            WARN(\"STUB: FSCTL_TXFS_START_RM\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_SHUTDOWN_RM:\n            WARN(\"STUB: FSCTL_TXFS_SHUTDOWN_RM\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_READ_BACKUP_INFORMATION:\n            WARN(\"STUB: FSCTL_TXFS_READ_BACKUP_INFORMATION\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_WRITE_BACKUP_INFORMATION:\n            WARN(\"STUB: FSCTL_TXFS_WRITE_BACKUP_INFORMATION\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_CREATE_SECONDARY_RM:\n            WARN(\"STUB: FSCTL_TXFS_CREATE_SECONDARY_RM\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_GET_METADATA_INFO:\n            WARN(\"STUB: FSCTL_TXFS_GET_METADATA_INFO\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_GET_TRANSACTED_VERSION:\n            WARN(\"STUB: FSCTL_TXFS_GET_TRANSACTED_VERSION\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_SAVEPOINT_INFORMATION:\n            WARN(\"STUB: FSCTL_TXFS_SAVEPOINT_INFORMATION\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_CREATE_MINIVERSION:\n            WARN(\"STUB: FSCTL_TXFS_CREATE_MINIVERSION\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_TRANSACTION_ACTIVE:\n            WARN(\"STUB: FSCTL_TXFS_TRANSACTION_ACTIVE\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SET_ZERO_ON_DEALLOCATION:\n            WARN(\"STUB: FSCTL_SET_ZERO_ON_DEALLOCATION\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SET_REPAIR:\n            WARN(\"STUB: FSCTL_SET_REPAIR\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_GET_REPAIR:\n            WARN(\"STUB: FSCTL_GET_REPAIR\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_WAIT_FOR_REPAIR:\n            WARN(\"STUB: FSCTL_WAIT_FOR_REPAIR\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_INITIATE_REPAIR:\n            WARN(\"STUB: FSCTL_INITIATE_REPAIR\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_CSC_INTERNAL:\n            WARN(\"STUB: FSCTL_CSC_INTERNAL\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SHRINK_VOLUME:\n            WARN(\"STUB: FSCTL_SHRINK_VOLUME\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_SET_SHORT_NAME_BEHAVIOR:\n            WARN(\"STUB: FSCTL_SET_SHORT_NAME_BEHAVIOR\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_DFSR_SET_GHOST_HANDLE_STATE:\n            WARN(\"STUB: FSCTL_DFSR_SET_GHOST_HANDLE_STATE\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_LIST_TRANSACTION_LOCKED_FILES:\n            WARN(\"STUB: FSCTL_TXFS_LIST_TRANSACTION_LOCKED_FILES\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_LIST_TRANSACTIONS:\n            WARN(\"STUB: FSCTL_TXFS_LIST_TRANSACTIONS\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_QUERY_PAGEFILE_ENCRYPTION:\n            WARN(\"STUB: FSCTL_QUERY_PAGEFILE_ENCRYPTION\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_RESET_VOLUME_ALLOCATION_HINTS:\n            WARN(\"STUB: FSCTL_RESET_VOLUME_ALLOCATION_HINTS\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_TXFS_READ_BACKUP_INFORMATION2:\n            WARN(\"STUB: FSCTL_TXFS_READ_BACKUP_INFORMATION2\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_CSV_CONTROL:\n            WARN(\"STUB: FSCTL_CSV_CONTROL\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n#endif\n        // TRACE rather than WARN because Windows 10 spams this undocumented fsctl\n        case FSCTL_QUERY_VOLUME_CONTAINER_STATE:\n            TRACE(\"STUB: FSCTL_QUERY_VOLUME_CONTAINER_STATE\\n\");\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n\n        case FSCTL_GET_INTEGRITY_INFORMATION:\n            Status = get_integrity_information(DeviceObject->DeviceExtension, IrpSp->FileObject, map_user_buffer(Irp, NormalPagePriority),\n                                               IrpSp->Parameters.FileSystemControl.OutputBufferLength);\n            break;\n\n        case FSCTL_SET_INTEGRITY_INFORMATION:\n            Status = set_integrity_information(IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength);\n            break;\n\n        case FSCTL_DUPLICATE_EXTENTS_TO_FILE:\n            Status = duplicate_extents(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer,\n                                       IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp);\n            break;\n\n        case FSCTL_BTRFS_GET_FILE_IDS:\n            Status = get_file_ids(IrpSp->FileObject, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength);\n            break;\n\n        case FSCTL_BTRFS_CREATE_SUBVOL:\n            Status = create_subvol(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp);\n            break;\n\n        case FSCTL_BTRFS_CREATE_SNAPSHOT:\n            Status = create_snapshot(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp);\n            break;\n\n        case FSCTL_BTRFS_GET_INODE_INFO:\n            Status = get_inode_info(IrpSp->FileObject, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength);\n            break;\n\n        case FSCTL_BTRFS_SET_INODE_INFO:\n            Status = set_inode_info(IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp);\n            break;\n\n        case FSCTL_BTRFS_GET_DEVICES:\n            Status = get_devices(DeviceObject->DeviceExtension, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength);\n            break;\n\n        case FSCTL_BTRFS_GET_USAGE:\n            Status = get_usage(DeviceObject->DeviceExtension, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength, Irp);\n            break;\n\n        case FSCTL_BTRFS_START_BALANCE:\n            Status = start_balance(DeviceObject->DeviceExtension, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_QUERY_BALANCE:\n            Status = query_balance(DeviceObject->DeviceExtension, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength);\n            break;\n\n        case FSCTL_BTRFS_PAUSE_BALANCE:\n            Status = pause_balance(DeviceObject->DeviceExtension, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_RESUME_BALANCE:\n            Status = resume_balance(DeviceObject->DeviceExtension, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_STOP_BALANCE:\n            Status = stop_balance(DeviceObject->DeviceExtension, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_ADD_DEVICE:\n            Status = add_device(DeviceObject->DeviceExtension, Irp, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_REMOVE_DEVICE:\n            Status = remove_device(DeviceObject->DeviceExtension, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_GET_UUID:\n            Status = query_uuid(DeviceObject->DeviceExtension, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength);\n            break;\n\n        case FSCTL_BTRFS_START_SCRUB:\n            Status = start_scrub(DeviceObject->DeviceExtension, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_QUERY_SCRUB:\n            Status = query_scrub(DeviceObject->DeviceExtension, Irp->RequestorMode, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength);\n            break;\n\n        case FSCTL_BTRFS_PAUSE_SCRUB:\n            Status = pause_scrub(DeviceObject->DeviceExtension, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_RESUME_SCRUB:\n            Status = resume_scrub(DeviceObject->DeviceExtension, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_STOP_SCRUB:\n            Status = stop_scrub(DeviceObject->DeviceExtension, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_RESET_STATS:\n            Status = reset_stats(DeviceObject->DeviceExtension, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_MKNOD:\n            Status = mknod(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp);\n            break;\n\n        case FSCTL_BTRFS_RECEIVED_SUBVOL:\n            Status = recvd_subvol(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer,\n                                  IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_GET_XATTRS:\n            Status = fsctl_get_xattrs(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->UserBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_SET_XATTR:\n            Status = fsctl_set_xattr(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer,\n                                     IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp);\n            break;\n\n        case FSCTL_BTRFS_RESERVE_SUBVOL:\n            Status = reserve_subvol(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp);\n            break;\n\n        case FSCTL_BTRFS_FIND_SUBVOL:\n            Status = find_subvol(DeviceObject->DeviceExtension, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength,\n                                 Irp->UserBuffer, IrpSp->Parameters.FileSystemControl.OutputBufferLength, Irp);\n            break;\n\n        case FSCTL_BTRFS_SEND_SUBVOL:\n            Status = send_subvol(DeviceObject->DeviceExtension, Irp->AssociatedIrp.SystemBuffer, IrpSp->Parameters.FileSystemControl.InputBufferLength,\n                                 IrpSp->FileObject, Irp);\n            break;\n\n        case FSCTL_BTRFS_READ_SEND_BUFFER:\n            Status = read_send_buffer(DeviceObject->DeviceExtension, IrpSp->FileObject, map_user_buffer(Irp, NormalPagePriority), IrpSp->Parameters.FileSystemControl.OutputBufferLength,\n                                      &Irp->IoStatus.Information, Irp->RequestorMode);\n            break;\n\n        case FSCTL_BTRFS_RESIZE:\n            Status = resize_device(DeviceObject->DeviceExtension, Irp->AssociatedIrp.SystemBuffer,\n                                   IrpSp->Parameters.FileSystemControl.InputBufferLength, Irp);\n            break;\n\n        case FSCTL_BTRFS_GET_CSUM_INFO:\n            Status = get_csum_info(DeviceObject->DeviceExtension, IrpSp->FileObject, Irp->AssociatedIrp.SystemBuffer,\n                                   IrpSp->Parameters.FileSystemControl.OutputBufferLength, &Irp->IoStatus.Information,\n                                   Irp->RequestorMode);\n            break;\n\n        default:\n            WARN(\"unknown control code %lx (DeviceType = %lx, Access = %lx, Function = %lx, Method = %lx)\\n\",\n                          IrpSp->Parameters.FileSystemControl.FsControlCode, (IrpSp->Parameters.FileSystemControl.FsControlCode & 0xff0000) >> 16,\n                          (IrpSp->Parameters.FileSystemControl.FsControlCode & 0xc000) >> 14, (IrpSp->Parameters.FileSystemControl.FsControlCode & 0x3ffc) >> 2,\n                          IrpSp->Parameters.FileSystemControl.FsControlCode & 0x3);\n            Status = STATUS_INVALID_DEVICE_REQUEST;\n            break;\n    }\n\n    return Status;\n}\n"
  },
  {
    "path": "src/fsrtl.c",
    "content": "/*\n * PROJECT:         ReactOS Kernel - Vista+ APIs\n * LICENSE:         LGPL-2.1-or-later (https://spdx.org/licenses/LGPL-2.1-or-later)\n * FILE:            lib/drivers/ntoskrnl_vista/fsrtl.c\n * PURPOSE:         FsRtl functions of Vista+\n * PROGRAMMERS:     Pierre Schweitzer <pierre@reactos.org>\n */\n\n#include <ntifs.h>\n#include <ntdef.h>\n\nFORCEINLINE\nBOOLEAN\nIsNullGuid(IN PGUID Guid)\n{\n    if (Guid->Data1 == 0 && Guid->Data2 == 0 && Guid->Data3 == 0 &&\n        ((ULONG *)Guid->Data4)[0] == 0 && ((ULONG *)Guid->Data4)[1] == 0)\n    {\n        return TRUE;\n    }\n\n    return FALSE;\n}\n\nFORCEINLINE\nBOOLEAN\nIsEven(IN USHORT Digit)\n{\n    return ((Digit & 1) != 1);\n}\n\nNTSTATUS __stdcall compat_FsRtlValidateReparsePointBuffer(IN ULONG BufferLength, IN PREPARSE_DATA_BUFFER ReparseBuffer)\n{\n    USHORT DataLength;\n    ULONG ReparseTag;\n    PREPARSE_GUID_DATA_BUFFER GuidBuffer;\n\n    /* Validate data size range */\n    if (BufferLength < REPARSE_DATA_BUFFER_HEADER_SIZE || BufferLength > MAXIMUM_REPARSE_DATA_BUFFER_SIZE)\n    {\n        return STATUS_IO_REPARSE_DATA_INVALID;\n    }\n\n    GuidBuffer = (PREPARSE_GUID_DATA_BUFFER)ReparseBuffer;\n    DataLength = ReparseBuffer->ReparseDataLength;\n    ReparseTag = ReparseBuffer->ReparseTag;\n\n    /* Validate size consistency */\n    if (DataLength + REPARSE_DATA_BUFFER_HEADER_SIZE != BufferLength && DataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE != BufferLength)\n    {\n        return STATUS_IO_REPARSE_DATA_INVALID;\n    }\n\n    /* REPARSE_DATA_BUFFER is reserved for MS tags */\n    if (DataLength + REPARSE_DATA_BUFFER_HEADER_SIZE == BufferLength && !IsReparseTagMicrosoft(ReparseTag))\n    {\n        return STATUS_IO_REPARSE_DATA_INVALID;\n    }\n\n    /* If that a GUID data buffer, its GUID cannot be null, and it cannot contain a MS tag */\n    if (DataLength + REPARSE_GUID_DATA_BUFFER_HEADER_SIZE == BufferLength && ((!IsReparseTagMicrosoft(ReparseTag)\n        && IsNullGuid(&GuidBuffer->ReparseGuid)) || (ReparseTag == IO_REPARSE_TAG_MOUNT_POINT || ReparseTag == IO_REPARSE_TAG_SYMLINK)))\n    {\n        return STATUS_IO_REPARSE_DATA_INVALID;\n    }\n\n    /* Check the data for MS non reserved tags */\n    if (!(ReparseTag & 0xFFF0000) && ReparseTag != IO_REPARSE_TAG_RESERVED_ZERO && ReparseTag != IO_REPARSE_TAG_RESERVED_ONE)\n    {\n        /* If that's a mount point, validate the MountPointReparseBuffer branch */\n        if (ReparseTag == IO_REPARSE_TAG_MOUNT_POINT)\n        {\n            /* We need information */\n            if (DataLength >= REPARSE_DATA_BUFFER_HEADER_SIZE)\n            {\n                /* Substitue must be the first in row */\n                if (!ReparseBuffer->MountPointReparseBuffer.SubstituteNameOffset)\n                {\n                    /* Substitude must be null-terminated */\n                    if (ReparseBuffer->MountPointReparseBuffer.PrintNameOffset == ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength + sizeof(UNICODE_NULL))\n                    {\n                        /* There must just be the Offset/Length fields + buffer + 2 null chars */\n                        if (DataLength == ReparseBuffer->MountPointReparseBuffer.PrintNameLength + ReparseBuffer->MountPointReparseBuffer.SubstituteNameLength + (FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) - FIELD_OFFSET(REPARSE_DATA_BUFFER, MountPointReparseBuffer.SubstituteNameOffset)) + 2 * sizeof(UNICODE_NULL))\n                        {\n                            return STATUS_SUCCESS;\n                        }\n                    }\n                }\n            }\n        }\n        else\n        {\n#define FIELDS_SIZE (FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) - FIELD_OFFSET(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.SubstituteNameOffset))\n\n            /* If that's not a symlink, accept the MS tag as it */\n            if (ReparseTag != IO_REPARSE_TAG_SYMLINK)\n            {\n                return STATUS_SUCCESS;\n            }\n\n            /* We need information */\n            if (DataLength >= FIELDS_SIZE)\n            {\n                /* Validate lengths */\n                if (ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength && ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameLength)\n                {\n                    /* Validate unicode strings */\n                    if (IsEven(ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength) && IsEven(ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameLength) &&\n                        IsEven(ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameOffset) && IsEven(ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameOffset))\n                    {\n                        if ((DataLength + REPARSE_DATA_BUFFER_HEADER_SIZE >= ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameOffset + ReparseBuffer->SymbolicLinkReparseBuffer.SubstituteNameLength + FIELDS_SIZE + REPARSE_DATA_BUFFER_HEADER_SIZE)\n                            && (DataLength + REPARSE_DATA_BUFFER_HEADER_SIZE >= ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameLength + ReparseBuffer->SymbolicLinkReparseBuffer.PrintNameOffset + FIELDS_SIZE + REPARSE_DATA_BUFFER_HEADER_SIZE))\n                        {\n                            return STATUS_SUCCESS;\n                        }\n                    }\n                }\n            }\n#undef FIELDS_SIZE\n        }\n\n        return STATUS_IO_REPARSE_DATA_INVALID;\n    }\n\n    return STATUS_IO_REPARSE_TAG_INVALID;\n}\n"
  },
  {
    "path": "src/galois.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n\nstatic const uint8_t glog[] = {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26,\n                             0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0,\n                             0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23,\n                             0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1,\n                             0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0,\n                             0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2,\n                             0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce,\n                             0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc,\n                             0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54,\n                             0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73,\n                             0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff,\n                             0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41,\n                             0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6,\n                             0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09,\n                             0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16,\n                             0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x01};\n\nstatic const uint8_t gilog[] = {0x00, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b,\n                              0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71,\n                              0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45,\n                              0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6,\n                              0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88,\n                              0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40,\n                              0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d,\n                              0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57,\n                              0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18,\n                              0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e,\n                              0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61,\n                              0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2,\n                              0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6,\n                              0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a,\n                              0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7,\n                              0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf};\n\n// divides the bytes in data by 2^div\nvoid galois_divpower(uint8_t* data, uint8_t div, uint32_t len) {\n    while (len > 0) {\n        if (data[0] != 0) {\n            if (gilog[data[0]] <= div)\n                data[0] = glog[(gilog[data[0]] + (255 - div)) % 255];\n            else\n                data[0] = glog[(gilog[data[0]] - div) % 255];\n        }\n\n        data++;\n        len--;\n    }\n}\n\nuint8_t gpow2(uint8_t e) {\n    return glog[e%255];\n}\n\nuint8_t gmul(uint8_t a, uint8_t b) {\n    if (a == 0 || b == 0)\n        return 0;\n    else\n        return glog[(gilog[a] + gilog[b]) % 255];\n}\n\nuint8_t gdiv(uint8_t a, uint8_t b) {\n    if (b == 0) {\n        return 0xff; // shouldn't happen\n    } else if (a == 0) {\n        return 0;\n    } else {\n        if (gilog[a] >= gilog[b])\n            return glog[(gilog[a] - gilog[b]) % 255];\n        else\n            return glog[255-((gilog[b] - gilog[a]) % 255)];\n    }\n}\n\n// The code from the following functions is derived from the paper\n// \"The mathematics of RAID-6\", by H. Peter Anvin.\n// https://www.kernel.org/pub/linux/kernel/people/hpa/raid6.pdf\n\n#if defined(_AMD64_) || defined(_ARM64_)\n__inline static uint64_t galois_double_mask64(uint64_t v) {\n    v &= 0x8080808080808080;\n    return (v << 1) - (v >> 7);\n}\n#else\n__inline static uint32_t galois_double_mask32(uint32_t v) {\n    v &= 0x80808080;\n    return (v << 1) - (v >> 7);\n}\n#endif\n\nvoid galois_double(uint8_t* data, uint32_t len) {\n    // FIXME - SIMD?\n\n#if defined(_AMD64_) || defined(_ARM64_)\n    while (len > sizeof(uint64_t)) {\n        uint64_t v = *((uint64_t*)data), vv;\n\n        vv = (v << 1) & 0xfefefefefefefefe;\n        vv ^= galois_double_mask64(v) & 0x1d1d1d1d1d1d1d1d;\n        *((uint64_t*)data) = vv;\n\n        data += sizeof(uint64_t);\n        len -= sizeof(uint64_t);\n    }\n#else\n    while (len > sizeof(uint32_t)) {\n        uint32_t v = *((uint32_t*)data), vv;\n\n        vv = (v << 1) & 0xfefefefe;\n        vv ^= galois_double_mask32(v) & 0x1d1d1d1d;\n        *((uint32_t*)data) = vv;\n\n        data += sizeof(uint32_t);\n        len -= sizeof(uint32_t);\n    }\n#endif\n\n    while (len > 0) {\n        data[0] = (data[0] << 1) ^ ((data[0] & 0x80) ? 0x1d : 0);\n        data++;\n        len--;\n    }\n}\n"
  },
  {
    "path": "src/mkbtrfs/mkbtrfs.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include <windef.h>\n#include <winbase.h>\n#include <winternl.h>\n#include <devioctl.h>\n#include <ntdddisk.h>\n#include <stdio.h>\n#include <stdarg.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <stringapiset.h>\n#include \"resource.h\"\n#include \"../btrfs.h\"\n\n#define UBTRFS_DLL L\"ubtrfs.dll\"\n\n// These are undocumented, and what comes from format.exe\ntypedef struct {\n    void* table;\n    void* unk1;\n    WCHAR* string;\n} DSTRING;\n\ntypedef struct {\n    void* table;\n} STREAM_MESSAGE;\n\n#define FORMAT_FLAG_QUICK_FORMAT        0x00000001\n#define FORMAT_FLAG_UNKNOWN1            0x00000002\n#define FORMAT_FLAG_DISMOUNT_FIRST      0x00000004\n#define FORMAT_FLAG_UNKNOWN2            0x00000040\n#define FORMAT_FLAG_LARGE_RECORDS       0x00000100\n#define FORMAT_FLAG_INTEGRITY_DISABLE   0x00000100\n\ntypedef struct {\n    uint16_t unk1;\n    uint16_t unk2;\n    uint32_t flags;\n    DSTRING* label;\n} options;\n\ntypedef BOOL (__stdcall* pFormatEx)(DSTRING* root, STREAM_MESSAGE* message, options* opts, uint32_t unk1);\ntypedef void (__stdcall* pSetSizes)(ULONG sector, ULONG node);\ntypedef void (__stdcall* pSetIncompatFlags)(uint64_t incompat_flags);\ntypedef void (__stdcall* pSetCompatROFlags)(uint64_t compat_ro_flags);\ntypedef void (__stdcall* pSetCsumType)(uint16_t csum_type);\n\nstatic void print_string(FILE* f, int resid, ...) {\n    WCHAR s[1024], t[1024];\n    va_list ap;\n\n    if (!LoadStringW(GetModuleHandle(NULL), resid, s, sizeof(s) / sizeof(WCHAR))) {\n        fprintf(stderr, \"LoadString failed (error %lu)\\n\", GetLastError());\n        return;\n    }\n\n    va_start(ap, resid);\n    vswprintf(t, sizeof(t) / sizeof(WCHAR), s, ap);\n\n    fwprintf(f, L\"%s\\n\", t);\n\n    va_end(ap);\n}\n\nint main(int argc, char** argv) {\n    HMODULE ubtrfs;\n    bool baddrive = false, success;\n    char *ds = NULL, *labels = NULL;\n    WCHAR dsw[10], labelw[255], dsw2[255];\n    UNICODE_STRING drive, label;\n    pFormatEx FormatEx;\n    options opts;\n    DSTRING labelds, rootds;\n    ULONG sector_size = 0, node_size = 0;\n    int i;\n    bool invalid_args = false;\n    uint64_t incompat_flags = BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF | BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA |\n                              BTRFS_INCOMPAT_FLAGS_NO_HOLES;\n    uint64_t compat_ro_flags = BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE;\n    uint16_t csum_type = CSUM_TYPE_CRC32C;\n    pSetIncompatFlags SetIncompatFlags;\n    pSetCsumType SetCsumType;\n\n    if (argc >= 2) {\n        for (i = 1; i < argc; i++) {\n            if (argv[i][0] == '/' && argv[i][1] != 0) {\n                char cmd[255], *colon;\n\n                colon = strstr(argv[i], \":\");\n\n                if (colon) {\n                    memcpy(cmd, argv[i] + 1, colon - argv[i] - 1);\n                    cmd[colon - argv[i] - 1] = 0;\n                } else\n                    strcpy(cmd, argv[i] + 1);\n\n                if (!_stricmp(cmd, \"sectorsize\")) {\n                    if (!colon || colon[1] == 0) {\n                        print_string(stdout, IDS_NO_SECTOR_SIZE);\n                        invalid_args = true;\n                        break;\n                    } else\n                        sector_size = atoi(&colon[1]);\n                } else if (!_stricmp(cmd, \"nodesize\")) {\n                    if (!colon || colon[1] == 0) {\n                        print_string(stdout, IDS_NO_NODE_SIZE);\n                        invalid_args = true;\n                        break;\n                    } else\n                        node_size = atoi(&colon[1]);\n                } else if (!_stricmp(cmd, \"csum\")) {\n                    char* v;\n\n                    if (!colon || colon[1] == 0) {\n                        print_string(stdout, IDS_NO_CSUM);\n                        invalid_args = true;\n                        break;\n                    }\n\n                    v = &colon[1];\n\n                    if (!_stricmp(v, \"crc32c\"))\n                        csum_type = CSUM_TYPE_CRC32C;\n                    else if (!_stricmp(v, \"xxhash\"))\n                        csum_type = CSUM_TYPE_XXHASH;\n                    else if (!_stricmp(v, \"sha256\"))\n                        csum_type = CSUM_TYPE_SHA256;\n                    else if (!_stricmp(v, \"blake2\"))\n                        csum_type = CSUM_TYPE_BLAKE2;\n                    else {\n                        print_string(stdout, IDS_INVALID_CSUM_TYPE);\n                        invalid_args = true;\n                        break;\n                    }\n                } else if (!_stricmp(cmd, \"mixed\"))\n                    incompat_flags |= BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS;\n                else if (!_stricmp(cmd, \"notmixed\"))\n                    incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS;\n                else if (!_stricmp(cmd, \"extiref\"))\n                    incompat_flags |= BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF;\n                else if (!_stricmp(cmd, \"notextiref\"))\n                    incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF;\n                else if (!_stricmp(cmd, \"skinnymetadata\"))\n                    incompat_flags |= BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA;\n                else if (!_stricmp(cmd, \"notskinnymetadata\"))\n                    incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA;\n                else if (!_stricmp(cmd, \"noholes\"))\n                    incompat_flags |= BTRFS_INCOMPAT_FLAGS_NO_HOLES;\n                else if (!_stricmp(cmd, \"notnoholes\"))\n                    incompat_flags &= ~BTRFS_INCOMPAT_FLAGS_NO_HOLES;\n                else if (!_stricmp(cmd, \"freespacetree\"))\n                    compat_ro_flags |= BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE;\n                else if (!_stricmp(cmd, \"notfreespacetree\"))\n                    compat_ro_flags &= ~BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE;\n                else if (!_stricmp(cmd, \"blockgrouptree\"))\n                    compat_ro_flags |= BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE;\n                else if (!_stricmp(cmd, \"notblockgrouptree\"))\n                    compat_ro_flags &= ~BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE;\n                else {\n                    print_string(stdout, IDS_UNKNOWN_ARG);\n                    invalid_args = true;\n                    break;\n                }\n            } else {\n                if (!ds)\n                    ds = argv[i];\n                else if (!labels)\n                    labels = argv[i];\n                else {\n                    print_string(stdout, IDS_TOO_MANY_ARGS);\n                    invalid_args = true;\n                    break;\n                }\n            }\n        }\n    } else\n        invalid_args = true;\n\n    if (!ds)\n        invalid_args = true;\n\n    if (invalid_args) {\n        char* c = argv[0] + strlen(argv[0]) - 1;\n        char* fn = NULL;\n        WCHAR fnw[MAX_PATH], *s;\n        int ret;\n\n        while (c > argv[0]) {\n            if (*c == '/' || *c == '\\\\') {\n                fn = c + 1;\n                break;\n            }\n            c--;\n        }\n\n        if (!fn)\n            fn = argv[0];\n\n        if (!MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, fn, -1, fnw, sizeof(fnw) / sizeof(WCHAR))) {\n            print_string(stderr, IDS_MULTIBYTE_FAILED, GetLastError());\n            return 1;\n        }\n\n        print_string(stdout, IDS_USAGE, fnw);\n\n        ret = LoadStringW(GetModuleHandle(NULL), IDS_USAGE2, (WCHAR*)&s, 0);\n\n        if (!ret) {\n            fprintf(stderr, \"LoadString failed (error %lu)\\n\", GetLastError());\n            return 0;\n        }\n\n        fwprintf(stdout, L\"%.*s\\n\", ret, s);\n\n        return 0;\n    }\n\n    if (ds[0] != '\\\\') {\n        if ((ds[0] >= 'A' && ds[0] <= 'Z') || (ds[0] >= 'a' && ds[0] <= 'z')) {\n            if (ds[1] == 0 || (ds[1] == ':' && ds[2] == 0) || (ds[1] == ':' && ds[2] == '\\\\' && ds[3] == 0)) {\n                dsw[0] = '\\\\';\n                dsw[1] = '?';\n                dsw[2] = '?';\n                dsw[3] = '\\\\';\n                dsw[4] = ds[0];\n                dsw[5] = ':';\n                dsw[6] = 0;\n\n                drive.Buffer = dsw;\n                drive.Length = drive.MaximumLength = (USHORT)(wcslen(drive.Buffer) * sizeof(WCHAR));\n            } else\n                baddrive = true;\n        } else\n            baddrive = true;\n    } else {\n        if (!MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, ds, -1, dsw2, sizeof(dsw2) / sizeof(WCHAR))) {\n            print_string(stderr, IDS_MULTIBYTE_FAILED, GetLastError());\n            return 1;\n        }\n\n        drive.Buffer = dsw2;\n        drive.Length = drive.MaximumLength = (USHORT)(wcslen(drive.Buffer) * sizeof(WCHAR));\n    }\n\n    if (baddrive) {\n        if (!MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, ds, -1, dsw2, sizeof(dsw2) / sizeof(WCHAR))) {\n            print_string(stderr, IDS_MULTIBYTE_FAILED, GetLastError());\n            return 1;\n        }\n\n        print_string(stderr, IDS_CANT_RECOGNIZE_DRIVE, dsw2);\n        return 1;\n    }\n\n    if (labels) {\n        if (!MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, labels, -1, labelw, sizeof(labelw) / sizeof(WCHAR))) {\n            print_string(stderr, IDS_MULTIBYTE_FAILED, GetLastError());\n            return 1;\n        }\n\n        label.Buffer = labelw;\n        label.Length = label.MaximumLength = (USHORT)(wcslen(labelw) * sizeof(WCHAR));\n    } else {\n        label.Buffer = NULL;\n        label.Length = label.MaximumLength = 0;\n    }\n\n    ubtrfs = LoadLibraryW(UBTRFS_DLL);\n\n    if (!ubtrfs) {\n#if defined(__i386) || defined(_M_IX86)\n        ubtrfs = LoadLibraryW(L\"Debug\\\\x86\\\\ubtrfs.dll\");\n#elif defined(__x86_64__) || defined(_M_X64)\n        ubtrfs = LoadLibraryW(L\"Debug\\\\x64\\\\ubtrfs.dll\");\n#endif\n    }\n\n    if (!ubtrfs) {\n        print_string(stderr, IDS_CANT_LOAD_DLL, UBTRFS_DLL);\n        return 1;\n    }\n\n    if (node_size != 0 || sector_size != 0) {\n        pSetSizes SetSizes;\n\n        SetSizes = (pSetSizes)GetProcAddress(ubtrfs, \"SetSizes\");\n\n        if (!SetSizes) {\n            print_string(stderr, IDS_CANT_FIND_SETSIZES, UBTRFS_DLL);\n            return 1;\n        }\n\n        SetSizes(node_size, sector_size);\n    }\n\n    // From Linux btrfs/disk-io.c: \"Artificial requirement for block-group-tree to force\n    // newer features (free-space-tree, no-holes) so the test matrix is smaller.\"\n    if (compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE) {\n        compat_ro_flags |= BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE;\n        incompat_flags |= BTRFS_INCOMPAT_FLAGS_NO_HOLES;\n    }\n\n    if (compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE)\n        compat_ro_flags |= BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE_VALID;\n\n    SetIncompatFlags = (pSetIncompatFlags)GetProcAddress(ubtrfs, \"SetIncompatFlags\");\n\n    if (!SetIncompatFlags) {\n        print_string(stderr, IDS_CANT_FIND_FUNCTION, \"SetIncompatFlags\", UBTRFS_DLL);\n        return 1;\n    }\n\n    SetIncompatFlags(incompat_flags);\n\n    if (compat_ro_flags != 0) {\n        pSetCompatROFlags SetCompatROFlags = (pSetIncompatFlags)GetProcAddress(ubtrfs, \"SetCompatROFlags\");\n\n        if (!SetCompatROFlags) {\n            print_string(stderr, IDS_CANT_FIND_FUNCTION, \"SetCompatROFlags\", UBTRFS_DLL);\n            return 1;\n        }\n\n        SetCompatROFlags(compat_ro_flags);\n    }\n\n    SetCsumType = (pSetCsumType)GetProcAddress(ubtrfs, \"SetCsumType\");\n\n    if (!SetCsumType) {\n        print_string(stderr, IDS_CANT_FIND_FUNCTION, \"SetCsumType\", UBTRFS_DLL);\n        return 1;\n    }\n\n    SetCsumType(csum_type);\n\n    FormatEx = (pFormatEx)GetProcAddress(ubtrfs, \"FormatEx\");\n\n    if (!FormatEx) {\n        print_string(stderr, IDS_CANT_FIND_FORMATEX, UBTRFS_DLL);\n        return 1;\n    }\n\n    memset(&opts, 0, sizeof(options));\n\n    if (label.Length > 0) {\n        labelds.string = label.Buffer;\n        opts.label = &labelds;\n    }\n\n    rootds.string = drive.Buffer;\n\n    success = FormatEx(&rootds, NULL, &opts, 0);\n\n    if (!success) {\n        print_string(stderr, IDS_FORMATEX_ERROR);\n        return 1;\n    }\n\n    print_string(stdout, IDS_SUCCESS);\n\n    return 0;\n}\n"
  },
  {
    "path": "src/mkbtrfs/mkbtrfs.rc.in",
    "content": "// Microsoft Visual C++ generated resource script.\n//\n#include \"@CMAKE_CURRENT_SOURCE_DIR@/src/mkbtrfs/resource.h\"\n\n#define APSTUDIO_READONLY_SYMBOLS\n/////////////////////////////////////////////////////////////////////////////\n//\n// Generated from the TEXTINCLUDE 2 resource.\n//\n#include <winresrc.h>\n\n/////////////////////////////////////////////////////////////////////////////\n#undef APSTUDIO_READONLY_SYMBOLS\n\n/////////////////////////////////////////////////////////////////////////////\n// English (United Kingdom) resources\n\n#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)\nLANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK\n#pragma code_page(1252)\n\n#ifdef APSTUDIO_INVOKED\n/////////////////////////////////////////////////////////////////////////////\n//\n// TEXTINCLUDE\n//\n\n1 TEXTINCLUDE\nBEGIN\n    \"resource.h\\0\"\nEND\n\n2 TEXTINCLUDE\nBEGIN\n    \"#include \"\"winres.h\"\"\\r\\n\"\n    \"\\0\"\nEND\n\n3 TEXTINCLUDE\nBEGIN\n    \"\\r\\n\"\n    \"\\0\"\nEND\n\n#endif    // APSTUDIO_INVOKED\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// Version\n//\n\nVS_VERSION_INFO VERSIONINFO\n FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0\n PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0\n FILEFLAGSMASK 0x17L\n#ifdef _DEBUG\n FILEFLAGS 0x1L\n#else\n FILEFLAGS 0x0L\n#endif\n FILEOS 0x4L\n FILETYPE 0x0L\n FILESUBTYPE 0x0L\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"080904b0\"\n        BEGIN\n            VALUE \"FileDescription\", \"Btrfs formatting utility\"\n            VALUE \"FileVersion\", \"@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@\"\n            VALUE \"InternalName\", \"mkbtrfs\"\n            VALUE \"LegalCopyright\", \"Copyright (c) Mark Harmstone 2016-24\"\n            VALUE \"OriginalFilename\", \"mkbtrfs.exe\"\n            VALUE \"ProductName\", \"WinBtrfs\"\n            VALUE \"ProductVersion\", \"@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x809, 1200\n    END\nEND\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// String Table\n//\n\nSTRINGTABLE\nBEGIN\n    IDS_USAGE               \"Usage: %s device [label]\\n\"\n    IDS_MULTIBYTE_FAILED    \"MultiByteToWideChar failed (error %lu)\"\n    IDS_CANT_RECOGNIZE_DRIVE \"Could not recognize drive %s\"\n    IDS_CANT_LOAD_DLL       \"Unable to load %s\"\n    IDS_CANT_FIND_FORMATEX  \"Could not load function FormatEx in %s\"\n    IDS_FORMATEX_ERROR      \"FormatEx failed\"\n    IDS_SUCCESS             \"Completed successfully.\"\n    IDS_CANT_FIND_SETSIZES  \"Could not load function SetSizes in %s\"\n    IDS_TOO_MANY_ARGS       \"Too many arguments.\"\n    IDS_UNKNOWN_ARG         \"Unknown argument.\"\n    IDS_NO_SECTOR_SIZE      \"No sector size specified.\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_NO_NODE_SIZE        \"No node size specified.\"\n    IDS_CANT_FIND_FUNCTION  \"Could not load function %s in %s\"\n    IDS_USAGE2              \"The device parameter can either be a drive letter, e.g. D:, or a device path,\\nsuch as \\\\Device\\\\Harddisk0\\\\Partition2.\\n\\nTo format the whole of the first hard disk without using partitions, you would\\nneed to use the parameter \\\\Device\\\\Harddisk0\\\\Partition0.\\n\\nSupported flags:\\n\\n/sectorsize:num                     Sets the sector size. This must be a\\n                                    multiple of the size that the disk itself\\n                                    reports. The default is 4096, which should\\n                                    be used unless you have a good reason.\\n\\n/nodesize:num                       Sets the node size, i.e. the size of the\\n                                    metadata trees. The default is 16384. This\\n                                    needs to either be the same as sector size,\\n                                    or a power of two multiple.\\n                                    \\n/csum:id                            Sets the checksum algorithm to use. Valid\\n                                    values are crc32c, xxhash, sha256, and\\n                                    blake2.\\n\\n/mixed                              Enables or disables mixed block groups,\\n/notmixed                           which store data and and metadata in the\\n                                    same chunks. The default is disabled. This\\n                                    is only useful for very small filesystems.\\n\\n/extiref                            Enables or disables extended inode refs,\\n/notextiref                         which increase the number of hardlinks\\n                                    allowed. The default is enabled.\\n\\n/skinnymetadata                     Enables or disable skinny metadata, which\\n/notskinnymetadata                  allows more efficient storage of metadata\\n                                    refs. The default is enabled.\\n\\n/noholes                            Enables or disables whether sparse extents\\n/notnoholes                         should be stored implicitly, which can save\\n                                    a little space. The default is enabled.\\n\\n/freespacetree                      Enables or disables storing the free-space\\n/notfreespacetree                   cache in a tree rather than a blob.\\n                                    Supported from Linux 4.5. The default is\\n                                    enabled.\\n\\n/blockgrouptree                     Enables or disables the block group tree,\\n/notblockgrouptree                  which can decrease mount times. Supported\\n                                    from Linux 6.1. The default is disabled.\\n                                    Implies /noholes and /freespacetree.\"\n    IDS_NO_CSUM             \"No csum value given. Valid values are crc32c, xxhash, sha256, and blake2.\"\n    IDS_INVALID_CSUM_TYPE   \"Invalid csum value. Valid values are crc32c, xxhash, sha256, and blake2.\"\nEND\n\n#endif    // English (United Kingdom) resources\n/////////////////////////////////////////////////////////////////////////////\n\n\n\n#ifndef APSTUDIO_INVOKED\n/////////////////////////////////////////////////////////////////////////////\n//\n// Generated from the TEXTINCLUDE 3 resource.\n//\n\n\n/////////////////////////////////////////////////////////////////////////////\n#endif    // not APSTUDIO_INVOKED\n\n"
  },
  {
    "path": "src/mkbtrfs/resource.h",
    "content": "//{{NO_DEPENDENCIES}}\r\n// Microsoft Visual C++ generated include file.\r\n// Used by mkbtrfs.rc\r\n//\r\n#define IDS_USAGE                       101\r\n#define IDS_MULTIBYTE_FAILED            102\r\n#define IDS_CANT_RECOGNIZE_DRIVE        103\r\n#define IDS_CANT_LOAD_DLL               104\r\n#define IDS_CANT_FIND_FORMATEX          105\r\n#define IDS_FORMATEX_ERROR              106\r\n#define IDS_SUCCESS                     107\r\n#define IDS_CANT_FIND_SETSIZES          108\r\n#define IDS_TOO_MANY_ARGS               109\r\n#define IDS_UNKNOWN_ARG                 110\r\n#define IDS_NO_SECTOR_SIZE              111\r\n#define IDS_NO_NODE_SIZE                112\r\n#define IDS_CANT_FIND_FUNCTION          113\r\n#define IDS_USAGE2                      114\r\n#define IDS_NO_CSUM                     115\r\n#define IDS_INVALID_CSUM_TYPE           116\r\n\r\n// Next default values for new objects\r\n//\r\n#ifdef APSTUDIO_INVOKED\r\n#ifndef APSTUDIO_READONLY_SYMBOLS\r\n#define _APS_NEXT_RESOURCE_VALUE        102\r\n#define _APS_NEXT_COMMAND_VALUE         40001\r\n#define _APS_NEXT_CONTROL_VALUE         1001\r\n#define _APS_NEXT_SYMED_VALUE           101\r\n#endif\r\n#endif\r\n"
  },
  {
    "path": "src/pnp.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n\nextern ERESOURCE pdo_list_lock;\nextern LIST_ENTRY pdo_list;\n\nNTSTATUS pnp_query_remove_device(PDEVICE_OBJECT DeviceObject, PIRP Irp) {\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    NTSTATUS Status;\n\n    // We might be going away imminently - do a flush so we're not caught out\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    if (Vcb->root_fileref && Vcb->root_fileref->fcb && (Vcb->root_fileref->open_count > 0 || has_open_children(Vcb->root_fileref))) {\n        ExReleaseResourceLite(&Vcb->tree_lock);\n        return STATUS_ACCESS_DENIED;\n    }\n\n    if (Vcb->need_write && !Vcb->readonly) {\n        Status = do_write(Vcb, Irp);\n\n        free_trees(Vcb);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"do_write returned %08lx\\n\", Status);\n            ExReleaseResourceLite(&Vcb->tree_lock);\n            return Status;\n        }\n    }\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return STATUS_UNSUCCESSFUL;\n}\n\nstatic NTSTATUS pnp_remove_device(PDEVICE_OBJECT DeviceObject) {\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    NTSTATUS Status;\n\n    if (DeviceObject->Vpb->Flags & VPB_MOUNTED) {\n        Status = FsRtlNotifyVolumeEvent(Vcb->root_file, FSRTL_VOLUME_DISMOUNT);\n        if (!NT_SUCCESS(Status)) {\n            WARN(\"FsRtlNotifyVolumeEvent returned %08lx\\n\", Status);\n        }\n\n        if (Vcb->vde)\n            Vcb->vde->mounted_device = NULL;\n\n        ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n        Vcb->removing = true;\n        ExReleaseResourceLite(&Vcb->tree_lock);\n\n        if (Vcb->open_files == 0)\n            uninit(Vcb);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS pnp_surprise_removal(PDEVICE_OBJECT DeviceObject, PIRP Irp) {\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n\n    TRACE(\"(%p, %p)\\n\", DeviceObject, Irp);\n\n    UNUSED(Irp);\n\n    if (DeviceObject->Vpb->Flags & VPB_MOUNTED) {\n        ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n        if (Vcb->vde)\n            Vcb->vde->mounted_device = NULL;\n\n        Vcb->removing = true;\n\n        ExReleaseResourceLite(&Vcb->tree_lock);\n\n        if (Vcb->open_files == 0)\n            uninit(Vcb);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS bus_query_capabilities(PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PDEVICE_CAPABILITIES dc = IrpSp->Parameters.DeviceCapabilities.Capabilities;\n\n    dc->UniqueID = true;\n    dc->SilentInstall = true;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS bus_query_device_relations(PIRP Irp) {\n    NTSTATUS Status;\n    ULONG num_children;\n    LIST_ENTRY* le;\n    ULONG drsize, i;\n    DEVICE_RELATIONS* dr;\n\n    ExAcquireResourceSharedLite(&pdo_list_lock, true);\n\n    num_children = 0;\n\n    le = pdo_list.Flink;\n    while (le != &pdo_list) {\n        pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);\n\n        if (!pdode->dont_report)\n            num_children++;\n\n        le = le->Flink;\n    }\n\n    drsize = offsetof(DEVICE_RELATIONS, Objects[0]) + (num_children * sizeof(PDEVICE_OBJECT));\n    dr = ExAllocatePoolWithTag(PagedPool, drsize, ALLOC_TAG);\n\n    if (!dr) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    dr->Count = num_children;\n\n    i = 0;\n    le = pdo_list.Flink;\n    while (le != &pdo_list) {\n        pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);\n\n        if (!pdode->dont_report) {\n            ObReferenceObject(pdode->pdo);\n            dr->Objects[i] = pdode->pdo;\n            i++;\n        }\n\n        le = le->Flink;\n    }\n\n    Irp->IoStatus.Information = (ULONG_PTR)dr;\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&pdo_list_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS bus_query_hardware_ids(PIRP Irp) {\n    WCHAR* out;\n\n    static const WCHAR ids[] = L\"ROOT\\\\btrfs\\0\";\n\n    out = ExAllocatePoolWithTag(PagedPool, sizeof(ids), ALLOC_TAG);\n    if (!out) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(out, ids, sizeof(ids));\n\n    Irp->IoStatus.Information = (ULONG_PTR)out;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS bus_pnp(bus_device_extension* bde, PIRP Irp) {\n    NTSTATUS Status = Irp->IoStatus.Status;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    bool handled = false;\n\n    switch (IrpSp->MinorFunction) {\n        case IRP_MN_START_DEVICE:\n        case IRP_MN_CANCEL_REMOVE_DEVICE:\n        case IRP_MN_SURPRISE_REMOVAL:\n        case IRP_MN_REMOVE_DEVICE:\n            Status = STATUS_SUCCESS;\n            handled = true;\n            break;\n\n        case IRP_MN_QUERY_REMOVE_DEVICE:\n            Status = STATUS_UNSUCCESSFUL;\n            handled = true;\n            break;\n\n        case IRP_MN_QUERY_CAPABILITIES:\n            Status = bus_query_capabilities(Irp);\n            handled = true;\n            break;\n\n        case IRP_MN_QUERY_DEVICE_RELATIONS:\n            if (IrpSp->Parameters.QueryDeviceRelations.Type != BusRelations || no_pnp)\n                break;\n\n            Status = bus_query_device_relations(Irp);\n            handled = true;\n            break;\n\n        case IRP_MN_QUERY_ID:\n            if (IrpSp->Parameters.QueryId.IdType != BusQueryHardwareIDs)\n                break;\n\n            Status = bus_query_hardware_ids(Irp);\n            handled = true;\n            break;\n    }\n\n    if (!NT_SUCCESS(Status) && handled) {\n        Irp->IoStatus.Status = Status;\n        IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\n        return Status;\n    }\n\n    Irp->IoStatus.Status = Status;\n\n    IoSkipCurrentIrpStackLocation(Irp);\n    return IoCallDriver(bde->attached_device, Irp);\n}\n\nstatic NTSTATUS pdo_query_device_id(pdo_device_extension* pdode, PIRP Irp) {\n    WCHAR name[100], *noff, *out;\n    int i;\n\n    static const WCHAR pref[] = L\"Btrfs\\\\\";\n\n    RtlCopyMemory(name, pref, sizeof(pref) - sizeof(WCHAR));\n\n    noff = &name[(sizeof(pref) / sizeof(WCHAR)) - 1];\n    for (i = 0; i < 16; i++) {\n        *noff = hex_digit(pdode->uuid.uuid[i] >> 4); noff++;\n        *noff = hex_digit(pdode->uuid.uuid[i] & 0xf); noff++;\n\n        if (i == 3 || i == 5 || i == 7 || i == 9) {\n            *noff = '-';\n            noff++;\n        }\n    }\n    *noff = 0;\n\n    out = ExAllocatePoolWithTag(PagedPool, (wcslen(name) + 1) * sizeof(WCHAR), ALLOC_TAG);\n    if (!out) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(out, name, (wcslen(name) + 1) * sizeof(WCHAR));\n\n    Irp->IoStatus.Information = (ULONG_PTR)out;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS pdo_query_hardware_ids(PIRP Irp) {\n    WCHAR* out;\n\n    static const WCHAR ids[] = L\"BtrfsVolume\\0\";\n\n    out = ExAllocatePoolWithTag(PagedPool, sizeof(ids), ALLOC_TAG);\n    if (!out) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(out, ids, sizeof(ids));\n\n    Irp->IoStatus.Information = (ULONG_PTR)out;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS pdo_query_id(pdo_device_extension* pdode, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n    switch (IrpSp->Parameters.QueryId.IdType) {\n        case BusQueryDeviceID:\n            TRACE(\"BusQueryDeviceID\\n\");\n            return pdo_query_device_id(pdode, Irp);\n\n        case BusQueryHardwareIDs:\n            TRACE(\"BusQueryHardwareIDs\\n\");\n            return pdo_query_hardware_ids(Irp);\n\n        default:\n            break;\n    }\n\n    return Irp->IoStatus.Status;\n}\n\ntypedef struct {\n    IO_STATUS_BLOCK iosb;\n    KEVENT Event;\n    NTSTATUS Status;\n} device_usage_context;\n\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall device_usage_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {\n    device_usage_context* context = conptr;\n\n    UNUSED(DeviceObject);\n\n    context->Status = Irp->IoStatus.Status;\n\n    KeSetEvent(&context->Event, 0, false);\n\n    return STATUS_MORE_PROCESSING_REQUIRED;\n}\n\nstatic NTSTATUS pdo_device_usage_notification(pdo_device_extension* pdode, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    LIST_ENTRY* le;\n\n    TRACE(\"(%p, %p)\\n\", pdode, Irp);\n\n    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n    le = pdode->children.Flink;\n\n    while (le != &pdode->children) {\n        volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n        if (vc->devobj) {\n            PIRP Irp2;\n            PIO_STACK_LOCATION IrpSp2;\n            device_usage_context context;\n\n            Irp2 = IoAllocateIrp(vc->devobj->StackSize, false);\n            if (!Irp2) {\n                ERR(\"out of memory\\n\");\n                ExReleaseResourceLite(&pdode->child_lock);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            IrpSp2 = IoGetNextIrpStackLocation(Irp2);\n            IrpSp2->MajorFunction = IRP_MJ_PNP;\n            IrpSp2->MinorFunction = IRP_MN_DEVICE_USAGE_NOTIFICATION;\n            IrpSp2->Parameters.UsageNotification = IrpSp->Parameters.UsageNotification;\n            IrpSp2->FileObject = vc->fileobj;\n\n            context.iosb.Status = STATUS_SUCCESS;\n            Irp2->UserIosb = &context.iosb;\n\n            KeInitializeEvent(&context.Event, NotificationEvent, false);\n            Irp2->UserEvent = &context.Event;\n\n            IoSetCompletionRoutine(Irp2, device_usage_completion, &context, true, true, true);\n\n            context.Status = IoCallDriver(vc->devobj, Irp2);\n\n            if (context.Status == STATUS_PENDING)\n                KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n\n            if (!NT_SUCCESS(context.Status)) {\n                ERR(\"IoCallDriver returned %08lx\\n\", context.Status);\n                ExReleaseResourceLite(&pdode->child_lock);\n                return context.Status;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS pdo_query_device_relations(PDEVICE_OBJECT pdo, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PDEVICE_RELATIONS device_relations;\n\n    if (IrpSp->Parameters.QueryDeviceRelations.Type != TargetDeviceRelation)\n        return Irp->IoStatus.Status;\n\n    device_relations = ExAllocatePoolWithTag(PagedPool, sizeof(DEVICE_RELATIONS), ALLOC_TAG);\n    if (!device_relations) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    device_relations->Count = 1;\n    device_relations->Objects[0] = pdo;\n\n    ObReferenceObject(pdo);\n\n    Irp->IoStatus.Information = (ULONG_PTR)device_relations;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS pdo_pnp(PDEVICE_OBJECT pdo, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    pdo_device_extension* pdode = pdo->DeviceExtension;\n\n    switch (IrpSp->MinorFunction) {\n        case IRP_MN_QUERY_ID:\n            return pdo_query_id(pdode, Irp);\n\n        case IRP_MN_START_DEVICE:\n        case IRP_MN_CANCEL_REMOVE_DEVICE:\n        case IRP_MN_SURPRISE_REMOVAL:\n        case IRP_MN_REMOVE_DEVICE:\n            return STATUS_SUCCESS;\n\n        case IRP_MN_QUERY_REMOVE_DEVICE:\n            return STATUS_UNSUCCESSFUL;\n\n        case IRP_MN_DEVICE_USAGE_NOTIFICATION:\n            return pdo_device_usage_notification(pdode, Irp);\n\n        case IRP_MN_QUERY_DEVICE_RELATIONS:\n            return pdo_query_device_relations(pdo, Irp);\n    }\n\n    return Irp->IoStatus.Status;\n}\n\nstatic NTSTATUS pnp_device_usage_notification(PDEVICE_OBJECT DeviceObject, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n\n    if (IrpSp->Parameters.UsageNotification.InPath) {\n        switch (IrpSp->Parameters.UsageNotification.Type) {\n            case DeviceUsageTypePaging:\n            case DeviceUsageTypeHibernation:\n            case DeviceUsageTypeDumpFile:\n                IoAdjustPagingPathCount(&Vcb->page_file_count, IrpSp->Parameters.UsageNotification.InPath);\n                break;\n\n            default:\n                break;\n        }\n    }\n\n    IoSkipCurrentIrpStackLocation(Irp);\n    return IoCallDriver(Vcb->Vpb->RealDevice, Irp);\n}\n\n_Dispatch_type_(IRP_MJ_PNP)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_pnp(PDEVICE_OBJECT DeviceObject, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    NTSTATUS Status;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_BUS) {\n        Status = bus_pnp(DeviceObject->DeviceExtension, Irp);\n        goto exit;\n    } else if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        volume_device_extension* vde = DeviceObject->DeviceExtension;\n        IoSkipCurrentIrpStackLocation(Irp);\n        Status = IoCallDriver(vde->attached_device, Irp);\n        goto exit;\n    } else if (Vcb && Vcb->type == VCB_TYPE_PDO) {\n        Status = pdo_pnp(DeviceObject, Irp);\n        goto end;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    Status = STATUS_NOT_IMPLEMENTED;\n\n    switch (IrpSp->MinorFunction) {\n        case IRP_MN_CANCEL_REMOVE_DEVICE:\n            Status = STATUS_SUCCESS;\n            break;\n\n        case IRP_MN_QUERY_REMOVE_DEVICE:\n            Status = pnp_query_remove_device(DeviceObject, Irp);\n            break;\n\n        case IRP_MN_REMOVE_DEVICE:\n            Status = pnp_remove_device(DeviceObject);\n            break;\n\n        case IRP_MN_SURPRISE_REMOVAL:\n            Status = pnp_surprise_removal(DeviceObject, Irp);\n            break;\n\n        case IRP_MN_DEVICE_USAGE_NOTIFICATION:\n            Status = pnp_device_usage_notification(DeviceObject, Irp);\n            goto exit;\n\n        default:\n            TRACE(\"passing minor function 0x%x on\\n\", IrpSp->MinorFunction);\n\n            IoSkipCurrentIrpStackLocation(Irp);\n            Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp);\n            goto exit;\n    }\n\nend:\n    Irp->IoStatus.Status = Status;\n\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\nexit:\n    TRACE(\"returning %08lx\\n\", Status);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n"
  },
  {
    "path": "src/read.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include \"zstd/lib/common/xxhash.h\"\n#include \"crc32c.h\"\n\nenum read_data_status {\n    ReadDataStatus_Pending,\n    ReadDataStatus_Success,\n    ReadDataStatus_Error,\n    ReadDataStatus_MissingDevice,\n    ReadDataStatus_Skip\n};\n\nstruct read_data_context;\n\ntypedef struct {\n    struct read_data_context* context;\n    uint16_t stripenum;\n    bool rewrite;\n    PIRP Irp;\n    IO_STATUS_BLOCK iosb;\n    enum read_data_status status;\n    PMDL mdl;\n    uint64_t stripestart;\n    uint64_t stripeend;\n} read_data_stripe;\n\ntypedef struct {\n    KEVENT Event;\n    NTSTATUS Status;\n    chunk* c;\n    uint64_t address;\n    uint32_t buflen;\n    LONG num_stripes, stripes_left;\n    uint64_t type;\n    uint32_t sector_size;\n    uint16_t firstoff, startoffstripe, sectors_per_stripe;\n    void* csum;\n    bool tree;\n    read_data_stripe* stripes;\n    uint8_t* va;\n} read_data_context;\n\nextern bool diskacc;\nextern tPsUpdateDiskCounters fPsUpdateDiskCounters;\nextern tCcCopyReadEx fCcCopyReadEx;\nextern tFsRtlUpdateDiskCounters fFsRtlUpdateDiskCounters;\n\n#define LZO_PAGE_SIZE 4096\n\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall read_data_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {\n    read_data_stripe* stripe = conptr;\n    read_data_context* context = (read_data_context*)stripe->context;\n\n    UNUSED(DeviceObject);\n\n    stripe->iosb = Irp->IoStatus;\n\n    if (NT_SUCCESS(Irp->IoStatus.Status))\n        stripe->status = ReadDataStatus_Success;\n    else\n        stripe->status = ReadDataStatus_Error;\n\n    if (InterlockedDecrement(&context->stripes_left) == 0)\n        KeSetEvent(&context->Event, 0, false);\n\n    return STATUS_MORE_PROCESSING_REQUIRED;\n}\n\nNTSTATUS check_csum(device_extension* Vcb, uint8_t* data, uint32_t sectors, void* csum) {\n    void* csum2;\n\n    csum2 = ExAllocatePoolWithTag(PagedPool, Vcb->csum_size * sectors, ALLOC_TAG);\n    if (!csum2) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    do_calc_job(Vcb, data, sectors, csum2);\n\n    if (RtlCompareMemory(csum2, csum, sectors * Vcb->csum_size) != sectors * Vcb->csum_size) {\n        ExFreePool(csum2);\n        return STATUS_CRC_ERROR;\n    }\n\n    ExFreePool(csum2);\n\n    return STATUS_SUCCESS;\n}\n\nvoid get_tree_checksum(device_extension* Vcb, tree_header* th, void* csum) {\n    switch (Vcb->superblock.csum_type) {\n        case CSUM_TYPE_CRC32C:\n            *(uint32_t*)csum = ~calc_crc32c(0xffffffff, (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));\n        break;\n\n        case CSUM_TYPE_XXHASH:\n            *(uint64_t*)csum = XXH64((uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum), 0);\n        break;\n\n        case CSUM_TYPE_SHA256:\n            calc_sha256(csum, &th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));\n        break;\n\n        case CSUM_TYPE_BLAKE2:\n            blake2b(csum, BLAKE2_HASH_SIZE, (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));\n        break;\n    }\n}\n\nbool check_tree_checksum(device_extension* Vcb, tree_header* th) {\n    switch (Vcb->superblock.csum_type) {\n        case CSUM_TYPE_CRC32C: {\n            uint32_t crc32 = ~calc_crc32c(0xffffffff, (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));\n\n            if (crc32 == *((uint32_t*)th->csum))\n                return true;\n\n            WARN(\"hash was %08x, expected %08x\\n\", crc32, *((uint32_t*)th->csum));\n\n            break;\n        }\n\n        case CSUM_TYPE_XXHASH: {\n            uint64_t hash = XXH64((uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum), 0);\n\n            if (hash == *((uint64_t*)th->csum))\n                return true;\n\n            WARN(\"hash was %I64x, expected %I64x\\n\", hash, *((uint64_t*)th->csum));\n\n            break;\n        }\n\n        case CSUM_TYPE_SHA256: {\n            uint8_t hash[SHA256_HASH_SIZE];\n\n            calc_sha256(hash, (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));\n\n            if (RtlCompareMemory(hash, th, SHA256_HASH_SIZE) == SHA256_HASH_SIZE)\n                return true;\n\n            WARN(\"hash was invalid\\n\");\n\n            break;\n        }\n\n        case CSUM_TYPE_BLAKE2: {\n            uint8_t hash[BLAKE2_HASH_SIZE];\n\n            blake2b(hash, sizeof(hash), (uint8_t*)&th->fs_uuid, Vcb->superblock.node_size - sizeof(th->csum));\n\n            if (RtlCompareMemory(hash, th, BLAKE2_HASH_SIZE) == BLAKE2_HASH_SIZE)\n                return true;\n\n            WARN(\"hash was invalid\\n\");\n\n            break;\n        }\n    }\n\n    return false;\n}\n\nvoid get_sector_csum(device_extension* Vcb, void* buf, void* csum) {\n    switch (Vcb->superblock.csum_type) {\n        case CSUM_TYPE_CRC32C:\n            *(uint32_t*)csum = ~calc_crc32c(0xffffffff, buf, Vcb->superblock.sector_size);\n        break;\n\n        case CSUM_TYPE_XXHASH:\n            *(uint64_t*)csum = XXH64(buf, Vcb->superblock.sector_size, 0);\n        break;\n\n        case CSUM_TYPE_SHA256:\n            calc_sha256(csum, buf, Vcb->superblock.sector_size);\n        break;\n\n        case CSUM_TYPE_BLAKE2:\n            blake2b(csum, BLAKE2_HASH_SIZE, buf, Vcb->superblock.sector_size);\n        break;\n    }\n}\n\nbool check_sector_csum(device_extension* Vcb, void* buf, void* csum) {\n    switch (Vcb->superblock.csum_type) {\n        case CSUM_TYPE_CRC32C: {\n            uint32_t crc32 = ~calc_crc32c(0xffffffff, buf, Vcb->superblock.sector_size);\n\n            return *(uint32_t*)csum == crc32;\n        }\n\n        case CSUM_TYPE_XXHASH: {\n            uint64_t hash = XXH64(buf, Vcb->superblock.sector_size, 0);\n\n            return *(uint64_t*)csum == hash;\n        }\n\n        case CSUM_TYPE_SHA256: {\n            uint8_t hash[SHA256_HASH_SIZE];\n\n            calc_sha256(hash, buf, Vcb->superblock.sector_size);\n\n            return RtlCompareMemory(hash, csum, SHA256_HASH_SIZE) == SHA256_HASH_SIZE;\n        }\n\n        case CSUM_TYPE_BLAKE2: {\n            uint8_t hash[BLAKE2_HASH_SIZE];\n\n            blake2b(hash, sizeof(hash), buf, Vcb->superblock.sector_size);\n\n            return RtlCompareMemory(hash, csum, BLAKE2_HASH_SIZE) == BLAKE2_HASH_SIZE;\n        }\n    }\n\n    return false;\n}\n\nstatic NTSTATUS read_data_dup(device_extension* Vcb, uint8_t* buf, uint64_t addr, read_data_context* context, CHUNK_ITEM* ci,\n                              device** devices, uint64_t generation) {\n    bool checksum_error = false;\n    uint16_t j, stripe = 0;\n    NTSTATUS Status;\n    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1];\n\n    for (j = 0; j < ci->num_stripes; j++) {\n        if (context->stripes[j].status == ReadDataStatus_Error) {\n            WARN(\"stripe %u returned error %08lx\\n\", j, context->stripes[j].iosb.Status);\n            log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n            return context->stripes[j].iosb.Status;\n        } else if (context->stripes[j].status == ReadDataStatus_Success) {\n            stripe = j;\n            break;\n        }\n    }\n\n    if (context->stripes[stripe].status != ReadDataStatus_Success)\n        return STATUS_INTERNAL_ERROR;\n\n    if (context->tree) {\n        tree_header* th = (tree_header*)buf;\n\n        if (th->address != context->address || !check_tree_checksum(Vcb, th)) {\n            checksum_error = true;\n            log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n        } else if (generation != 0 && th->generation != generation) {\n            checksum_error = true;\n            log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_GENERATION_ERRORS);\n        }\n    } else if (context->csum) {\n        Status = check_csum(Vcb, buf, (ULONG)context->stripes[stripe].Irp->IoStatus.Information / context->sector_size, context->csum);\n\n        if (Status == STATUS_CRC_ERROR) {\n            checksum_error = true;\n            log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n        } else if (!NT_SUCCESS(Status)) {\n            ERR(\"check_csum returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    if (!checksum_error)\n        return STATUS_SUCCESS;\n\n    if (ci->num_stripes == 1)\n        return STATUS_CRC_ERROR;\n\n    if (context->tree) {\n        tree_header* t2;\n        bool recovered = false;\n\n        t2 = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG);\n        if (!t2) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        for (j = 0; j < ci->num_stripes; j++) {\n            if (j != stripe && devices[j] && devices[j]->devobj) {\n                Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + context->stripes[stripe].stripestart,\n                                        Vcb->superblock.node_size, (uint8_t*)t2, false);\n                if (!NT_SUCCESS(Status)) {\n                    WARN(\"sync_read_phys returned %08lx\\n\", Status);\n                    log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n                } else {\n                    bool checksum_error = !check_tree_checksum(Vcb, t2);\n\n                    if (t2->address == addr && !checksum_error && (generation == 0 || t2->generation == generation)) {\n                        RtlCopyMemory(buf, t2, Vcb->superblock.node_size);\n                        ERR(\"recovering from checksum error at %I64x, device %I64x\\n\", addr, devices[stripe]->devitem.dev_id);\n                        recovered = true;\n\n                        if (!Vcb->readonly && !devices[stripe]->readonly) { // write good data over bad\n                            Status = write_data_phys(devices[stripe]->devobj, devices[stripe]->fileobj, cis[stripe].offset + context->stripes[stripe].stripestart,\n                                                     t2, Vcb->superblock.node_size);\n                            if (!NT_SUCCESS(Status)) {\n                                WARN(\"write_data_phys returned %08lx\\n\", Status);\n                                log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_WRITE_ERRORS);\n                            }\n                        }\n\n                        break;\n                    } else if (t2->address != addr || checksum_error)\n                        log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                    else\n                        log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_GENERATION_ERRORS);\n                }\n            }\n        }\n\n        if (!recovered) {\n            ERR(\"unrecoverable checksum error at %I64x\\n\", addr);\n            ExFreePool(t2);\n            return STATUS_CRC_ERROR;\n        }\n\n        ExFreePool(t2);\n    } else {\n        ULONG sectors = (ULONG)context->stripes[stripe].Irp->IoStatus.Information >> Vcb->sector_shift;\n        uint8_t* sector;\n        void* ptr = context->csum;\n\n        sector = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.sector_size, ALLOC_TAG);\n        if (!sector) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        for (ULONG i = 0; i < sectors; i++) {\n            if (!check_sector_csum(Vcb, buf + (i << Vcb->sector_shift), ptr)) {\n                bool recovered = false;\n\n                for (j = 0; j < ci->num_stripes; j++) {\n                    if (j != stripe && devices[j] && devices[j]->devobj) {\n                        Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj,\n                                                cis[j].offset + context->stripes[stripe].stripestart + ((uint64_t)i << Vcb->sector_shift),\n                                                Vcb->superblock.sector_size, sector, false);\n                        if (!NT_SUCCESS(Status)) {\n                            WARN(\"sync_read_phys returned %08lx\\n\", Status);\n                            log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n                        } else {\n                            if (check_sector_csum(Vcb, sector, ptr)) {\n                                RtlCopyMemory(buf + (i << Vcb->sector_shift), sector, Vcb->superblock.sector_size);\n                                ERR(\"recovering from checksum error at %I64x, device %I64x\\n\", addr + ((uint64_t)i << Vcb->sector_shift), devices[stripe]->devitem.dev_id);\n                                recovered = true;\n\n                                if (!Vcb->readonly && !devices[stripe]->readonly) { // write good data over bad\n                                    Status = write_data_phys(devices[stripe]->devobj, devices[stripe]->fileobj,\n                                                             cis[stripe].offset + context->stripes[stripe].stripestart + ((uint64_t)i << Vcb->sector_shift),\n                                                             sector, Vcb->superblock.sector_size);\n                                    if (!NT_SUCCESS(Status)) {\n                                        WARN(\"write_data_phys returned %08lx\\n\", Status);\n                                        log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_WRITE_ERRORS);\n                                    }\n                                }\n\n                                break;\n                            } else\n                                log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                        }\n                    }\n                }\n\n                if (!recovered) {\n                    ERR(\"unrecoverable checksum error at %I64x\\n\", addr + ((uint64_t)i << Vcb->sector_shift));\n                    ExFreePool(sector);\n                    return STATUS_CRC_ERROR;\n                }\n            }\n\n            ptr = (uint8_t*)ptr + Vcb->csum_size;\n        }\n\n        ExFreePool(sector);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS read_data_raid0(device_extension* Vcb, uint8_t* buf, uint64_t addr, uint32_t length, read_data_context* context,\n                                CHUNK_ITEM* ci, device** devices, uint64_t generation, uint64_t offset) {\n    for (uint16_t i = 0; i < ci->num_stripes; i++) {\n        if (context->stripes[i].status == ReadDataStatus_Error) {\n            WARN(\"stripe %u returned error %08lx\\n\", i, context->stripes[i].iosb.Status);\n            log_device_error(Vcb, devices[i], BTRFS_DEV_STAT_READ_ERRORS);\n            return context->stripes[i].iosb.Status;\n        }\n    }\n\n    if (context->tree) { // shouldn't happen, as trees shouldn't cross stripe boundaries\n        tree_header* th = (tree_header*)buf;\n        bool checksum_error = !check_tree_checksum(Vcb, th);\n\n        if (checksum_error || addr != th->address || (generation != 0 && generation != th->generation)) {\n            uint64_t off;\n            uint16_t stripe;\n\n            get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes, &off, &stripe);\n\n            ERR(\"unrecoverable checksum error at %I64x, device %I64x\\n\", addr, devices[stripe]->devitem.dev_id);\n\n            if (checksum_error) {\n                log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                return STATUS_CRC_ERROR;\n            } else if (addr != th->address) {\n                WARN(\"address of tree was %I64x, not %I64x as expected\\n\", th->address, addr);\n                log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                return STATUS_CRC_ERROR;\n            } else if (generation != 0 && generation != th->generation) {\n                WARN(\"generation of tree was %I64x, not %I64x as expected\\n\", th->generation, generation);\n                log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_GENERATION_ERRORS);\n                return STATUS_CRC_ERROR;\n            }\n        }\n    } else if (context->csum) {\n        NTSTATUS Status;\n\n        Status = check_csum(Vcb, buf, length >> Vcb->sector_shift, context->csum);\n\n        if (Status == STATUS_CRC_ERROR) {\n            void* ptr = context->csum;\n\n            for (uint32_t i = 0; i < length >> Vcb->sector_shift; i++) {\n                if (!check_sector_csum(Vcb, buf + (i << Vcb->sector_shift), ptr)) {\n                    uint64_t off;\n                    uint16_t stripe;\n\n                    get_raid0_offset(addr - offset + ((uint64_t)i << Vcb->sector_shift), ci->stripe_length, ci->num_stripes, &off, &stripe);\n\n                    ERR(\"unrecoverable checksum error at %I64x, device %I64x\\n\", addr, devices[stripe]->devitem.dev_id);\n\n                    log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n\n                    return Status;\n                }\n\n                ptr = (uint8_t*)ptr + Vcb->csum_size;\n            }\n\n            return Status;\n        } else if (!NT_SUCCESS(Status)) {\n            ERR(\"check_csum returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS read_data_raid10(device_extension* Vcb, uint8_t* buf, uint64_t addr, uint32_t length, read_data_context* context,\n                                 CHUNK_ITEM* ci, device** devices, uint64_t generation, uint64_t offset) {\n    uint16_t stripe = 0;\n    NTSTATUS Status;\n    bool checksum_error = false;\n    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1];\n\n    for (uint16_t j = 0; j < ci->num_stripes; j++) {\n        if (context->stripes[j].status == ReadDataStatus_Error) {\n            WARN(\"stripe %u returned error %08lx\\n\", j, context->stripes[j].iosb.Status);\n            log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n            return context->stripes[j].iosb.Status;\n        } else if (context->stripes[j].status == ReadDataStatus_Success)\n            stripe = j;\n    }\n\n    if (context->tree) {\n        tree_header* th = (tree_header*)buf;\n\n        if (!check_tree_checksum(Vcb, th)) {\n            checksum_error = true;\n            log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n        } else if (addr != th->address) {\n            WARN(\"address of tree was %I64x, not %I64x as expected\\n\", th->address, addr);\n            checksum_error = true;\n            log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n        } else if (generation != 0 && generation != th->generation) {\n            WARN(\"generation of tree was %I64x, not %I64x as expected\\n\", th->generation, generation);\n            checksum_error = true;\n            log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_GENERATION_ERRORS);\n        }\n    } else if (context->csum) {\n        Status = check_csum(Vcb, buf, length >> Vcb->sector_shift, context->csum);\n\n        if (Status == STATUS_CRC_ERROR)\n            checksum_error = true;\n        else if (!NT_SUCCESS(Status)) {\n            ERR(\"check_csum returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    if (!checksum_error)\n        return STATUS_SUCCESS;\n\n    if (context->tree) {\n        tree_header* t2;\n        uint64_t off;\n        uint16_t badsubstripe = 0;\n        bool recovered = false;\n\n        t2 = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size, ALLOC_TAG);\n        if (!t2) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes / ci->sub_stripes, &off, &stripe);\n\n        stripe *= ci->sub_stripes;\n\n        for (uint16_t j = 0; j < ci->sub_stripes; j++) {\n            if (context->stripes[stripe + j].status == ReadDataStatus_Success) {\n                badsubstripe = j;\n                break;\n            }\n        }\n\n        for (uint16_t j = 0; j < ci->sub_stripes; j++) {\n            if (context->stripes[stripe + j].status != ReadDataStatus_Success && devices[stripe + j] && devices[stripe + j]->devobj) {\n                Status = sync_read_phys(devices[stripe + j]->devobj, devices[stripe + j]->fileobj, cis[stripe + j].offset + off,\n                                        Vcb->superblock.node_size, (uint8_t*)t2, false);\n                if (!NT_SUCCESS(Status)) {\n                    WARN(\"sync_read_phys returned %08lx\\n\", Status);\n                    log_device_error(Vcb, devices[stripe + j], BTRFS_DEV_STAT_READ_ERRORS);\n                } else {\n                    bool checksum_error = !check_tree_checksum(Vcb, t2);\n\n                    if (t2->address == addr && !checksum_error && (generation == 0 || t2->generation == generation)) {\n                        RtlCopyMemory(buf, t2, Vcb->superblock.node_size);\n                        ERR(\"recovering from checksum error at %I64x, device %I64x\\n\", addr, devices[stripe + j]->devitem.dev_id);\n                        recovered = true;\n\n                        if (!Vcb->readonly && !devices[stripe + badsubstripe]->readonly && devices[stripe + badsubstripe]->devobj) { // write good data over bad\n                            Status = write_data_phys(devices[stripe + badsubstripe]->devobj, devices[stripe + badsubstripe]->fileobj,\n                                                     cis[stripe + badsubstripe].offset + off, t2, Vcb->superblock.node_size);\n                            if (!NT_SUCCESS(Status)) {\n                                WARN(\"write_data_phys returned %08lx\\n\", Status);\n                                log_device_error(Vcb, devices[stripe + badsubstripe], BTRFS_DEV_STAT_WRITE_ERRORS);\n                            }\n                        }\n\n                        break;\n                    } else if (t2->address != addr || checksum_error)\n                        log_device_error(Vcb, devices[stripe + j], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                    else\n                        log_device_error(Vcb, devices[stripe + j], BTRFS_DEV_STAT_GENERATION_ERRORS);\n                }\n            }\n        }\n\n        if (!recovered) {\n            ERR(\"unrecoverable checksum error at %I64x\\n\", addr);\n            ExFreePool(t2);\n            return STATUS_CRC_ERROR;\n        }\n\n        ExFreePool(t2);\n    } else {\n        ULONG sectors = length >> Vcb->sector_shift;\n        uint8_t* sector;\n        void* ptr = context->csum;\n\n        sector = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.sector_size, ALLOC_TAG);\n        if (!sector) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        for (ULONG i = 0; i < sectors; i++) {\n            if (!check_sector_csum(Vcb, buf + (i << Vcb->sector_shift), ptr)) {\n                uint64_t off;\n                uint16_t stripe2, badsubstripe = 0;\n                bool recovered = false;\n\n                get_raid0_offset(addr - offset + ((uint64_t)i << Vcb->sector_shift), ci->stripe_length,\n                                 ci->num_stripes / ci->sub_stripes, &off, &stripe2);\n\n                stripe2 *= ci->sub_stripes;\n\n                for (uint16_t j = 0; j < ci->sub_stripes; j++) {\n                    if (context->stripes[stripe2 + j].status == ReadDataStatus_Success) {\n                        badsubstripe = j;\n                        break;\n                    }\n                }\n\n                log_device_error(Vcb, devices[stripe2 + badsubstripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n\n                for (uint16_t j = 0; j < ci->sub_stripes; j++) {\n                    if (context->stripes[stripe2 + j].status != ReadDataStatus_Success && devices[stripe2 + j] && devices[stripe2 + j]->devobj) {\n                        Status = sync_read_phys(devices[stripe2 + j]->devobj, devices[stripe2 + j]->fileobj, cis[stripe2 + j].offset + off,\n                                                Vcb->superblock.sector_size, sector, false);\n                        if (!NT_SUCCESS(Status)) {\n                            WARN(\"sync_read_phys returned %08lx\\n\", Status);\n                            log_device_error(Vcb, devices[stripe2 + j], BTRFS_DEV_STAT_READ_ERRORS);\n                        } else {\n                            if (check_sector_csum(Vcb, sector, ptr)) {\n                                RtlCopyMemory(buf + (i << Vcb->sector_shift), sector, Vcb->superblock.sector_size);\n                                ERR(\"recovering from checksum error at %I64x, device %I64x\\n\", addr + ((uint64_t)i << Vcb->sector_shift), devices[stripe2 + j]->devitem.dev_id);\n                                recovered = true;\n\n                                if (!Vcb->readonly && !devices[stripe2 + badsubstripe]->readonly && devices[stripe2 + badsubstripe]->devobj) { // write good data over bad\n                                    Status = write_data_phys(devices[stripe2 + badsubstripe]->devobj, devices[stripe2 + badsubstripe]->fileobj,\n                                                             cis[stripe2 + badsubstripe].offset + off, sector, Vcb->superblock.sector_size);\n                                    if (!NT_SUCCESS(Status)) {\n                                        WARN(\"write_data_phys returned %08lx\\n\", Status);\n                                        log_device_error(Vcb, devices[stripe2 + badsubstripe], BTRFS_DEV_STAT_READ_ERRORS);\n                                    }\n                                }\n\n                                break;\n                            } else\n                                log_device_error(Vcb, devices[stripe2 + j], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                        }\n                    }\n                }\n\n                if (!recovered) {\n                    ERR(\"unrecoverable checksum error at %I64x\\n\", addr + ((uint64_t)i << Vcb->sector_shift));\n                    ExFreePool(sector);\n                    return STATUS_CRC_ERROR;\n                }\n            }\n\n            ptr = (uint8_t*)ptr + Vcb->csum_size;\n        }\n\n        ExFreePool(sector);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS read_data_raid5(device_extension* Vcb, uint8_t* buf, uint64_t addr, uint32_t length, read_data_context* context, CHUNK_ITEM* ci,\n                                device** devices, uint64_t offset, uint64_t generation, chunk* c, bool degraded) {\n    NTSTATUS Status;\n    bool checksum_error = false;\n    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1];\n    uint16_t j, stripe = 0;\n    bool no_success = true;\n\n    for (j = 0; j < ci->num_stripes; j++) {\n        if (context->stripes[j].status == ReadDataStatus_Error) {\n            WARN(\"stripe %u returned error %08lx\\n\", j, context->stripes[j].iosb.Status);\n            log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n            return context->stripes[j].iosb.Status;\n        } else if (context->stripes[j].status == ReadDataStatus_Success) {\n            stripe = j;\n            no_success = false;\n        }\n    }\n\n    if (c) {    // check partial stripes\n        LIST_ENTRY* le;\n        uint64_t ps_length = (ci->num_stripes - 1) * ci->stripe_length;\n\n        ExAcquireResourceSharedLite(&c->partial_stripes_lock, true);\n\n        le = c->partial_stripes.Flink;\n        while (le != &c->partial_stripes) {\n            partial_stripe* ps = CONTAINING_RECORD(le, partial_stripe, list_entry);\n\n            if (ps->address + ps_length > addr && ps->address < addr + length) {\n                ULONG runlength, index;\n\n                runlength = RtlFindFirstRunClear(&ps->bmp, &index);\n\n                while (runlength != 0) {\n                    if (index >= ps->bmplen)\n                        break;\n\n                    if (index + runlength >= ps->bmplen) {\n                        runlength = ps->bmplen - index;\n\n                        if (runlength == 0)\n                            break;\n                    }\n\n                    uint64_t runstart = ps->address + (index << Vcb->sector_shift);\n                    uint64_t runend = runstart + (runlength << Vcb->sector_shift);\n                    uint64_t start = max(runstart, addr);\n                    uint64_t end = min(runend, addr + length);\n\n                    if (end > start)\n                        RtlCopyMemory(buf + start - addr, &ps->data[start - ps->address], (ULONG)(end - start));\n\n                    runlength = RtlFindNextForwardRunClear(&ps->bmp, index + runlength, &index);\n                }\n            } else if (ps->address >= addr + length)\n                break;\n\n            le = le->Flink;\n        }\n\n        ExReleaseResourceLite(&c->partial_stripes_lock);\n    }\n\n    if (context->tree) {\n        tree_header* th = (tree_header*)buf;\n\n        if (addr != th->address || !check_tree_checksum(Vcb, th)) {\n            checksum_error = true;\n            if (!no_success && !degraded)\n                log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n        } else if (generation != 0 && generation != th->generation) {\n            checksum_error = true;\n            if (!no_success && !degraded)\n                log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_GENERATION_ERRORS);\n        }\n    } else if (context->csum) {\n        Status = check_csum(Vcb, buf, length >> Vcb->sector_shift, context->csum);\n\n        if (Status == STATUS_CRC_ERROR) {\n            if (!degraded)\n                WARN(\"checksum error\\n\");\n            checksum_error = true;\n        } else if (!NT_SUCCESS(Status)) {\n            ERR(\"check_csum returned %08lx\\n\", Status);\n            return Status;\n        }\n    } else if (degraded)\n        checksum_error = true;\n\n    if (!checksum_error)\n        return STATUS_SUCCESS;\n\n    if (context->tree) {\n        uint16_t parity;\n        uint64_t off;\n        bool recovered = false, first = true, failed = false;\n        uint8_t* t2;\n\n        t2 = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size * 2, ALLOC_TAG);\n        if (!t2) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes - 1, &off, &stripe);\n\n        parity = (((addr - offset) / ((ci->num_stripes - 1) * ci->stripe_length)) + ci->num_stripes - 1) % ci->num_stripes;\n\n        stripe = (parity + stripe + 1) % ci->num_stripes;\n\n        for (j = 0; j < ci->num_stripes; j++) {\n            if (j != stripe) {\n                if (devices[j] && devices[j]->devobj) {\n                    if (first) {\n                        Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + off, Vcb->superblock.node_size, t2, false);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"sync_read_phys returned %08lx\\n\", Status);\n                            log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n                            failed = true;\n                            break;\n                        }\n\n                        first = false;\n                    } else {\n                        Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + off, Vcb->superblock.node_size, t2 + Vcb->superblock.node_size, false);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"sync_read_phys returned %08lx\\n\", Status);\n                            log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n                            failed = true;\n                            break;\n                        }\n\n                        do_xor(t2, t2 + Vcb->superblock.node_size, Vcb->superblock.node_size);\n                    }\n                } else {\n                    failed = true;\n                    break;\n                }\n            }\n        }\n\n        if (!failed) {\n            tree_header* t3 = (tree_header*)t2;\n\n            if (t3->address == addr && check_tree_checksum(Vcb, t3) && (generation == 0 || t3->generation == generation)) {\n                RtlCopyMemory(buf, t2, Vcb->superblock.node_size);\n\n                if (!degraded)\n                    ERR(\"recovering from checksum error at %I64x, device %I64x\\n\", addr, devices[stripe]->devitem.dev_id);\n\n                recovered = true;\n\n                if (!Vcb->readonly && devices[stripe] && !devices[stripe]->readonly && devices[stripe]->devobj) { // write good data over bad\n                    Status = write_data_phys(devices[stripe]->devobj, devices[stripe]->fileobj, cis[stripe].offset + off, t2, Vcb->superblock.node_size);\n                    if (!NT_SUCCESS(Status)) {\n                        WARN(\"write_data_phys returned %08lx\\n\", Status);\n                        log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_WRITE_ERRORS);\n                    }\n                }\n            }\n        }\n\n        if (!recovered) {\n            ERR(\"unrecoverable checksum error at %I64x\\n\", addr);\n            ExFreePool(t2);\n            return STATUS_CRC_ERROR;\n        }\n\n        ExFreePool(t2);\n    } else {\n        ULONG sectors = length >> Vcb->sector_shift;\n        uint8_t* sector;\n        void* ptr = context->csum;\n\n        sector = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.sector_size * 2, ALLOC_TAG);\n        if (!sector) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        for (ULONG i = 0; i < sectors; i++) {\n            uint16_t parity;\n            uint64_t off;\n\n            get_raid0_offset(addr - offset + ((uint64_t)i << Vcb->sector_shift), ci->stripe_length,\n                             ci->num_stripes - 1, &off, &stripe);\n\n            parity = (((addr - offset + ((uint64_t)i << Vcb->sector_shift)) / ((ci->num_stripes - 1) * ci->stripe_length)) + ci->num_stripes - 1) % ci->num_stripes;\n\n            stripe = (parity + stripe + 1) % ci->num_stripes;\n\n            if (!devices[stripe] || !devices[stripe]->devobj || (ptr && !check_sector_csum(Vcb, buf + (i << Vcb->sector_shift), ptr))) {\n                bool recovered = false, first = true, failed = false;\n\n                if (devices[stripe] && devices[stripe]->devobj)\n                    log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_READ_ERRORS);\n\n                for (j = 0; j < ci->num_stripes; j++) {\n                    if (j != stripe) {\n                        if (devices[j] && devices[j]->devobj) {\n                            if (first) {\n                                Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + off, Vcb->superblock.sector_size, sector, false);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"sync_read_phys returned %08lx\\n\", Status);\n                                    failed = true;\n                                    log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n                                    break;\n                                }\n\n                                first = false;\n                            } else {\n                                Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + off, Vcb->superblock.sector_size,\n                                                        sector + Vcb->superblock.sector_size, false);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"sync_read_phys returned %08lx\\n\", Status);\n                                    failed = true;\n                                    log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n                                    break;\n                                }\n\n                                do_xor(sector, sector + Vcb->superblock.sector_size, Vcb->superblock.sector_size);\n                            }\n                        } else {\n                            failed = true;\n                            break;\n                        }\n                    }\n                }\n\n                if (!failed) {\n                    if (!ptr || check_sector_csum(Vcb, sector, ptr)) {\n                        RtlCopyMemory(buf + (i << Vcb->sector_shift), sector, Vcb->superblock.sector_size);\n\n                        if (!degraded)\n                            ERR(\"recovering from checksum error at %I64x, device %I64x\\n\", addr + ((uint64_t)i << Vcb->sector_shift), devices[stripe]->devitem.dev_id);\n\n                        recovered = true;\n\n                        if (!Vcb->readonly && devices[stripe] && !devices[stripe]->readonly && devices[stripe]->devobj) { // write good data over bad\n                            Status = write_data_phys(devices[stripe]->devobj, devices[stripe]->fileobj, cis[stripe].offset + off,\n                                                     sector, Vcb->superblock.sector_size);\n                            if (!NT_SUCCESS(Status)) {\n                                WARN(\"write_data_phys returned %08lx\\n\", Status);\n                                log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_WRITE_ERRORS);\n                            }\n                        }\n                    }\n                }\n\n                if (!recovered) {\n                    ERR(\"unrecoverable checksum error at %I64x\\n\", addr + ((uint64_t)i << Vcb->sector_shift));\n                    ExFreePool(sector);\n                    return STATUS_CRC_ERROR;\n                }\n            }\n\n            if (ptr)\n                ptr = (uint8_t*)ptr + Vcb->csum_size;\n        }\n\n        ExFreePool(sector);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nvoid raid6_recover2(uint8_t* sectors, uint16_t num_stripes, ULONG sector_size, uint16_t missing1, uint16_t missing2, uint8_t* out) {\n    if (missing1 == num_stripes - 2 || missing2 == num_stripes - 2) { // reconstruct from q and data\n        uint16_t missing = missing1 == (num_stripes - 2) ? missing2 : missing1;\n        uint16_t stripe;\n\n        stripe = num_stripes - 3;\n\n        if (stripe == missing)\n            RtlZeroMemory(out, sector_size);\n        else\n            RtlCopyMemory(out, sectors + (stripe * sector_size), sector_size);\n\n        do {\n            stripe--;\n\n            galois_double(out, sector_size);\n\n            if (stripe != missing)\n                do_xor(out, sectors + (stripe * sector_size), sector_size);\n        } while (stripe > 0);\n\n        do_xor(out, sectors + ((num_stripes - 1) * sector_size), sector_size);\n\n        if (missing != 0)\n            galois_divpower(out, (uint8_t)missing, sector_size);\n    } else { // reconstruct from p and q\n        uint16_t x = missing1, y = missing2, stripe;\n        uint8_t gyx, gx, denom, a, b, *p, *q, *pxy, *qxy;\n        uint32_t j;\n\n        stripe = num_stripes - 3;\n\n        pxy = out + sector_size;\n        qxy = out;\n\n        if (stripe == missing1 || stripe == missing2) {\n            RtlZeroMemory(qxy, sector_size);\n            RtlZeroMemory(pxy, sector_size);\n        } else {\n            RtlCopyMemory(qxy, sectors + (stripe * sector_size), sector_size);\n            RtlCopyMemory(pxy, sectors + (stripe * sector_size), sector_size);\n        }\n\n        do {\n            stripe--;\n\n            galois_double(qxy, sector_size);\n\n            if (stripe != missing1 && stripe != missing2) {\n                do_xor(qxy, sectors + (stripe * sector_size), sector_size);\n                do_xor(pxy, sectors + (stripe * sector_size), sector_size);\n            }\n        } while (stripe > 0);\n\n        gyx = gpow2(y > x ? (y-x) : (255-x+y));\n        gx = gpow2(255-x);\n\n        denom = gdiv(1, gyx ^ 1);\n        a = gmul(gyx, denom);\n        b = gmul(gx, denom);\n\n        p = sectors + ((num_stripes - 2) * sector_size);\n        q = sectors + ((num_stripes - 1) * sector_size);\n\n        for (j = 0; j < sector_size; j++) {\n            *qxy = gmul(a, *p ^ *pxy) ^ gmul(b, *q ^ *qxy);\n\n            p++;\n            q++;\n            pxy++;\n            qxy++;\n        }\n\n        do_xor(out + sector_size, out, sector_size);\n        do_xor(out + sector_size, sectors + ((num_stripes - 2) * sector_size), sector_size);\n    }\n}\n\nstatic NTSTATUS read_data_raid6(device_extension* Vcb, uint8_t* buf, uint64_t addr, uint32_t length, read_data_context* context, CHUNK_ITEM* ci,\n                                device** devices, uint64_t offset, uint64_t generation, chunk* c, bool degraded) {\n    NTSTATUS Status;\n    bool checksum_error = false;\n    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&ci[1];\n    uint16_t stripe = 0, j;\n    bool no_success = true;\n\n    for (j = 0; j < ci->num_stripes; j++) {\n        if (context->stripes[j].status == ReadDataStatus_Error) {\n            WARN(\"stripe %u returned error %08lx\\n\", j, context->stripes[j].iosb.Status);\n\n            if (devices[j])\n                log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n            return context->stripes[j].iosb.Status;\n        } else if (context->stripes[j].status == ReadDataStatus_Success) {\n            stripe = j;\n            no_success = false;\n        }\n    }\n\n    if (c) {    // check partial stripes\n        LIST_ENTRY* le;\n        uint64_t ps_length = (ci->num_stripes - 2) * ci->stripe_length;\n\n        ExAcquireResourceSharedLite(&c->partial_stripes_lock, true);\n\n        le = c->partial_stripes.Flink;\n        while (le != &c->partial_stripes) {\n            partial_stripe* ps = CONTAINING_RECORD(le, partial_stripe, list_entry);\n\n            if (ps->address + ps_length > addr && ps->address < addr + length) {\n                ULONG runlength, index;\n\n                runlength = RtlFindFirstRunClear(&ps->bmp, &index);\n\n                while (runlength != 0) {\n                    if (index >= ps->bmplen)\n                        break;\n\n                    if (index + runlength >= ps->bmplen) {\n                        runlength = ps->bmplen - index;\n\n                        if (runlength == 0)\n                            break;\n                    }\n\n                    uint64_t runstart = ps->address + (index << Vcb->sector_shift);\n                    uint64_t runend = runstart + (runlength << Vcb->sector_shift);\n                    uint64_t start = max(runstart, addr);\n                    uint64_t end = min(runend, addr + length);\n\n                    if (end > start)\n                        RtlCopyMemory(buf + start - addr, &ps->data[start - ps->address], (ULONG)(end - start));\n\n                    runlength = RtlFindNextForwardRunClear(&ps->bmp, index + runlength, &index);\n                }\n            } else if (ps->address >= addr + length)\n                break;\n\n            le = le->Flink;\n        }\n\n        ExReleaseResourceLite(&c->partial_stripes_lock);\n    }\n\n    if (context->tree) {\n        tree_header* th = (tree_header*)buf;\n\n        if (addr != th->address || !check_tree_checksum(Vcb, th)) {\n            checksum_error = true;\n            if (!no_success && !degraded && devices[stripe])\n                log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n        } else if (generation != 0 && generation != th->generation) {\n            checksum_error = true;\n            if (!no_success && !degraded && devices[stripe])\n                log_device_error(Vcb, devices[stripe], BTRFS_DEV_STAT_GENERATION_ERRORS);\n        }\n    } else if (context->csum) {\n        Status = check_csum(Vcb, buf, length >> Vcb->sector_shift, context->csum);\n\n        if (Status == STATUS_CRC_ERROR) {\n            if (!degraded)\n                WARN(\"checksum error\\n\");\n            checksum_error = true;\n        } else if (!NT_SUCCESS(Status)) {\n            ERR(\"check_csum returned %08lx\\n\", Status);\n            return Status;\n        }\n    } else if (degraded)\n        checksum_error = true;\n\n    if (!checksum_error)\n        return STATUS_SUCCESS;\n\n    if (context->tree) {\n        uint8_t* sector;\n        uint16_t k, physstripe, parity1, parity2, error_stripe = 0;\n        uint64_t off;\n        bool recovered = false, failed = false;\n        ULONG num_errors = 0;\n\n        sector = ExAllocatePoolWithTag(NonPagedPool, Vcb->superblock.node_size * (ci->num_stripes + 2), ALLOC_TAG);\n        if (!sector) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes - 2, &off, &stripe);\n\n        parity1 = (((addr - offset) / ((ci->num_stripes - 2) * ci->stripe_length)) + ci->num_stripes - 2) % ci->num_stripes;\n        parity2 = (parity1 + 1) % ci->num_stripes;\n\n        physstripe = (parity2 + stripe + 1) % ci->num_stripes;\n\n        j = (parity2 + 1) % ci->num_stripes;\n\n        for (k = 0; k < ci->num_stripes - 1; k++) {\n            if (j != physstripe) {\n                if (devices[j] && devices[j]->devobj) {\n                    Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + off, Vcb->superblock.node_size,\n                                            sector + (k * Vcb->superblock.node_size), false);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"sync_read_phys returned %08lx\\n\", Status);\n                        log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n                        num_errors++;\n                        error_stripe = k;\n\n                        if (num_errors > 1) {\n                            failed = true;\n                            break;\n                        }\n                    }\n                } else {\n                    num_errors++;\n                    error_stripe = k;\n\n                    if (num_errors > 1) {\n                        failed = true;\n                        break;\n                    }\n                }\n            }\n\n            j = (j + 1) % ci->num_stripes;\n        }\n\n        if (!failed) {\n            if (num_errors == 0) {\n                tree_header* th = (tree_header*)(sector + (stripe * Vcb->superblock.node_size));\n\n                RtlCopyMemory(sector + (stripe * Vcb->superblock.node_size), sector + ((ci->num_stripes - 2) * Vcb->superblock.node_size),\n                              Vcb->superblock.node_size);\n\n                for (j = 0; j < ci->num_stripes - 2; j++) {\n                    if (j != stripe)\n                        do_xor(sector + (stripe * Vcb->superblock.node_size), sector + (j * Vcb->superblock.node_size), Vcb->superblock.node_size);\n                }\n\n                if (th->address == addr && check_tree_checksum(Vcb, th) && (generation == 0 || th->generation == generation)) {\n                    RtlCopyMemory(buf, sector + (stripe * Vcb->superblock.node_size), Vcb->superblock.node_size);\n\n                    if (devices[physstripe] && devices[physstripe]->devobj)\n                        ERR(\"recovering from checksum error at %I64x, device %I64x\\n\", addr, devices[physstripe]->devitem.dev_id);\n\n                    recovered = true;\n\n                    if (!Vcb->readonly && devices[physstripe] && devices[physstripe]->devobj && !devices[physstripe]->readonly) { // write good data over bad\n                        Status = write_data_phys(devices[physstripe]->devobj, devices[physstripe]->fileobj, cis[physstripe].offset + off,\n                                                 sector + (stripe * Vcb->superblock.node_size), Vcb->superblock.node_size);\n                        if (!NT_SUCCESS(Status)) {\n                            WARN(\"write_data_phys returned %08lx\\n\", Status);\n                            log_device_error(Vcb, devices[physstripe], BTRFS_DEV_STAT_WRITE_ERRORS);\n                        }\n                    }\n                }\n            }\n\n            if (!recovered) {\n                tree_header* th = (tree_header*)(sector + (ci->num_stripes * Vcb->superblock.node_size));\n                bool read_q = false;\n\n                if (devices[parity2] && devices[parity2]->devobj) {\n                    Status = sync_read_phys(devices[parity2]->devobj, devices[parity2]->fileobj, cis[parity2].offset + off,\n                                            Vcb->superblock.node_size, sector + ((ci->num_stripes - 1) * Vcb->superblock.node_size), false);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"sync_read_phys returned %08lx\\n\", Status);\n                        log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n                    } else\n                        read_q = true;\n                }\n\n                if (read_q) {\n                    if (num_errors == 1) {\n                        raid6_recover2(sector, ci->num_stripes, Vcb->superblock.node_size, stripe, error_stripe, sector + (ci->num_stripes * Vcb->superblock.node_size));\n\n                        if (th->address == addr && check_tree_checksum(Vcb, th) && (generation == 0 || th->generation == generation))\n                            recovered = true;\n                    } else {\n                        for (j = 0; j < ci->num_stripes - 1; j++) {\n                            if (j != stripe) {\n                                raid6_recover2(sector, ci->num_stripes, Vcb->superblock.node_size, stripe, j, sector + (ci->num_stripes * Vcb->superblock.node_size));\n\n                                if (th->address == addr && check_tree_checksum(Vcb, th) && (generation == 0 || th->generation == generation)) {\n                                    recovered = true;\n                                    error_stripe = j;\n                                    break;\n                                }\n                            }\n                        }\n                    }\n                }\n\n                if (recovered) {\n                    uint16_t error_stripe_phys = (parity2 + error_stripe + 1) % ci->num_stripes;\n\n                    if (devices[physstripe] && devices[physstripe]->devobj)\n                        ERR(\"recovering from checksum error at %I64x, device %I64x\\n\", addr, devices[physstripe]->devitem.dev_id);\n\n                    RtlCopyMemory(buf, sector + (ci->num_stripes * Vcb->superblock.node_size), Vcb->superblock.node_size);\n\n                    if (!Vcb->readonly && devices[physstripe] && devices[physstripe]->devobj && !devices[physstripe]->readonly) { // write good data over bad\n                        Status = write_data_phys(devices[physstripe]->devobj, devices[physstripe]->fileobj, cis[physstripe].offset + off,\n                                                 sector + (ci->num_stripes * Vcb->superblock.node_size), Vcb->superblock.node_size);\n                        if (!NT_SUCCESS(Status)) {\n                            WARN(\"write_data_phys returned %08lx\\n\", Status);\n                            log_device_error(Vcb, devices[physstripe], BTRFS_DEV_STAT_WRITE_ERRORS);\n                        }\n                    }\n\n                    if (devices[error_stripe_phys] && devices[error_stripe_phys]->devobj) {\n                        if (error_stripe == ci->num_stripes - 2) {\n                            ERR(\"recovering from parity error at %I64x, device %I64x\\n\", addr, devices[error_stripe_phys]->devitem.dev_id);\n\n                            log_device_error(Vcb, devices[error_stripe_phys], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n\n                            RtlZeroMemory(sector + ((ci->num_stripes - 2) * Vcb->superblock.node_size), Vcb->superblock.node_size);\n\n                            for (j = 0; j < ci->num_stripes - 2; j++) {\n                                if (j == stripe) {\n                                    do_xor(sector + ((ci->num_stripes - 2) * Vcb->superblock.node_size), sector + (ci->num_stripes * Vcb->superblock.node_size),\n                                           Vcb->superblock.node_size);\n                                } else {\n                                    do_xor(sector + ((ci->num_stripes - 2) * Vcb->superblock.node_size), sector + (j * Vcb->superblock.node_size),\n                                            Vcb->superblock.node_size);\n                                }\n                            }\n                        } else {\n                            ERR(\"recovering from checksum error at %I64x, device %I64x\\n\", addr + ((error_stripe - stripe) * ci->stripe_length),\n                                devices[error_stripe_phys]->devitem.dev_id);\n\n                            log_device_error(Vcb, devices[error_stripe_phys], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n\n                            RtlCopyMemory(sector + (error_stripe * Vcb->superblock.node_size),\n                                          sector + ((ci->num_stripes + 1) * Vcb->superblock.node_size), Vcb->superblock.node_size);\n                        }\n                    }\n\n                    if (!Vcb->readonly && devices[error_stripe_phys] && devices[error_stripe_phys]->devobj && !devices[error_stripe_phys]->readonly) { // write good data over bad\n                        Status = write_data_phys(devices[error_stripe_phys]->devobj, devices[error_stripe_phys]->fileobj, cis[error_stripe_phys].offset + off,\n                                                 sector + (error_stripe * Vcb->superblock.node_size), Vcb->superblock.node_size);\n                        if (!NT_SUCCESS(Status)) {\n                            WARN(\"write_data_phys returned %08lx\\n\", Status);\n                            log_device_error(Vcb, devices[error_stripe_phys], BTRFS_DEV_STAT_WRITE_ERRORS);\n                        }\n                    }\n                }\n            }\n        }\n\n        if (!recovered) {\n            ERR(\"unrecoverable checksum error at %I64x\\n\", addr);\n            ExFreePool(sector);\n            return STATUS_CRC_ERROR;\n        }\n\n        ExFreePool(sector);\n    } else {\n        ULONG sectors = length >> Vcb->sector_shift;\n        uint8_t* sector;\n        void* ptr = context->csum;\n\n        sector = ExAllocatePoolWithTag(NonPagedPool, (ci->num_stripes + 2) << Vcb->sector_shift, ALLOC_TAG);\n        if (!sector) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        for (ULONG i = 0; i < sectors; i++) {\n            uint64_t off;\n            uint16_t physstripe, parity1, parity2;\n\n            get_raid0_offset(addr - offset + ((uint64_t)i << Vcb->sector_shift), ci->stripe_length,\n                             ci->num_stripes - 2, &off, &stripe);\n\n            parity1 = (((addr - offset + ((uint64_t)i << Vcb->sector_shift)) / ((ci->num_stripes - 2) * ci->stripe_length)) + ci->num_stripes - 2) % ci->num_stripes;\n            parity2 = (parity1 + 1) % ci->num_stripes;\n\n            physstripe = (parity2 + stripe + 1) % ci->num_stripes;\n\n            if (!devices[physstripe] || !devices[physstripe]->devobj || (context->csum && !check_sector_csum(Vcb, buf + (i << Vcb->sector_shift), ptr))) {\n                uint16_t error_stripe = 0;\n                bool recovered = false, failed = false;\n                ULONG num_errors = 0;\n\n                if (devices[physstripe] && devices[physstripe]->devobj)\n                    log_device_error(Vcb, devices[physstripe], BTRFS_DEV_STAT_READ_ERRORS);\n\n                j = (parity2 + 1) % ci->num_stripes;\n\n                for (uint16_t k = 0; k < ci->num_stripes - 1; k++) {\n                    if (j != physstripe) {\n                        if (devices[j] && devices[j]->devobj) {\n                            Status = sync_read_phys(devices[j]->devobj, devices[j]->fileobj, cis[j].offset + off, Vcb->superblock.sector_size,\n                                                    sector + ((ULONG)k << Vcb->sector_shift), false);\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"sync_read_phys returned %08lx\\n\", Status);\n                                log_device_error(Vcb, devices[j], BTRFS_DEV_STAT_READ_ERRORS);\n                                num_errors++;\n                                error_stripe = k;\n\n                                if (num_errors > 1) {\n                                    failed = true;\n                                    break;\n                                }\n                            }\n                        } else {\n                            num_errors++;\n                            error_stripe = k;\n\n                            if (num_errors > 1) {\n                                failed = true;\n                                break;\n                            }\n                        }\n                    }\n\n                    j = (j + 1) % ci->num_stripes;\n                }\n\n                if (!failed) {\n                    if (num_errors == 0) {\n                        RtlCopyMemory(sector + ((unsigned int)stripe << Vcb->sector_shift), sector + ((unsigned int)(ci->num_stripes - 2) << Vcb->sector_shift), Vcb->superblock.sector_size);\n\n                        for (j = 0; j < ci->num_stripes - 2; j++) {\n                            if (j != stripe)\n                                do_xor(sector + ((unsigned int)stripe << Vcb->sector_shift), sector + ((unsigned int)j << Vcb->sector_shift), Vcb->superblock.sector_size);\n                        }\n\n                        if (!ptr || check_sector_csum(Vcb, sector + ((unsigned int)stripe << Vcb->sector_shift), ptr)) {\n                            RtlCopyMemory(buf + (i << Vcb->sector_shift), sector + ((unsigned int)stripe << Vcb->sector_shift), Vcb->superblock.sector_size);\n\n                            if (devices[physstripe] && devices[physstripe]->devobj)\n                                ERR(\"recovering from checksum error at %I64x, device %I64x\\n\", addr + ((uint64_t)i << Vcb->sector_shift),\n                                    devices[physstripe]->devitem.dev_id);\n\n                            recovered = true;\n\n                            if (!Vcb->readonly && devices[physstripe] && devices[physstripe]->devobj && !devices[physstripe]->readonly) { // write good data over bad\n                                Status = write_data_phys(devices[physstripe]->devobj, devices[physstripe]->fileobj, cis[physstripe].offset + off,\n                                                         sector + ((unsigned int)stripe << Vcb->sector_shift), Vcb->superblock.sector_size);\n                                if (!NT_SUCCESS(Status)) {\n                                    WARN(\"write_data_phys returned %08lx\\n\", Status);\n                                    log_device_error(Vcb, devices[physstripe], BTRFS_DEV_STAT_WRITE_ERRORS);\n                                }\n                            }\n                        }\n                    }\n\n                    if (!recovered) {\n                        bool read_q = false;\n\n                        if (devices[parity2] && devices[parity2]->devobj) {\n                            Status = sync_read_phys(devices[parity2]->devobj, devices[parity2]->fileobj, cis[parity2].offset + off,\n                                                    Vcb->superblock.sector_size, sector + ((unsigned int)(ci->num_stripes - 1) << Vcb->sector_shift), false);\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"sync_read_phys returned %08lx\\n\", Status);\n                                log_device_error(Vcb, devices[parity2], BTRFS_DEV_STAT_READ_ERRORS);\n                            } else\n                                read_q = true;\n                        }\n\n                        if (read_q) {\n                            if (num_errors == 1) {\n                                raid6_recover2(sector, ci->num_stripes, Vcb->superblock.sector_size, stripe, error_stripe, sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift));\n\n                                if (!devices[physstripe] || !devices[physstripe]->devobj)\n                                    recovered = true;\n                                else\n                                    recovered = check_sector_csum(Vcb, sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift), ptr);\n                            } else {\n                                for (j = 0; j < ci->num_stripes - 1; j++) {\n                                    if (j != stripe) {\n                                        raid6_recover2(sector, ci->num_stripes, Vcb->superblock.sector_size, stripe, j, sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift));\n\n                                        if (check_sector_csum(Vcb, sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift), ptr)) {\n                                            recovered = true;\n                                            error_stripe = j;\n                                            break;\n                                        }\n                                    }\n                                }\n                            }\n                        }\n\n                        if (recovered) {\n                            uint16_t error_stripe_phys = (parity2 + error_stripe + 1) % ci->num_stripes;\n\n                            if (devices[physstripe] && devices[physstripe]->devobj)\n                                ERR(\"recovering from checksum error at %I64x, device %I64x\\n\",\n                                    addr + ((uint64_t)i << Vcb->sector_shift), devices[physstripe]->devitem.dev_id);\n\n                            RtlCopyMemory(buf + (i << Vcb->sector_shift), sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift), Vcb->superblock.sector_size);\n\n                            if (!Vcb->readonly && devices[physstripe] && devices[physstripe]->devobj && !devices[physstripe]->readonly) { // write good data over bad\n                                Status = write_data_phys(devices[physstripe]->devobj, devices[physstripe]->fileobj, cis[physstripe].offset + off,\n                                                         sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift), Vcb->superblock.sector_size);\n                                if (!NT_SUCCESS(Status)) {\n                                    WARN(\"write_data_phys returned %08lx\\n\", Status);\n                                    log_device_error(Vcb, devices[physstripe], BTRFS_DEV_STAT_WRITE_ERRORS);\n                                }\n                            }\n\n                            if (devices[error_stripe_phys] && devices[error_stripe_phys]->devobj) {\n                                if (error_stripe == ci->num_stripes - 2) {\n                                    ERR(\"recovering from parity error at %I64x, device %I64x\\n\", addr + ((uint64_t)i << Vcb->sector_shift),\n                                        devices[error_stripe_phys]->devitem.dev_id);\n\n                                    log_device_error(Vcb, devices[error_stripe_phys], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n\n                                    RtlZeroMemory(sector + ((unsigned int)(ci->num_stripes - 2) << Vcb->sector_shift), Vcb->superblock.sector_size);\n\n                                    for (j = 0; j < ci->num_stripes - 2; j++) {\n                                        if (j == stripe) {\n                                            do_xor(sector + ((unsigned int)(ci->num_stripes - 2) << Vcb->sector_shift), sector + ((unsigned int)ci->num_stripes << Vcb->sector_shift),\n                                                   Vcb->superblock.sector_size);\n                                        } else {\n                                            do_xor(sector + ((unsigned int)(ci->num_stripes - 2) << Vcb->sector_shift), sector + ((unsigned int)j << Vcb->sector_shift),\n                                                   Vcb->superblock.sector_size);\n                                        }\n                                    }\n                                } else {\n                                    ERR(\"recovering from checksum error at %I64x, device %I64x\\n\",\n                                        addr + ((uint64_t)i << Vcb->sector_shift) + ((error_stripe - stripe) * ci->stripe_length),\n                                        devices[error_stripe_phys]->devitem.dev_id);\n\n                                    log_device_error(Vcb, devices[error_stripe_phys], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n\n                                    RtlCopyMemory(sector + ((unsigned int)error_stripe << Vcb->sector_shift),\n                                                  sector + ((unsigned int)(ci->num_stripes + 1) << Vcb->sector_shift), Vcb->superblock.sector_size);\n                                }\n                            }\n\n                            if (!Vcb->readonly && devices[error_stripe_phys] && devices[error_stripe_phys]->devobj && !devices[error_stripe_phys]->readonly) { // write good data over bad\n                                Status = write_data_phys(devices[error_stripe_phys]->devobj, devices[error_stripe_phys]->fileobj, cis[error_stripe_phys].offset + off,\n                                                         sector + ((unsigned int)error_stripe << Vcb->sector_shift), Vcb->superblock.sector_size);\n                                if (!NT_SUCCESS(Status)) {\n                                    WARN(\"write_data_phys returned %08lx\\n\", Status);\n                                    log_device_error(Vcb, devices[error_stripe_phys], BTRFS_DEV_STAT_WRITE_ERRORS);\n                                }\n                            }\n                        }\n                    }\n                }\n\n                if (!recovered) {\n                    ERR(\"unrecoverable checksum error at %I64x\\n\", addr + ((uint64_t)i << Vcb->sector_shift));\n                    ExFreePool(sector);\n                    return STATUS_CRC_ERROR;\n                }\n            }\n\n            if (ptr)\n                ptr = (uint8_t*)ptr + Vcb->csum_size;\n        }\n\n        ExFreePool(sector);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS read_data(_In_ device_extension* Vcb, _In_ uint64_t addr, _In_ uint32_t length, _In_reads_bytes_opt_(length*sizeof(uint32_t)/Vcb->superblock.sector_size) void* csum,\n                   _In_ bool is_tree, _Out_writes_bytes_(length) uint8_t* buf, _In_opt_ chunk* c, _Out_opt_ chunk** pc, _In_opt_ PIRP Irp, _In_ uint64_t generation, _In_ bool file_read,\n                   _In_ ULONG priority) {\n    CHUNK_ITEM* ci;\n    CHUNK_ITEM_STRIPE* cis;\n    read_data_context context;\n    uint64_t type, offset, total_reading = 0;\n    NTSTATUS Status;\n    device** devices = NULL;\n    uint16_t i, startoffstripe, allowed_missing, missing_devices = 0;\n    uint8_t* dummypage = NULL;\n    PMDL dummy_mdl = NULL;\n    bool need_to_wait;\n    uint64_t lockaddr, locklen;\n\n    if (Vcb->log_to_phys_loaded) {\n        if (!c) {\n            c = get_chunk_from_address(Vcb, addr);\n\n            if (!c) {\n                ERR(\"get_chunk_from_address failed\\n\");\n                return STATUS_INTERNAL_ERROR;\n            }\n        }\n\n        ci = c->chunk_item;\n        offset = c->offset;\n        devices = c->devices;\n\n        if (pc)\n            *pc = c;\n    } else {\n        LIST_ENTRY* le = Vcb->sys_chunks.Flink;\n\n        ci = NULL;\n\n        c = NULL;\n        while (le != &Vcb->sys_chunks) {\n            sys_chunk* sc = CONTAINING_RECORD(le, sys_chunk, list_entry);\n\n            if (sc->key.obj_id == 0x100 && sc->key.obj_type == TYPE_CHUNK_ITEM && sc->key.offset <= addr) {\n                CHUNK_ITEM* chunk_item = sc->data;\n\n                if ((addr - sc->key.offset) < chunk_item->size && chunk_item->num_stripes > 0) {\n                    ci = chunk_item;\n                    offset = sc->key.offset;\n                    cis = (CHUNK_ITEM_STRIPE*)&chunk_item[1];\n\n                    devices = ExAllocatePoolWithTag(NonPagedPool, sizeof(device*) * ci->num_stripes, ALLOC_TAG);\n                    if (!devices) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    for (i = 0; i < ci->num_stripes; i++) {\n                        devices[i] = find_device_from_uuid(Vcb, &cis[i].dev_uuid);\n                    }\n\n                    break;\n                }\n            }\n\n            le = le->Flink;\n        }\n\n        if (!ci) {\n            ERR(\"could not find chunk for %I64x in bootstrap\\n\", addr);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (pc)\n            *pc = NULL;\n    }\n\n    if (ci->type & BLOCK_FLAG_DUPLICATE) {\n        type = BLOCK_FLAG_DUPLICATE;\n        allowed_missing = ci->num_stripes - 1;\n    } else if (ci->type & BLOCK_FLAG_RAID0) {\n        type = BLOCK_FLAG_RAID0;\n        allowed_missing = 0;\n    } else if (ci->type & BLOCK_FLAG_RAID1) {\n        type = BLOCK_FLAG_DUPLICATE;\n        allowed_missing = 1;\n    } else if (ci->type & BLOCK_FLAG_RAID10) {\n        type = BLOCK_FLAG_RAID10;\n        allowed_missing = 1;\n    } else if (ci->type & BLOCK_FLAG_RAID5) {\n        type = BLOCK_FLAG_RAID5;\n        allowed_missing = 1;\n    } else if (ci->type & BLOCK_FLAG_RAID6) {\n        type = BLOCK_FLAG_RAID6;\n        allowed_missing = 2;\n    } else if (ci->type & BLOCK_FLAG_RAID1C3) {\n        type = BLOCK_FLAG_DUPLICATE;\n        allowed_missing = 2;\n    } else if (ci->type & BLOCK_FLAG_RAID1C4) {\n        type = BLOCK_FLAG_DUPLICATE;\n        allowed_missing = 3;\n    } else { // SINGLE\n        type = BLOCK_FLAG_DUPLICATE;\n        allowed_missing = 0;\n    }\n\n    cis = (CHUNK_ITEM_STRIPE*)&ci[1];\n\n    RtlZeroMemory(&context, sizeof(read_data_context));\n    KeInitializeEvent(&context.Event, NotificationEvent, false);\n\n    context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(read_data_stripe) * ci->num_stripes, ALLOC_TAG);\n    if (!context.stripes) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    if (c && (type == BLOCK_FLAG_RAID5 || type == BLOCK_FLAG_RAID6)) {\n        get_raid56_lock_range(c, addr, length, &lockaddr, &locklen);\n        chunk_lock_range(Vcb, c, lockaddr, locklen);\n    }\n\n    RtlZeroMemory(context.stripes, sizeof(read_data_stripe) * ci->num_stripes);\n\n    context.buflen = length;\n    context.num_stripes = ci->num_stripes;\n    context.stripes_left = context.num_stripes;\n    context.sector_size = Vcb->superblock.sector_size;\n    context.csum = csum;\n    context.tree = is_tree;\n    context.type = type;\n\n    if (type == BLOCK_FLAG_RAID0) {\n        uint64_t startoff, endoff;\n        uint16_t endoffstripe, stripe;\n        uint32_t *stripeoff, pos;\n        PMDL master_mdl;\n        PFN_NUMBER* pfns;\n\n        // FIXME - test this still works if page size isn't the same as sector size\n\n        // This relies on the fact that MDLs are followed in memory by the page file numbers,\n        // so with a bit of jiggery-pokery you can trick your disks into deinterlacing your RAID0\n        // data for you without doing a memcpy yourself.\n        // MDLs are officially opaque, so this might very well break in future versions of Windows.\n\n        get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes, &startoff, &startoffstripe);\n        get_raid0_offset(addr + length - offset - 1, ci->stripe_length, ci->num_stripes, &endoff, &endoffstripe);\n\n        if (file_read) {\n            // Unfortunately we can't avoid doing at least one memcpy, as Windows can give us an MDL\n            // with duplicated dummy PFNs, which confuse check_csum. Ah well.\n            // See https://msdn.microsoft.com/en-us/library/windows/hardware/Dn614012.aspx if you're interested.\n\n            context.va = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG);\n\n            if (!context.va) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n        } else\n            context.va = buf;\n\n        master_mdl = IoAllocateMdl(context.va, length, false, false, NULL);\n        if (!master_mdl) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        Status = STATUS_SUCCESS;\n\n        try {\n            MmProbeAndLockPages(master_mdl, KernelMode, IoWriteAccess);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n            IoFreeMdl(master_mdl);\n            goto exit;\n        }\n\n        pfns = (PFN_NUMBER*)(master_mdl + 1);\n\n        for (i = 0; i < ci->num_stripes; i++) {\n            if (startoffstripe > i)\n                context.stripes[i].stripestart = startoff - (startoff % ci->stripe_length) + ci->stripe_length;\n            else if (startoffstripe == i)\n                context.stripes[i].stripestart = startoff;\n            else\n                context.stripes[i].stripestart = startoff - (startoff % ci->stripe_length);\n\n            if (endoffstripe > i)\n                context.stripes[i].stripeend = endoff - (endoff % ci->stripe_length) + ci->stripe_length;\n            else if (endoffstripe == i)\n                context.stripes[i].stripeend = endoff + 1;\n            else\n                context.stripes[i].stripeend = endoff - (endoff % ci->stripe_length);\n\n            if (context.stripes[i].stripestart != context.stripes[i].stripeend) {\n                context.stripes[i].mdl = IoAllocateMdl(context.va, (ULONG)(context.stripes[i].stripeend - context.stripes[i].stripestart), false, false, NULL);\n\n                if (!context.stripes[i].mdl) {\n                    ERR(\"IoAllocateMdl failed\\n\");\n                    MmUnlockPages(master_mdl);\n                    IoFreeMdl(master_mdl);\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto exit;\n                }\n            }\n        }\n\n        stripeoff = ExAllocatePoolWithTag(NonPagedPool, sizeof(uint32_t) * ci->num_stripes, ALLOC_TAG);\n        if (!stripeoff) {\n            ERR(\"out of memory\\n\");\n            MmUnlockPages(master_mdl);\n            IoFreeMdl(master_mdl);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        RtlZeroMemory(stripeoff, sizeof(uint32_t) * ci->num_stripes);\n\n        pos = 0;\n        stripe = startoffstripe;\n        while (pos < length) {\n            PFN_NUMBER* stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1);\n\n            if (pos == 0) {\n                uint32_t readlen = (uint32_t)min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart, ci->stripe_length - (context.stripes[stripe].stripestart % ci->stripe_length));\n\n                RtlCopyMemory(stripe_pfns, pfns, readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                stripeoff[stripe] += readlen;\n                pos += readlen;\n            } else if (length - pos < ci->stripe_length) {\n                RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (length - pos) * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                pos = length;\n            } else {\n                RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(ci->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n\n                stripeoff[stripe] += (uint32_t)ci->stripe_length;\n                pos += (uint32_t)ci->stripe_length;\n            }\n\n            stripe = (stripe + 1) % ci->num_stripes;\n        }\n\n        MmUnlockPages(master_mdl);\n        IoFreeMdl(master_mdl);\n\n        ExFreePool(stripeoff);\n    } else if (type == BLOCK_FLAG_RAID10) {\n        uint64_t startoff, endoff;\n        uint16_t endoffstripe, j, stripe;\n        ULONG orig_ls;\n        PMDL master_mdl;\n        PFN_NUMBER* pfns;\n        uint32_t* stripeoff, pos;\n        read_data_stripe** stripes;\n\n        if (c)\n            orig_ls = c->last_stripe;\n        else\n            orig_ls = 0;\n\n        get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes / ci->sub_stripes, &startoff, &startoffstripe);\n        get_raid0_offset(addr + length - offset - 1, ci->stripe_length, ci->num_stripes / ci->sub_stripes, &endoff, &endoffstripe);\n\n        if ((ci->num_stripes % ci->sub_stripes) != 0) {\n            ERR(\"chunk %I64x: num_stripes %x was not a multiple of sub_stripes %x!\\n\", offset, ci->num_stripes, ci->sub_stripes);\n            Status = STATUS_INTERNAL_ERROR;\n            goto exit;\n        }\n\n        if (file_read) {\n            context.va = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG);\n\n            if (!context.va) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n        } else\n            context.va = buf;\n\n        context.firstoff = (uint16_t)((startoff % ci->stripe_length) >> Vcb->sector_shift);\n        context.startoffstripe = startoffstripe;\n        context.sectors_per_stripe = (uint16_t)(ci->stripe_length >> Vcb->sector_shift);\n\n        startoffstripe *= ci->sub_stripes;\n        endoffstripe *= ci->sub_stripes;\n\n        if (c)\n            c->last_stripe = (orig_ls + 1) % ci->sub_stripes;\n\n        master_mdl = IoAllocateMdl(context.va, length, false, false, NULL);\n        if (!master_mdl) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        Status = STATUS_SUCCESS;\n\n        try {\n            MmProbeAndLockPages(master_mdl, KernelMode, IoWriteAccess);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n            IoFreeMdl(master_mdl);\n            goto exit;\n        }\n\n        pfns = (PFN_NUMBER*)(master_mdl + 1);\n\n        stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(read_data_stripe*) * ci->num_stripes / ci->sub_stripes, ALLOC_TAG);\n        if (!stripes) {\n            ERR(\"out of memory\\n\");\n            MmUnlockPages(master_mdl);\n            IoFreeMdl(master_mdl);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        RtlZeroMemory(stripes, sizeof(read_data_stripe*) * ci->num_stripes / ci->sub_stripes);\n\n        for (i = 0; i < ci->num_stripes; i += ci->sub_stripes) {\n            uint64_t sstart, send;\n            bool stripeset = false;\n\n            if (startoffstripe > i)\n                sstart = startoff - (startoff % ci->stripe_length) + ci->stripe_length;\n            else if (startoffstripe == i)\n                sstart = startoff;\n            else\n                sstart = startoff - (startoff % ci->stripe_length);\n\n            if (endoffstripe > i)\n                send = endoff - (endoff % ci->stripe_length) + ci->stripe_length;\n            else if (endoffstripe == i)\n                send = endoff + 1;\n            else\n                send = endoff - (endoff % ci->stripe_length);\n\n            for (j = 0; j < ci->sub_stripes; j++) {\n                if (j == orig_ls && devices[i+j] && devices[i+j]->devobj) {\n                    context.stripes[i+j].stripestart = sstart;\n                    context.stripes[i+j].stripeend = send;\n                    stripes[i / ci->sub_stripes] = &context.stripes[i+j];\n\n                    if (sstart != send) {\n                        context.stripes[i+j].mdl = IoAllocateMdl(context.va, (ULONG)(send - sstart), false, false, NULL);\n\n                        if (!context.stripes[i+j].mdl) {\n                            ERR(\"IoAllocateMdl failed\\n\");\n                            MmUnlockPages(master_mdl);\n                            IoFreeMdl(master_mdl);\n                            Status = STATUS_INSUFFICIENT_RESOURCES;\n                            goto exit;\n                        }\n                    }\n\n                    stripeset = true;\n                } else\n                    context.stripes[i+j].status = ReadDataStatus_Skip;\n            }\n\n            if (!stripeset) {\n                for (j = 0; j < ci->sub_stripes; j++) {\n                    if (devices[i+j] && devices[i+j]->devobj) {\n                        context.stripes[i+j].stripestart = sstart;\n                        context.stripes[i+j].stripeend = send;\n                        context.stripes[i+j].status = ReadDataStatus_Pending;\n                        stripes[i / ci->sub_stripes] = &context.stripes[i+j];\n\n                        if (sstart != send) {\n                            context.stripes[i+j].mdl = IoAllocateMdl(context.va, (ULONG)(send - sstart), false, false, NULL);\n\n                            if (!context.stripes[i+j].mdl) {\n                                ERR(\"IoAllocateMdl failed\\n\");\n                                MmUnlockPages(master_mdl);\n                                IoFreeMdl(master_mdl);\n                                Status = STATUS_INSUFFICIENT_RESOURCES;\n                                goto exit;\n                            }\n                        }\n\n                        stripeset = true;\n                        break;\n                    }\n                }\n\n                if (!stripeset) {\n                    ERR(\"could not find stripe to read\\n\");\n                    Status = STATUS_DEVICE_NOT_READY;\n                    goto exit;\n                }\n            }\n        }\n\n        stripeoff = ExAllocatePoolWithTag(NonPagedPool, sizeof(uint32_t) * ci->num_stripes / ci->sub_stripes, ALLOC_TAG);\n        if (!stripeoff) {\n            ERR(\"out of memory\\n\");\n            MmUnlockPages(master_mdl);\n            IoFreeMdl(master_mdl);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        RtlZeroMemory(stripeoff, sizeof(uint32_t) * ci->num_stripes / ci->sub_stripes);\n\n        pos = 0;\n        stripe = startoffstripe / ci->sub_stripes;\n        while (pos < length) {\n            PFN_NUMBER* stripe_pfns = (PFN_NUMBER*)(stripes[stripe]->mdl + 1);\n\n            if (pos == 0) {\n                uint32_t readlen = (uint32_t)min(stripes[stripe]->stripeend - stripes[stripe]->stripestart,\n                                             ci->stripe_length - (stripes[stripe]->stripestart % ci->stripe_length));\n\n                RtlCopyMemory(stripe_pfns, pfns, readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                stripeoff[stripe] += readlen;\n                pos += readlen;\n            } else if (length - pos < ci->stripe_length) {\n                RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (length - pos) * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                pos = length;\n            } else {\n                RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(ci->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n\n                stripeoff[stripe] += (ULONG)ci->stripe_length;\n                pos += (ULONG)ci->stripe_length;\n            }\n\n            stripe = (stripe + 1) % (ci->num_stripes / ci->sub_stripes);\n        }\n\n        MmUnlockPages(master_mdl);\n        IoFreeMdl(master_mdl);\n\n        ExFreePool(stripeoff);\n        ExFreePool(stripes);\n    } else if (type == BLOCK_FLAG_DUPLICATE) {\n        uint64_t orig_ls;\n\n        if (c)\n            orig_ls = i = c->last_stripe;\n        else\n            orig_ls = i = 0;\n\n        while (!devices[i] || !devices[i]->devobj) {\n            i = (i + 1) % ci->num_stripes;\n\n            if (i == orig_ls) {\n                ERR(\"no devices available to service request\\n\");\n                Status = STATUS_DEVICE_NOT_READY;\n                goto exit;\n            }\n        }\n\n        if (c)\n            c->last_stripe = (i + 1) % ci->num_stripes;\n\n        context.stripes[i].stripestart = addr - offset;\n        context.stripes[i].stripeend = context.stripes[i].stripestart + length;\n\n        if (file_read) {\n            context.va = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG);\n\n            if (!context.va) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n\n            context.stripes[i].mdl = IoAllocateMdl(context.va, length, false, false, NULL);\n            if (!context.stripes[i].mdl) {\n                ERR(\"IoAllocateMdl failed\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n\n            MmBuildMdlForNonPagedPool(context.stripes[i].mdl);\n        } else {\n            context.stripes[i].mdl = IoAllocateMdl(buf, length, false, false, NULL);\n\n            if (!context.stripes[i].mdl) {\n                ERR(\"IoAllocateMdl failed\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n\n            Status = STATUS_SUCCESS;\n\n            try {\n                MmProbeAndLockPages(context.stripes[i].mdl, KernelMode, IoWriteAccess);\n            } except (EXCEPTION_EXECUTE_HANDLER) {\n                Status = GetExceptionCode();\n            }\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n                goto exit;\n            }\n        }\n    } else if (type == BLOCK_FLAG_RAID5) {\n        uint64_t startoff, endoff;\n        uint16_t endoffstripe, parity;\n        uint32_t *stripeoff, pos;\n        PMDL master_mdl;\n        PFN_NUMBER *pfns, dummy = 0;\n        bool need_dummy = false;\n\n        get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes - 1, &startoff, &startoffstripe);\n        get_raid0_offset(addr + length - offset - 1, ci->stripe_length, ci->num_stripes - 1, &endoff, &endoffstripe);\n\n        if (file_read) {\n            context.va = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG);\n\n            if (!context.va) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n        } else\n            context.va = buf;\n\n        master_mdl = IoAllocateMdl(context.va, length, false, false, NULL);\n        if (!master_mdl) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        Status = STATUS_SUCCESS;\n\n        try {\n            MmProbeAndLockPages(master_mdl, KernelMode, IoWriteAccess);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n            IoFreeMdl(master_mdl);\n            goto exit;\n        }\n\n        pfns = (PFN_NUMBER*)(master_mdl + 1);\n\n        pos = 0;\n        while (pos < length) {\n            parity = (((addr - offset + pos) / ((ci->num_stripes - 1) * ci->stripe_length)) + ci->num_stripes - 1) % ci->num_stripes;\n\n            if (pos == 0) {\n                uint16_t stripe = (parity + startoffstripe + 1) % ci->num_stripes;\n                ULONG skip, readlen;\n\n                i = startoffstripe;\n                while (stripe != parity) {\n                    if (i == startoffstripe) {\n                        readlen = min(length, (ULONG)(ci->stripe_length - (startoff % ci->stripe_length)));\n\n                        context.stripes[stripe].stripestart = startoff;\n                        context.stripes[stripe].stripeend = startoff + readlen;\n\n                        pos += readlen;\n\n                        if (pos == length)\n                            break;\n                    } else {\n                        readlen = min(length - pos, (ULONG)ci->stripe_length);\n\n                        context.stripes[stripe].stripestart = startoff - (startoff % ci->stripe_length);\n                        context.stripes[stripe].stripeend = context.stripes[stripe].stripestart + readlen;\n\n                        pos += readlen;\n\n                        if (pos == length)\n                            break;\n                    }\n\n                    i++;\n                    stripe = (stripe + 1) % ci->num_stripes;\n                }\n\n                if (pos == length)\n                    break;\n\n                for (i = 0; i < startoffstripe; i++) {\n                    uint16_t stripe2 = (parity + i + 1) % ci->num_stripes;\n\n                    context.stripes[stripe2].stripestart = context.stripes[stripe2].stripeend = startoff - (startoff % ci->stripe_length) + ci->stripe_length;\n                }\n\n                context.stripes[parity].stripestart = context.stripes[parity].stripeend = startoff - (startoff % ci->stripe_length) + ci->stripe_length;\n\n                if (length - pos > ci->num_stripes * (ci->num_stripes - 1) * ci->stripe_length) {\n                    skip = (ULONG)(((length - pos) / (ci->num_stripes * (ci->num_stripes - 1) * ci->stripe_length)) - 1);\n\n                    for (i = 0; i < ci->num_stripes; i++) {\n                        context.stripes[i].stripeend += skip * ci->num_stripes * ci->stripe_length;\n                    }\n\n                    pos += (uint32_t)(skip * (ci->num_stripes - 1) * ci->num_stripes * ci->stripe_length);\n                    need_dummy = true;\n                }\n            } else if (length - pos >= ci->stripe_length * (ci->num_stripes - 1)) {\n                for (i = 0; i < ci->num_stripes; i++) {\n                    context.stripes[i].stripeend += ci->stripe_length;\n                }\n\n                pos += (uint32_t)(ci->stripe_length * (ci->num_stripes - 1));\n                need_dummy = true;\n            } else {\n                uint16_t stripe = (parity + 1) % ci->num_stripes;\n\n                i = 0;\n                while (stripe != parity) {\n                    if (endoffstripe == i) {\n                        context.stripes[stripe].stripeend = endoff + 1;\n                        break;\n                    } else if (endoffstripe > i)\n                        context.stripes[stripe].stripeend = endoff - (endoff % ci->stripe_length) + ci->stripe_length;\n\n                    i++;\n                    stripe = (stripe + 1) % ci->num_stripes;\n                }\n\n                break;\n            }\n        }\n\n        for (i = 0; i < ci->num_stripes; i++) {\n            if (context.stripes[i].stripestart != context.stripes[i].stripeend) {\n                context.stripes[i].mdl = IoAllocateMdl(context.va, (ULONG)(context.stripes[i].stripeend - context.stripes[i].stripestart),\n                                                       false, false, NULL);\n\n                if (!context.stripes[i].mdl) {\n                    ERR(\"IoAllocateMdl failed\\n\");\n                    MmUnlockPages(master_mdl);\n                    IoFreeMdl(master_mdl);\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto exit;\n                }\n            }\n        }\n\n        if (need_dummy) {\n            dummypage = ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, ALLOC_TAG);\n            if (!dummypage) {\n                ERR(\"out of memory\\n\");\n                MmUnlockPages(master_mdl);\n                IoFreeMdl(master_mdl);\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n\n            dummy_mdl = IoAllocateMdl(dummypage, PAGE_SIZE, false, false, NULL);\n            if (!dummy_mdl) {\n                ERR(\"IoAllocateMdl failed\\n\");\n                MmUnlockPages(master_mdl);\n                IoFreeMdl(master_mdl);\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n\n            MmBuildMdlForNonPagedPool(dummy_mdl);\n\n            dummy = *(PFN_NUMBER*)(dummy_mdl + 1);\n        }\n\n        stripeoff = ExAllocatePoolWithTag(NonPagedPool, sizeof(uint32_t) * ci->num_stripes, ALLOC_TAG);\n        if (!stripeoff) {\n            ERR(\"out of memory\\n\");\n            MmUnlockPages(master_mdl);\n            IoFreeMdl(master_mdl);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        RtlZeroMemory(stripeoff, sizeof(uint32_t) * ci->num_stripes);\n\n        pos = 0;\n\n        while (pos < length) {\n            PFN_NUMBER* stripe_pfns;\n\n            parity = (((addr - offset + pos) / ((ci->num_stripes - 1) * ci->stripe_length)) + ci->num_stripes - 1) % ci->num_stripes;\n\n            if (pos == 0) {\n                uint16_t stripe = (parity + startoffstripe + 1) % ci->num_stripes;\n                uint32_t readlen = min(length - pos, (uint32_t)min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart,\n                                                       ci->stripe_length - (context.stripes[stripe].stripestart % ci->stripe_length)));\n\n                stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1);\n\n                RtlCopyMemory(stripe_pfns, pfns, readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                stripeoff[stripe] = readlen;\n                pos += readlen;\n\n                stripe = (stripe + 1) % ci->num_stripes;\n\n                while (stripe != parity) {\n                    stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1);\n                    readlen = min(length - pos, (uint32_t)min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart, ci->stripe_length));\n\n                    if (readlen == 0)\n                        break;\n\n                    RtlCopyMemory(stripe_pfns, &pfns[pos >> PAGE_SHIFT], readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                    stripeoff[stripe] = readlen;\n                    pos += readlen;\n\n                    stripe = (stripe + 1) % ci->num_stripes;\n                }\n            } else if (length - pos >= ci->stripe_length * (ci->num_stripes - 1)) {\n                uint16_t stripe = (parity + 1) % ci->num_stripes;\n                ULONG k;\n\n                while (stripe != parity) {\n                    stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1);\n\n                    RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(ci->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n\n                    stripeoff[stripe] += (uint32_t)ci->stripe_length;\n                    pos += (uint32_t)ci->stripe_length;\n\n                    stripe = (stripe + 1) % ci->num_stripes;\n                }\n\n                stripe_pfns = (PFN_NUMBER*)(context.stripes[parity].mdl + 1);\n\n                for (k = 0; k < ci->stripe_length >> PAGE_SHIFT; k++) {\n                    stripe_pfns[stripeoff[parity] >> PAGE_SHIFT] = dummy;\n                    stripeoff[parity] += PAGE_SIZE;\n                }\n            } else {\n                uint16_t stripe = (parity + 1) % ci->num_stripes;\n                uint32_t readlen;\n\n                while (pos < length) {\n                    stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1);\n                    readlen = min(length - pos, (ULONG)min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart, ci->stripe_length));\n\n                    if (readlen == 0)\n                        break;\n\n                    RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                    stripeoff[stripe] += readlen;\n                    pos += readlen;\n\n                    stripe = (stripe + 1) % ci->num_stripes;\n                }\n            }\n        }\n\n        MmUnlockPages(master_mdl);\n        IoFreeMdl(master_mdl);\n\n        ExFreePool(stripeoff);\n    } else if (type == BLOCK_FLAG_RAID6) {\n        uint64_t startoff, endoff;\n        uint16_t endoffstripe, parity1;\n        uint32_t *stripeoff, pos;\n        PMDL master_mdl;\n        PFN_NUMBER *pfns, dummy = 0;\n        bool need_dummy = false;\n\n        get_raid0_offset(addr - offset, ci->stripe_length, ci->num_stripes - 2, &startoff, &startoffstripe);\n        get_raid0_offset(addr + length - offset - 1, ci->stripe_length, ci->num_stripes - 2, &endoff, &endoffstripe);\n\n        if (file_read) {\n            context.va = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG);\n\n            if (!context.va) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n        } else\n            context.va = buf;\n\n        master_mdl = IoAllocateMdl(context.va, length, false, false, NULL);\n        if (!master_mdl) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        Status = STATUS_SUCCESS;\n\n        try {\n            MmProbeAndLockPages(master_mdl, KernelMode, IoWriteAccess);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n            IoFreeMdl(master_mdl);\n            goto exit;\n        }\n\n        pfns = (PFN_NUMBER*)(master_mdl + 1);\n\n        pos = 0;\n        while (pos < length) {\n            parity1 = (((addr - offset + pos) / ((ci->num_stripes - 2) * ci->stripe_length)) + ci->num_stripes - 2) % ci->num_stripes;\n\n            if (pos == 0) {\n                uint16_t stripe = (parity1 + startoffstripe + 2) % ci->num_stripes, parity2;\n                ULONG skip, readlen;\n\n                i = startoffstripe;\n                while (stripe != parity1) {\n                    if (i == startoffstripe) {\n                        readlen = (ULONG)min(length, ci->stripe_length - (startoff % ci->stripe_length));\n\n                        context.stripes[stripe].stripestart = startoff;\n                        context.stripes[stripe].stripeend = startoff + readlen;\n\n                        pos += readlen;\n\n                        if (pos == length)\n                            break;\n                    } else {\n                        readlen = min(length - pos, (ULONG)ci->stripe_length);\n\n                        context.stripes[stripe].stripestart = startoff - (startoff % ci->stripe_length);\n                        context.stripes[stripe].stripeend = context.stripes[stripe].stripestart + readlen;\n\n                        pos += readlen;\n\n                        if (pos == length)\n                            break;\n                    }\n\n                    i++;\n                    stripe = (stripe + 1) % ci->num_stripes;\n                }\n\n                if (pos == length)\n                    break;\n\n                for (i = 0; i < startoffstripe; i++) {\n                    uint16_t stripe2 = (parity1 + i + 2) % ci->num_stripes;\n\n                    context.stripes[stripe2].stripestart = context.stripes[stripe2].stripeend = startoff - (startoff % ci->stripe_length) + ci->stripe_length;\n                }\n\n                context.stripes[parity1].stripestart = context.stripes[parity1].stripeend = startoff - (startoff % ci->stripe_length) + ci->stripe_length;\n\n                parity2 = (parity1 + 1) % ci->num_stripes;\n                context.stripes[parity2].stripestart = context.stripes[parity2].stripeend = startoff - (startoff % ci->stripe_length) + ci->stripe_length;\n\n                if (length - pos > ci->num_stripes * (ci->num_stripes - 2) * ci->stripe_length) {\n                    skip = (ULONG)(((length - pos) / (ci->num_stripes * (ci->num_stripes - 2) * ci->stripe_length)) - 1);\n\n                    for (i = 0; i < ci->num_stripes; i++) {\n                        context.stripes[i].stripeend += skip * ci->num_stripes * ci->stripe_length;\n                    }\n\n                    pos += (uint32_t)(skip * (ci->num_stripes - 2) * ci->num_stripes * ci->stripe_length);\n                    need_dummy = true;\n                }\n            } else if (length - pos >= ci->stripe_length * (ci->num_stripes - 2)) {\n                for (i = 0; i < ci->num_stripes; i++) {\n                    context.stripes[i].stripeend += ci->stripe_length;\n                }\n\n                pos += (uint32_t)(ci->stripe_length * (ci->num_stripes - 2));\n                need_dummy = true;\n            } else {\n                uint16_t stripe = (parity1 + 2) % ci->num_stripes;\n\n                i = 0;\n                while (stripe != parity1) {\n                    if (endoffstripe == i) {\n                        context.stripes[stripe].stripeend = endoff + 1;\n                        break;\n                    } else if (endoffstripe > i)\n                        context.stripes[stripe].stripeend = endoff - (endoff % ci->stripe_length) + ci->stripe_length;\n\n                    i++;\n                    stripe = (stripe + 1) % ci->num_stripes;\n                }\n\n                break;\n            }\n        }\n\n        for (i = 0; i < ci->num_stripes; i++) {\n            if (context.stripes[i].stripestart != context.stripes[i].stripeend) {\n                context.stripes[i].mdl = IoAllocateMdl(context.va, (ULONG)(context.stripes[i].stripeend - context.stripes[i].stripestart), false, false, NULL);\n\n                if (!context.stripes[i].mdl) {\n                    ERR(\"IoAllocateMdl failed\\n\");\n                    MmUnlockPages(master_mdl);\n                    IoFreeMdl(master_mdl);\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto exit;\n                }\n            }\n        }\n\n        if (need_dummy) {\n            dummypage = ExAllocatePoolWithTag(NonPagedPool, PAGE_SIZE, ALLOC_TAG);\n            if (!dummypage) {\n                ERR(\"out of memory\\n\");\n                MmUnlockPages(master_mdl);\n                IoFreeMdl(master_mdl);\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n\n            dummy_mdl = IoAllocateMdl(dummypage, PAGE_SIZE, false, false, NULL);\n            if (!dummy_mdl) {\n                ERR(\"IoAllocateMdl failed\\n\");\n                MmUnlockPages(master_mdl);\n                IoFreeMdl(master_mdl);\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n\n            MmBuildMdlForNonPagedPool(dummy_mdl);\n\n            dummy = *(PFN_NUMBER*)(dummy_mdl + 1);\n        }\n\n        stripeoff = ExAllocatePoolWithTag(NonPagedPool, sizeof(uint32_t) * ci->num_stripes, ALLOC_TAG);\n        if (!stripeoff) {\n            ERR(\"out of memory\\n\");\n            MmUnlockPages(master_mdl);\n            IoFreeMdl(master_mdl);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        RtlZeroMemory(stripeoff, sizeof(uint32_t) * ci->num_stripes);\n\n        pos = 0;\n\n        while (pos < length) {\n            PFN_NUMBER* stripe_pfns;\n\n            parity1 = (((addr - offset + pos) / ((ci->num_stripes - 2) * ci->stripe_length)) + ci->num_stripes - 2) % ci->num_stripes;\n\n            if (pos == 0) {\n                uint16_t stripe = (parity1 + startoffstripe + 2) % ci->num_stripes;\n                uint32_t readlen = min(length - pos, (uint32_t)min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart,\n                                                       ci->stripe_length - (context.stripes[stripe].stripestart % ci->stripe_length)));\n\n                stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1);\n\n                RtlCopyMemory(stripe_pfns, pfns, readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                stripeoff[stripe] = readlen;\n                pos += readlen;\n\n                stripe = (stripe + 1) % ci->num_stripes;\n\n                while (stripe != parity1) {\n                    stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1);\n                    readlen = (uint32_t)min(length - pos, min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart, ci->stripe_length));\n\n                    if (readlen == 0)\n                        break;\n\n                    RtlCopyMemory(stripe_pfns, &pfns[pos >> PAGE_SHIFT], readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                    stripeoff[stripe] = readlen;\n                    pos += readlen;\n\n                    stripe = (stripe + 1) % ci->num_stripes;\n                }\n            } else if (length - pos >= ci->stripe_length * (ci->num_stripes - 2)) {\n                uint16_t stripe = (parity1 + 2) % ci->num_stripes;\n                uint16_t parity2 = (parity1 + 1) % ci->num_stripes;\n                ULONG k;\n\n                while (stripe != parity1) {\n                    stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1);\n\n                    RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(ci->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n\n                    stripeoff[stripe] += (uint32_t)ci->stripe_length;\n                    pos += (uint32_t)ci->stripe_length;\n\n                    stripe = (stripe + 1) % ci->num_stripes;\n                }\n\n                stripe_pfns = (PFN_NUMBER*)(context.stripes[parity1].mdl + 1);\n\n                for (k = 0; k < ci->stripe_length >> PAGE_SHIFT; k++) {\n                    stripe_pfns[stripeoff[parity1] >> PAGE_SHIFT] = dummy;\n                    stripeoff[parity1] += PAGE_SIZE;\n                }\n\n                stripe_pfns = (PFN_NUMBER*)(context.stripes[parity2].mdl + 1);\n\n                for (k = 0; k < ci->stripe_length >> PAGE_SHIFT; k++) {\n                    stripe_pfns[stripeoff[parity2] >> PAGE_SHIFT] = dummy;\n                    stripeoff[parity2] += PAGE_SIZE;\n                }\n            } else {\n                uint16_t stripe = (parity1 + 2) % ci->num_stripes;\n                uint32_t readlen;\n\n                while (pos < length) {\n                    stripe_pfns = (PFN_NUMBER*)(context.stripes[stripe].mdl + 1);\n                    readlen = (uint32_t)min(length - pos, min(context.stripes[stripe].stripeend - context.stripes[stripe].stripestart, ci->stripe_length));\n\n                    if (readlen == 0)\n                        break;\n\n                    RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], readlen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                    stripeoff[stripe] += readlen;\n                    pos += readlen;\n\n                    stripe = (stripe + 1) % ci->num_stripes;\n                }\n            }\n        }\n\n        MmUnlockPages(master_mdl);\n        IoFreeMdl(master_mdl);\n\n        ExFreePool(stripeoff);\n    }\n\n    context.address = addr;\n\n    for (i = 0; i < ci->num_stripes; i++) {\n        if (!devices[i] || !devices[i]->devobj || context.stripes[i].stripestart == context.stripes[i].stripeend) {\n            context.stripes[i].status = ReadDataStatus_MissingDevice;\n            context.stripes_left--;\n\n            if (!devices[i] || !devices[i]->devobj)\n                missing_devices++;\n        }\n    }\n\n    if (missing_devices > allowed_missing) {\n        ERR(\"not enough devices to service request (%u missing)\\n\", missing_devices);\n        Status = STATUS_UNEXPECTED_IO_ERROR;\n        goto exit;\n    }\n\n    for (i = 0; i < ci->num_stripes; i++) {\n        PIO_STACK_LOCATION IrpSp;\n\n        if (devices[i] && devices[i]->devobj && context.stripes[i].stripestart != context.stripes[i].stripeend && context.stripes[i].status != ReadDataStatus_Skip) {\n            context.stripes[i].context = (struct read_data_context*)&context;\n\n            if (type == BLOCK_FLAG_RAID10) {\n                context.stripes[i].stripenum = i / ci->sub_stripes;\n            }\n\n            if (!Irp) {\n                context.stripes[i].Irp = IoAllocateIrp(devices[i]->devobj->StackSize, false);\n\n                if (!context.stripes[i].Irp) {\n                    ERR(\"IoAllocateIrp failed\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto exit;\n                }\n            } else {\n                context.stripes[i].Irp = IoMakeAssociatedIrp(Irp, devices[i]->devobj->StackSize);\n\n                if (!context.stripes[i].Irp) {\n                    ERR(\"IoMakeAssociatedIrp failed\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto exit;\n                }\n            }\n\n            IrpSp = IoGetNextIrpStackLocation(context.stripes[i].Irp);\n            IrpSp->MajorFunction = IRP_MJ_READ;\n            IrpSp->MinorFunction = IRP_MN_NORMAL;\n            IrpSp->FileObject = devices[i]->fileobj;\n\n            if (devices[i]->devobj->Flags & DO_BUFFERED_IO) {\n                context.stripes[i].Irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPool, (ULONG)(context.stripes[i].stripeend - context.stripes[i].stripestart), ALLOC_TAG);\n                if (!context.stripes[i].Irp->AssociatedIrp.SystemBuffer) {\n                    ERR(\"out of memory\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto exit;\n                }\n\n                context.stripes[i].Irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION;\n\n                context.stripes[i].Irp->UserBuffer = MmGetSystemAddressForMdlSafe(context.stripes[i].mdl, priority);\n            } else if (devices[i]->devobj->Flags & DO_DIRECT_IO)\n                context.stripes[i].Irp->MdlAddress = context.stripes[i].mdl;\n            else\n                context.stripes[i].Irp->UserBuffer = MmGetSystemAddressForMdlSafe(context.stripes[i].mdl, priority);\n\n            IrpSp->Parameters.Read.Length = (ULONG)(context.stripes[i].stripeend - context.stripes[i].stripestart);\n            IrpSp->Parameters.Read.ByteOffset.QuadPart = context.stripes[i].stripestart + cis[i].offset;\n\n            total_reading += IrpSp->Parameters.Read.Length;\n\n            context.stripes[i].Irp->UserIosb = &context.stripes[i].iosb;\n\n            IoSetCompletionRoutine(context.stripes[i].Irp, read_data_completion, &context.stripes[i], true, true, true);\n\n            context.stripes[i].status = ReadDataStatus_Pending;\n        }\n    }\n\n    need_to_wait = false;\n    for (i = 0; i < ci->num_stripes; i++) {\n        if (context.stripes[i].status != ReadDataStatus_MissingDevice && context.stripes[i].status != ReadDataStatus_Skip) {\n            IoCallDriver(devices[i]->devobj, context.stripes[i].Irp);\n            need_to_wait = true;\n        }\n    }\n\n    if (need_to_wait)\n        KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n\n    if (diskacc)\n        fFsRtlUpdateDiskCounters(total_reading, 0);\n\n    // check if any of the devices return a \"user-induced\" error\n\n    for (i = 0; i < ci->num_stripes; i++) {\n        if (context.stripes[i].status == ReadDataStatus_Error && IoIsErrorUserInduced(context.stripes[i].iosb.Status)) {\n            Status = context.stripes[i].iosb.Status;\n            goto exit;\n        }\n    }\n\n    if (type == BLOCK_FLAG_RAID0) {\n        Status = read_data_raid0(Vcb, file_read ? context.va : buf, addr, length, &context, ci, devices, generation, offset);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"read_data_raid0 returned %08lx\\n\", Status);\n\n            if (file_read)\n                ExFreePool(context.va);\n\n            goto exit;\n        }\n\n        if (file_read) {\n            RtlCopyMemory(buf, context.va, length);\n            ExFreePool(context.va);\n        }\n    } else if (type == BLOCK_FLAG_RAID10) {\n        Status = read_data_raid10(Vcb, file_read ? context.va : buf, addr, length, &context, ci, devices, generation, offset);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"read_data_raid10 returned %08lx\\n\", Status);\n\n            if (file_read)\n                ExFreePool(context.va);\n\n            goto exit;\n        }\n\n        if (file_read) {\n            RtlCopyMemory(buf, context.va, length);\n            ExFreePool(context.va);\n        }\n    } else if (type == BLOCK_FLAG_DUPLICATE) {\n        Status = read_data_dup(Vcb, file_read ? context.va : buf, addr, &context, ci, devices, generation);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"read_data_dup returned %08lx\\n\", Status);\n\n            if (file_read)\n                ExFreePool(context.va);\n\n            goto exit;\n        }\n\n        if (file_read) {\n            RtlCopyMemory(buf, context.va, length);\n            ExFreePool(context.va);\n        }\n    } else if (type == BLOCK_FLAG_RAID5) {\n        Status = read_data_raid5(Vcb, file_read ? context.va : buf, addr, length, &context, ci, devices, offset, generation, c, missing_devices > 0 ? true : false);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"read_data_raid5 returned %08lx\\n\", Status);\n\n            if (file_read)\n                ExFreePool(context.va);\n\n            goto exit;\n        }\n\n        if (file_read) {\n            RtlCopyMemory(buf, context.va, length);\n            ExFreePool(context.va);\n        }\n    } else if (type == BLOCK_FLAG_RAID6) {\n        Status = read_data_raid6(Vcb, file_read ? context.va : buf, addr, length, &context, ci, devices, offset, generation, c, missing_devices > 0 ? true : false);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"read_data_raid6 returned %08lx\\n\", Status);\n\n            if (file_read)\n                ExFreePool(context.va);\n\n            goto exit;\n        }\n\n        if (file_read) {\n            RtlCopyMemory(buf, context.va, length);\n            ExFreePool(context.va);\n        }\n    }\n\nexit:\n    if (c && (type == BLOCK_FLAG_RAID5 || type == BLOCK_FLAG_RAID6))\n        chunk_unlock_range(Vcb, c, lockaddr, locklen);\n\n    if (dummy_mdl)\n        IoFreeMdl(dummy_mdl);\n\n    if (dummypage)\n        ExFreePool(dummypage);\n\n    for (i = 0; i < ci->num_stripes; i++) {\n        if (context.stripes[i].mdl) {\n            if (context.stripes[i].mdl->MdlFlags & MDL_PAGES_LOCKED)\n                MmUnlockPages(context.stripes[i].mdl);\n\n            IoFreeMdl(context.stripes[i].mdl);\n        }\n\n        if (context.stripes[i].Irp)\n            IoFreeIrp(context.stripes[i].Irp);\n    }\n\n    ExFreePool(context.stripes);\n\n    if (!Vcb->log_to_phys_loaded)\n        ExFreePool(devices);\n\n    return Status;\n}\n\n__attribute__((nonnull(1, 2)))\nNTSTATUS read_stream(fcb* fcb, uint8_t* data, uint64_t start, ULONG length, ULONG* pbr) {\n    ULONG readlen;\n\n    TRACE(\"(%p, %p, %I64x, %lx, %p)\\n\", fcb, data, start, length, pbr);\n\n    if (pbr) *pbr = 0;\n\n    if (start >= fcb->adsdata.Length) {\n        TRACE(\"tried to read beyond end of stream\\n\");\n        return STATUS_END_OF_FILE;\n    }\n\n    if (length == 0) {\n        WARN(\"tried to read zero bytes\\n\");\n        return STATUS_SUCCESS;\n    }\n\n    if (start + length < fcb->adsdata.Length)\n        readlen = length;\n    else\n        readlen = fcb->adsdata.Length - (ULONG)start;\n\n    if (readlen > 0)\n        RtlCopyMemory(data, fcb->adsdata.Buffer + start, readlen);\n\n    if (pbr) *pbr = readlen;\n\n    return STATUS_SUCCESS;\n}\n\ntypedef struct {\n    uint64_t off;\n    uint64_t ed_size;\n    uint64_t ed_offset;\n    uint64_t ed_num_bytes;\n} read_part_extent;\n\ntypedef struct {\n    LIST_ENTRY list_entry;\n    uint64_t addr;\n    chunk* c;\n    uint32_t read;\n    uint32_t to_read;\n    void* csum;\n    bool csum_free;\n    uint8_t* buf;\n    bool buf_free;\n    uint32_t bumpoff;\n    bool mdl;\n    void* data;\n    uint8_t compression;\n    unsigned int num_extents;\n    read_part_extent extents[1];\n} read_part;\n\ntypedef struct {\n    LIST_ENTRY list_entry;\n    calc_job* cj;\n    void* decomp;\n    void* data;\n    unsigned int offset;\n    size_t length;\n} comp_calc_job;\n\n__attribute__((nonnull(1, 2)))\nNTSTATUS read_file(fcb* fcb, uint8_t* data, uint64_t start, uint64_t length, ULONG* pbr, PIRP Irp) {\n    NTSTATUS Status;\n    uint32_t bytes_read = 0;\n    uint64_t last_end;\n    LIST_ENTRY* le;\n    POOL_TYPE pool_type;\n    LIST_ENTRY read_parts, calc_jobs;\n\n    TRACE(\"(%p, %p, %I64x, %I64x, %p)\\n\", fcb, data, start, length, pbr);\n\n    if (pbr)\n        *pbr = 0;\n\n    if (start >= fcb->inode_item.st_size) {\n        WARN(\"Tried to read beyond end of file\\n\");\n        return STATUS_END_OF_FILE;\n    }\n\n    InitializeListHead(&read_parts);\n    InitializeListHead(&calc_jobs);\n\n    pool_type = fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? NonPagedPool : PagedPool;\n\n    le = fcb->extents.Flink;\n\n    last_end = start;\n\n    while (le != &fcb->extents) {\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if (!ext->ignore) {\n            EXTENT_DATA* ed = &ext->extent_data;\n            uint64_t len;\n\n            if (ed->type == EXTENT_TYPE_REGULAR || ed->type == EXTENT_TYPE_PREALLOC)\n                len = ((EXTENT_DATA2*)ed->data)->num_bytes;\n            else\n                len = ed->decoded_size;\n\n            if (ext->offset + len <= start) {\n                last_end = ext->offset + len;\n                goto nextitem;\n            }\n\n            if (ext->offset > last_end && ext->offset > start + bytes_read) {\n                uint32_t read = (uint32_t)min(length, ext->offset - max(start, last_end));\n\n                RtlZeroMemory(data + bytes_read, read);\n                bytes_read += read;\n                length -= read;\n            }\n\n            if (length == 0 || ext->offset > start + bytes_read + length)\n                break;\n\n            if (ed->encryption != BTRFS_ENCRYPTION_NONE) {\n                WARN(\"Encryption not supported\\n\");\n                Status = STATUS_NOT_IMPLEMENTED;\n                goto exit;\n            }\n\n            if (ed->encoding != BTRFS_ENCODING_NONE) {\n                WARN(\"Other encodings not supported\\n\");\n                Status = STATUS_NOT_IMPLEMENTED;\n                goto exit;\n            }\n\n            switch (ed->type) {\n                case EXTENT_TYPE_INLINE:\n                {\n                    uint64_t off = start + bytes_read - ext->offset;\n                    uint32_t read;\n\n                    if (ed->compression == BTRFS_COMPRESSION_NONE) {\n                        read = (uint32_t)min(min(len, ext->datalen) - off, length);\n\n                        RtlCopyMemory(data + bytes_read, &ed->data[off], read);\n                    } else if (ed->compression == BTRFS_COMPRESSION_ZLIB || ed->compression == BTRFS_COMPRESSION_LZO || ed->compression == BTRFS_COMPRESSION_ZSTD) {\n                        uint8_t* decomp;\n                        bool decomp_alloc;\n                        uint16_t inlen = ext->datalen - (uint16_t)offsetof(EXTENT_DATA, data[0]);\n\n                        if (ed->decoded_size == 0 || ed->decoded_size > 0xffffffff) {\n                            ERR(\"ed->decoded_size was invalid (%I64x)\\n\", ed->decoded_size);\n                            Status = STATUS_INTERNAL_ERROR;\n                            goto exit;\n                        }\n\n                        read = (uint32_t)min(ed->decoded_size - off, length);\n\n                        if (off > 0) {\n                            decomp = ExAllocatePoolWithTag(NonPagedPool, (uint32_t)ed->decoded_size, ALLOC_TAG);\n                            if (!decomp) {\n                                ERR(\"out of memory\\n\");\n                                Status = STATUS_INSUFFICIENT_RESOURCES;\n                                goto exit;\n                            }\n\n                            decomp_alloc = true;\n                        } else {\n                            decomp = data + bytes_read;\n                            decomp_alloc = false;\n                        }\n\n                        if (ed->compression == BTRFS_COMPRESSION_ZLIB) {\n                            Status = zlib_decompress(ed->data, inlen, decomp, (uint32_t)(read + off));\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"zlib_decompress returned %08lx\\n\", Status);\n                                if (decomp_alloc) ExFreePool(decomp);\n                                goto exit;\n                            }\n                        } else if (ed->compression == BTRFS_COMPRESSION_LZO) {\n                            if (inlen < sizeof(uint32_t)) {\n                                ERR(\"extent data was truncated\\n\");\n                                Status = STATUS_INTERNAL_ERROR;\n                                if (decomp_alloc) ExFreePool(decomp);\n                                goto exit;\n                            } else\n                                inlen -= sizeof(uint32_t);\n\n                            Status = lzo_decompress(ed->data + sizeof(uint32_t), inlen, decomp, (uint32_t)(read + off), sizeof(uint32_t));\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"lzo_decompress returned %08lx\\n\", Status);\n                                if (decomp_alloc) ExFreePool(decomp);\n                                goto exit;\n                            }\n                        } else if (ed->compression == BTRFS_COMPRESSION_ZSTD) {\n                            Status = zstd_decompress(ed->data, inlen, decomp, (uint32_t)(read + off));\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"zstd_decompress returned %08lx\\n\", Status);\n                                if (decomp_alloc) ExFreePool(decomp);\n                                goto exit;\n                            }\n                        }\n\n                        if (decomp_alloc) {\n                            RtlCopyMemory(data + bytes_read, decomp + off, read);\n                            ExFreePool(decomp);\n                        }\n                    } else {\n                        ERR(\"unhandled compression type %x\\n\", ed->compression);\n                        Status = STATUS_NOT_IMPLEMENTED;\n                        goto exit;\n                    }\n\n                    bytes_read += read;\n                    length -= read;\n\n                    break;\n                }\n\n                case EXTENT_TYPE_REGULAR:\n                {\n                    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n                    read_part* rp;\n\n                    rp = ExAllocatePoolWithTag(pool_type, sizeof(read_part), ALLOC_TAG);\n                    if (!rp) {\n                        ERR(\"out of memory\\n\");\n                        Status = STATUS_INSUFFICIENT_RESOURCES;\n                        goto exit;\n                    }\n\n                    rp->mdl = (Irp && Irp->MdlAddress) ? true : false;\n                    rp->extents[0].off = start + bytes_read - ext->offset;\n                    rp->bumpoff = 0;\n                    rp->num_extents = 1;\n                    rp->csum_free = false;\n\n                    rp->read = (uint32_t)(len - rp->extents[0].off);\n                    if (rp->read > length) rp->read = (uint32_t)length;\n\n                    if (ed->compression == BTRFS_COMPRESSION_NONE) {\n                        rp->addr = ed2->address + ed2->offset + rp->extents[0].off;\n                        rp->to_read = (uint32_t)sector_align(rp->read, fcb->Vcb->superblock.sector_size);\n\n                        if (rp->addr & (fcb->Vcb->superblock.sector_size - 1)) {\n                            rp->bumpoff = rp->addr & (fcb->Vcb->superblock.sector_size - 1);\n                            rp->addr -= rp->bumpoff;\n                            rp->to_read = (uint32_t)sector_align(rp->read + rp->bumpoff, fcb->Vcb->superblock.sector_size);\n                        }\n                    } else {\n                        rp->addr = ed2->address;\n                        rp->to_read = (uint32_t)sector_align(ed2->size, fcb->Vcb->superblock.sector_size);\n                    }\n\n                    if (ed->compression == BTRFS_COMPRESSION_NONE && (start & (fcb->Vcb->superblock.sector_size - 1)) == 0 &&\n                        (length & (fcb->Vcb->superblock.sector_size - 1)) == 0) {\n                        rp->buf = data + bytes_read;\n                        rp->buf_free = false;\n                    } else {\n                        rp->buf = ExAllocatePoolWithTag(pool_type, rp->to_read, ALLOC_TAG);\n                        rp->buf_free = true;\n\n                        if (!rp->buf) {\n                            ERR(\"out of memory\\n\");\n                            Status = STATUS_INSUFFICIENT_RESOURCES;\n                            ExFreePool(rp);\n                            goto exit;\n                        }\n\n                        rp->mdl = false;\n                    }\n\n                    rp->c = get_chunk_from_address(fcb->Vcb, rp->addr);\n\n                    if (!rp->c) {\n                        ERR(\"get_chunk_from_address(%I64x) failed\\n\", rp->addr);\n\n                        if (rp->buf_free)\n                            ExFreePool(rp->buf);\n\n                        ExFreePool(rp);\n\n                        Status = STATUS_INTERNAL_ERROR;\n                        goto exit;\n                    }\n\n                    if (ext->csum) {\n                        if (ed->compression == BTRFS_COMPRESSION_NONE) {\n                            rp->csum = (uint8_t*)ext->csum + (fcb->Vcb->csum_size * (rp->extents[0].off >> fcb->Vcb->sector_shift));\n                        } else\n                            rp->csum = ext->csum;\n                    } else\n                        rp->csum = NULL;\n\n                    rp->data = data + bytes_read;\n                    rp->compression = ed->compression;\n                    rp->extents[0].ed_offset = ed2->offset;\n                    rp->extents[0].ed_size = ed2->size;\n                    rp->extents[0].ed_num_bytes = ed2->num_bytes;\n\n                    InsertTailList(&read_parts, &rp->list_entry);\n\n                    bytes_read += rp->read;\n                    length -= rp->read;\n\n                    break;\n                }\n\n                case EXTENT_TYPE_PREALLOC:\n                {\n                    uint64_t off = start + bytes_read - ext->offset;\n                    uint32_t read = (uint32_t)(len - off);\n\n                    if (read > length) read = (uint32_t)length;\n\n                    RtlZeroMemory(data + bytes_read, read);\n\n                    bytes_read += read;\n                    length -= read;\n\n                    break;\n                }\n\n                default:\n                    WARN(\"Unsupported extent data type %u\\n\", ed->type);\n                    Status = STATUS_NOT_IMPLEMENTED;\n                    goto exit;\n            }\n\n            last_end = ext->offset + len;\n\n            if (length == 0)\n                break;\n        }\n\nnextitem:\n        le = le->Flink;\n    }\n\n    if (!IsListEmpty(&read_parts) && read_parts.Flink->Flink != &read_parts) { // at least two entries in list\n        read_part* last_rp = CONTAINING_RECORD(read_parts.Flink, read_part, list_entry);\n\n        le = read_parts.Flink->Flink;\n        while (le != &read_parts) {\n            LIST_ENTRY* le2 = le->Flink;\n            read_part* rp = CONTAINING_RECORD(le, read_part, list_entry);\n\n            // merge together runs\n            if (rp->compression != BTRFS_COMPRESSION_NONE && rp->compression == last_rp->compression && rp->addr == last_rp->addr + last_rp->to_read &&\n                rp->data == (uint8_t*)last_rp->data + last_rp->read && rp->c == last_rp->c && ((rp->csum && last_rp->csum) || (!rp->csum && !last_rp->csum))) {\n                read_part* rp2;\n\n                rp2 = ExAllocatePoolWithTag(pool_type, offsetof(read_part, extents) + (sizeof(read_part_extent) * (last_rp->num_extents + 1)), ALLOC_TAG);\n\n                rp2->addr = last_rp->addr;\n                rp2->c = last_rp->c;\n                rp2->read = last_rp->read + rp->read;\n                rp2->to_read = last_rp->to_read + rp->to_read;\n                rp2->csum_free = false;\n\n                if (last_rp->csum) {\n                    uint32_t sectors = (last_rp->to_read + rp->to_read) >> fcb->Vcb->sector_shift;\n\n                    rp2->csum = ExAllocatePoolWithTag(pool_type, sectors * fcb->Vcb->csum_size, ALLOC_TAG);\n                    if (!rp2->csum) {\n                        ERR(\"out of memory\\n\");\n                        ExFreePool(rp2);\n                        Status = STATUS_INSUFFICIENT_RESOURCES;\n                        goto exit;\n                    }\n\n                    RtlCopyMemory(rp2->csum, last_rp->csum, (last_rp->to_read * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift);\n                    RtlCopyMemory((uint8_t*)rp2->csum + ((last_rp->to_read * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift), rp->csum,\n                                  (rp->to_read * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift);\n\n                    rp2->csum_free = true;\n                } else\n                    rp2->csum = NULL;\n\n                rp2->buf = ExAllocatePoolWithTag(pool_type, rp2->to_read, ALLOC_TAG);\n                if (!rp2->buf) {\n                    ERR(\"out of memory\\n\");\n\n                    if (rp2->csum)\n                        ExFreePool(rp2->csum);\n\n                    ExFreePool(rp2);\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto exit;\n                }\n\n                rp2->buf_free = true;\n                rp2->bumpoff = 0;\n                rp2->mdl = false;\n                rp2->data = last_rp->data;\n                rp2->compression = last_rp->compression;\n                rp2->num_extents = last_rp->num_extents + 1;\n\n                RtlCopyMemory(rp2->extents, last_rp->extents, last_rp->num_extents * sizeof(read_part_extent));\n                RtlCopyMemory(&rp2->extents[last_rp->num_extents], rp->extents, sizeof(read_part_extent));\n\n                InsertHeadList(le->Blink, &rp2->list_entry);\n\n                if (rp->buf_free)\n                    ExFreePool(rp->buf);\n\n                if (rp->csum_free)\n                    ExFreePool(rp->csum);\n\n                RemoveEntryList(&rp->list_entry);\n\n                ExFreePool(rp);\n\n                if (last_rp->buf_free)\n                    ExFreePool(last_rp->buf);\n\n                if (last_rp->csum_free)\n                    ExFreePool(last_rp->csum);\n\n                RemoveEntryList(&last_rp->list_entry);\n\n                ExFreePool(last_rp);\n\n                last_rp = rp2;\n            } else\n                last_rp = rp;\n\n            le = le2;\n        }\n    }\n\n    le = read_parts.Flink;\n    while (le != &read_parts) {\n        read_part* rp = CONTAINING_RECORD(le, read_part, list_entry);\n\n        Status = read_data(fcb->Vcb, rp->addr, rp->to_read, rp->csum, false, rp->buf, rp->c, NULL, Irp, 0, rp->mdl,\n                           fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? HighPagePriority : NormalPagePriority);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"read_data returned %08lx\\n\", Status);\n            goto exit;\n        }\n\n        if (rp->compression == BTRFS_COMPRESSION_NONE) {\n            if (rp->buf_free)\n                RtlCopyMemory(rp->data, rp->buf + rp->bumpoff, rp->read);\n        } else {\n            uint8_t* buf = rp->buf;\n\n            for (unsigned int i = 0; i < rp->num_extents; i++) {\n                uint8_t *decomp = NULL, *buf2;\n                ULONG outlen, inlen, off2;\n                uint32_t inpageoff = 0;\n                comp_calc_job* ccj;\n\n                off2 = (ULONG)(rp->extents[i].ed_offset + rp->extents[i].off);\n                buf2 = buf;\n                inlen = (ULONG)rp->extents[i].ed_size;\n\n                if (rp->compression == BTRFS_COMPRESSION_LZO) {\n                    ULONG inoff = sizeof(uint32_t);\n\n                    inlen -= sizeof(uint32_t);\n\n                    // If reading a few sectors in, skip to the interesting bit\n                    while (off2 > LZO_PAGE_SIZE) {\n                        uint32_t partlen;\n\n                        if (inlen < sizeof(uint32_t))\n                            break;\n\n                        partlen = *(uint32_t*)(buf2 + inoff);\n\n                        if (partlen < inlen) {\n                            off2 -= LZO_PAGE_SIZE;\n                            inoff += partlen + sizeof(uint32_t);\n                            inlen -= partlen + sizeof(uint32_t);\n\n                            if (LZO_PAGE_SIZE - (inoff % LZO_PAGE_SIZE) < sizeof(uint32_t))\n                                inoff = ((inoff / LZO_PAGE_SIZE) + 1) * LZO_PAGE_SIZE;\n                        } else\n                            break;\n                    }\n\n                    buf2 = &buf2[inoff];\n                    inpageoff = inoff % LZO_PAGE_SIZE;\n                }\n\n                /* Previous versions of this code decompressed directly into the destination buffer,\n                 * but unfortunately that can't be relied on - Windows likes to use dummy pages sometimes\n                 * when mmap-ing, which breaks the backtracking used by e.g. zstd. */\n\n                if (off2 != 0)\n                    outlen = off2 + min(rp->read, (uint32_t)(rp->extents[i].ed_num_bytes - rp->extents[i].off));\n                else\n                    outlen = min(rp->read, (uint32_t)(rp->extents[i].ed_num_bytes - rp->extents[i].off));\n\n                decomp = ExAllocatePoolWithTag(pool_type, outlen, ALLOC_TAG);\n                if (!decomp) {\n                    ERR(\"out of memory\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto exit;\n                }\n\n                ccj = (comp_calc_job*)ExAllocatePoolWithTag(pool_type, sizeof(comp_calc_job), ALLOC_TAG);\n                if (!ccj) {\n                    ERR(\"out of memory\\n\");\n\n                    ExFreePool(decomp);\n\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto exit;\n                }\n\n                ccj->data = rp->data;\n                ccj->decomp = decomp;\n\n                ccj->offset = off2;\n                ccj->length = (size_t)min(rp->read, rp->extents[i].ed_num_bytes - rp->extents[i].off);\n\n                Status = add_calc_job_decomp(fcb->Vcb, rp->compression, buf2, inlen, decomp, outlen,\n                                             inpageoff, &ccj->cj);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"add_calc_job_decomp returned %08lx\\n\", Status);\n\n                    ExFreePool(decomp);\n                    ExFreePool(ccj);\n\n                    goto exit;\n                }\n\n                InsertTailList(&calc_jobs, &ccj->list_entry);\n\n                buf += rp->extents[i].ed_size;\n                rp->data = (uint8_t*)rp->data + rp->extents[i].ed_num_bytes - rp->extents[i].off;\n                rp->read -= (uint32_t)(rp->extents[i].ed_num_bytes - rp->extents[i].off);\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    if (length > 0 && start + bytes_read < fcb->inode_item.st_size) {\n        uint32_t read = (uint32_t)min(fcb->inode_item.st_size - start - bytes_read, length);\n\n        RtlZeroMemory(data + bytes_read, read);\n\n        bytes_read += read;\n        length -= read;\n    }\n\n    Status = STATUS_SUCCESS;\n\n    while (!IsListEmpty(&calc_jobs)) {\n        comp_calc_job* ccj = CONTAINING_RECORD(RemoveTailList(&calc_jobs), comp_calc_job, list_entry);\n\n        calc_thread_main(fcb->Vcb, ccj->cj);\n\n        KeWaitForSingleObject(&ccj->cj->event, Executive, KernelMode, false, NULL);\n\n        if (!NT_SUCCESS(ccj->cj->Status))\n            Status = ccj->cj->Status;\n\n        RtlCopyMemory(ccj->data, (uint8_t*)ccj->decomp + ccj->offset, ccj->length);\n        ExFreePool(ccj->decomp);\n\n        ExFreePool(ccj);\n    }\n\n    if (pbr)\n        *pbr = bytes_read;\n\nexit:\n    while (!IsListEmpty(&read_parts)) {\n        read_part* rp = CONTAINING_RECORD(RemoveHeadList(&read_parts), read_part, list_entry);\n\n        if (rp->buf_free)\n            ExFreePool(rp->buf);\n\n        if (rp->csum_free)\n            ExFreePool(rp->csum);\n\n        ExFreePool(rp);\n    }\n\n    while (!IsListEmpty(&calc_jobs)) {\n        comp_calc_job* ccj = CONTAINING_RECORD(RemoveHeadList(&calc_jobs), comp_calc_job, list_entry);\n\n        KeWaitForSingleObject(&ccj->cj->event, Executive, KernelMode, false, NULL);\n\n        if (ccj->decomp)\n            ExFreePool(ccj->decomp);\n\n        ExFreePool(ccj->cj);\n\n        ExFreePool(ccj);\n    }\n\n    return Status;\n}\n\nNTSTATUS do_read(PIRP Irp, bool wait, ULONG* bytes_read) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    fcb* fcb = FileObject->FsContext;\n    uint8_t* data = NULL;\n    ULONG length = IrpSp->Parameters.Read.Length, addon = 0;\n    uint64_t start = IrpSp->Parameters.Read.ByteOffset.QuadPart;\n\n    *bytes_read = 0;\n\n    if (!fcb || !fcb->Vcb || !fcb->subvol)\n        return STATUS_INTERNAL_ERROR;\n\n    TRACE(\"fcb = %p\\n\", fcb);\n    TRACE(\"offset = %I64x, length = %lx\\n\", start, length);\n    TRACE(\"paging_io = %s, no cache = %s\\n\", Irp->Flags & IRP_PAGING_IO ? \"true\" : \"false\", Irp->Flags & IRP_NOCACHE ? \"true\" : \"false\");\n\n    if (!fcb->ads && fcb->type == BTRFS_TYPE_DIRECTORY)\n        return STATUS_INVALID_DEVICE_REQUEST;\n\n    if (!(Irp->Flags & IRP_PAGING_IO) && !FsRtlCheckLockForReadAccess(&fcb->lock, Irp)) {\n        WARN(\"tried to read locked region\\n\");\n        return STATUS_FILE_LOCK_CONFLICT;\n    }\n\n    if (length == 0) {\n        TRACE(\"tried to read zero bytes\\n\");\n        return STATUS_SUCCESS;\n    }\n\n    if (start >= (uint64_t)fcb->Header.FileSize.QuadPart) {\n        TRACE(\"tried to read with offset after file end (%I64x >= %I64x)\\n\", start, fcb->Header.FileSize.QuadPart);\n        return STATUS_END_OF_FILE;\n    }\n\n    TRACE(\"FileObject %p fcb %p FileSize = %I64x st_size = %I64x (%p)\\n\", FileObject, fcb, fcb->Header.FileSize.QuadPart, fcb->inode_item.st_size, &fcb->inode_item.st_size);\n\n    if (!(Irp->Flags & IRP_NOCACHE) && IrpSp->MinorFunction & IRP_MN_MDL) {\n        NTSTATUS Status = STATUS_SUCCESS;\n\n        try {\n            if (!FileObject->PrivateCacheMap) {\n                CC_FILE_SIZES ccfs;\n\n                ccfs.AllocationSize = fcb->Header.AllocationSize;\n                ccfs.FileSize = fcb->Header.FileSize;\n                ccfs.ValidDataLength = fcb->Header.ValidDataLength;\n\n                init_file_cache(FileObject, &ccfs);\n            }\n\n            CcMdlRead(FileObject, &IrpSp->Parameters.Read.ByteOffset, length, &Irp->MdlAddress, &Irp->IoStatus);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (NT_SUCCESS(Status)) {\n            Status = Irp->IoStatus.Status;\n            Irp->IoStatus.Information += addon;\n            *bytes_read = (ULONG)Irp->IoStatus.Information;\n        } else\n            ERR(\"EXCEPTION - %08lx\\n\", Status);\n\n        return Status;\n    }\n\n    data = map_user_buffer(Irp, fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? HighPagePriority : NormalPagePriority);\n\n    if (Irp->MdlAddress && !data) {\n        ERR(\"MmGetSystemAddressForMdlSafe returned NULL\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    if (start >= (uint64_t)fcb->Header.ValidDataLength.QuadPart) {\n        length = (ULONG)min(length, min(start + length, (uint64_t)fcb->Header.FileSize.QuadPart) - fcb->Header.ValidDataLength.QuadPart);\n        RtlZeroMemory(data, length);\n        Irp->IoStatus.Information = *bytes_read = length;\n        return STATUS_SUCCESS;\n    }\n\n    if (length + start > (uint64_t)fcb->Header.ValidDataLength.QuadPart) {\n        addon = (ULONG)(min(start + length, (uint64_t)fcb->Header.FileSize.QuadPart) - fcb->Header.ValidDataLength.QuadPart);\n        RtlZeroMemory(data + (fcb->Header.ValidDataLength.QuadPart - start), addon);\n        length = (ULONG)(fcb->Header.ValidDataLength.QuadPart - start);\n    }\n\n    if (!(Irp->Flags & IRP_NOCACHE)) {\n        NTSTATUS Status = STATUS_SUCCESS;\n\n        try {\n            if (!FileObject->PrivateCacheMap) {\n                CC_FILE_SIZES ccfs;\n\n                ccfs.AllocationSize = fcb->Header.AllocationSize;\n                ccfs.FileSize = fcb->Header.FileSize;\n                ccfs.ValidDataLength = fcb->Header.ValidDataLength;\n\n                init_file_cache(FileObject, &ccfs);\n            }\n\n            if (fCcCopyReadEx) {\n                TRACE(\"CcCopyReadEx(%p, %I64x, %lx, %u, %p, %p, %p)\\n\", FileObject, IrpSp->Parameters.Read.ByteOffset.QuadPart,\n                        length, wait, data, &Irp->IoStatus, Irp->Tail.Overlay.Thread);\n                TRACE(\"sizes = %I64x, %I64x, %I64x\\n\", fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);\n                if (!fCcCopyReadEx(FileObject, &IrpSp->Parameters.Read.ByteOffset, length, wait, data, &Irp->IoStatus, Irp->Tail.Overlay.Thread)) {\n                    TRACE(\"CcCopyReadEx could not wait\\n\");\n\n                    IoMarkIrpPending(Irp);\n                    return STATUS_PENDING;\n                }\n                TRACE(\"CcCopyReadEx finished\\n\");\n            } else {\n                TRACE(\"CcCopyRead(%p, %I64x, %lx, %u, %p, %p)\\n\", FileObject, IrpSp->Parameters.Read.ByteOffset.QuadPart, length, wait, data, &Irp->IoStatus);\n                TRACE(\"sizes = %I64x, %I64x, %I64x\\n\", fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);\n                if (!CcCopyRead(FileObject, &IrpSp->Parameters.Read.ByteOffset, length, wait, data, &Irp->IoStatus)) {\n                    TRACE(\"CcCopyRead could not wait\\n\");\n\n                    IoMarkIrpPending(Irp);\n                    return STATUS_PENDING;\n                }\n                TRACE(\"CcCopyRead finished\\n\");\n            }\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (NT_SUCCESS(Status)) {\n            Status = Irp->IoStatus.Status;\n            Irp->IoStatus.Information += addon;\n            *bytes_read = (ULONG)Irp->IoStatus.Information;\n        } else\n            ERR(\"EXCEPTION - %08lx\\n\", Status);\n\n        return Status;\n    } else {\n        NTSTATUS Status;\n\n        if (!wait) {\n            IoMarkIrpPending(Irp);\n            return STATUS_PENDING;\n        }\n\n        if (fcb->ads) {\n            Status = read_stream(fcb, data, start, length, bytes_read);\n\n            if (!NT_SUCCESS(Status))\n                ERR(\"read_stream returned %08lx\\n\", Status);\n        } else {\n            Status = read_file(fcb, data, start, length, bytes_read, Irp);\n\n            if (!NT_SUCCESS(Status))\n                ERR(\"read_file returned %08lx\\n\", Status);\n        }\n\n        *bytes_read += addon;\n        TRACE(\"read %lu bytes\\n\", *bytes_read);\n\n        Irp->IoStatus.Information = *bytes_read;\n\n        if (diskacc && Status != STATUS_PENDING) {\n            PETHREAD thread = NULL;\n\n            if (Irp->Tail.Overlay.Thread && !IoIsSystemThread(Irp->Tail.Overlay.Thread))\n                thread = Irp->Tail.Overlay.Thread;\n            else if (!IoIsSystemThread(PsGetCurrentThread()))\n                thread = PsGetCurrentThread();\n            else if (IoIsSystemThread(PsGetCurrentThread()) && IoGetTopLevelIrp() == Irp)\n                thread = PsGetCurrentThread();\n\n            if (thread)\n                fPsUpdateDiskCounters(PsGetThreadProcess(thread), *bytes_read, 0, 1, 0, 0);\n        }\n\n        return Status;\n    }\n}\n\n_Dispatch_type_(IRP_MJ_READ)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_read(PDEVICE_OBJECT DeviceObject, PIRP Irp) {\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    ULONG bytes_read = 0;\n    NTSTATUS Status;\n    bool top_level;\n    fcb* fcb;\n    ccb* ccb;\n    bool acquired_fcb_lock = false, wait;\n\n    FsRtlEnterFileSystem();\n\n    top_level = is_top_level(Irp);\n\n    TRACE(\"read\\n\");\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = vol_read(DeviceObject, Irp);\n        goto exit2;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    Irp->IoStatus.Information = 0;\n\n    if (IrpSp->MinorFunction & IRP_MN_COMPLETE) {\n        CcMdlReadComplete(IrpSp->FileObject, Irp->MdlAddress);\n\n        Irp->MdlAddress = NULL;\n        Status = STATUS_SUCCESS;\n\n        goto exit;\n    }\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb) {\n        ERR(\"fcb was NULL\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto exit;\n    }\n\n    ccb = FileObject->FsContext2;\n\n    if (!ccb) {\n        ERR(\"ccb was NULL\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto exit;\n    }\n\n    if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_READ_DATA)) {\n        WARN(\"insufficient privileges\\n\");\n        Status = STATUS_ACCESS_DENIED;\n        goto exit;\n    }\n\n    if (fcb == Vcb->volume_fcb) {\n        TRACE(\"reading volume FCB\\n\");\n\n        IoSkipCurrentIrpStackLocation(Irp);\n\n        Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp);\n\n        goto exit2;\n    }\n\n    if (!(Irp->Flags & IRP_PAGING_IO))\n        FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL);\n\n    wait = IoIsOperationSynchronous(Irp);\n\n    // Don't offload jobs when doing paging IO - otherwise this can lead to\n    // deadlocks in CcCopyRead.\n    if (Irp->Flags & IRP_PAGING_IO)\n        wait = true;\n\n    if (!(Irp->Flags & IRP_PAGING_IO) && FileObject->SectionObjectPointer && FileObject->SectionObjectPointer->DataSectionObject) {\n        IO_STATUS_BLOCK iosb;\n\n        CcFlushCache(FileObject->SectionObjectPointer, &IrpSp->Parameters.Read.ByteOffset, IrpSp->Parameters.Read.Length, &iosb);\n        if (!NT_SUCCESS(iosb.Status)) {\n            ERR(\"CcFlushCache returned %08lx\\n\", iosb.Status);\n            Status = iosb.Status;\n            goto exit;\n        }\n    }\n\n    if (!ExIsResourceAcquiredSharedLite(fcb->Header.Resource)) {\n        if (!ExAcquireResourceSharedLite(fcb->Header.Resource, wait)) {\n            Status = STATUS_PENDING;\n            IoMarkIrpPending(Irp);\n            goto exit;\n        }\n\n        acquired_fcb_lock = true;\n    }\n\n    Status = do_read(Irp, wait, &bytes_read);\n\n    if (acquired_fcb_lock)\n        ExReleaseResourceLite(fcb->Header.Resource);\n\nexit:\n    if (FileObject->Flags & FO_SYNCHRONOUS_IO && !(Irp->Flags & IRP_PAGING_IO))\n        FileObject->CurrentByteOffset.QuadPart = IrpSp->Parameters.Read.ByteOffset.QuadPart + (NT_SUCCESS(Status) ? bytes_read : 0);\n\nend:\n    Irp->IoStatus.Status = Status;\n\n    TRACE(\"Irp->IoStatus.Status = %08lx\\n\", Irp->IoStatus.Status);\n    TRACE(\"Irp->IoStatus.Information = %Iu\\n\", Irp->IoStatus.Information);\n    TRACE(\"returning %08lx\\n\", Status);\n\n    if (Status != STATUS_PENDING)\n        IoCompleteRequest(Irp, IO_NO_INCREMENT);\n    else {\n        if (!add_thread_job(Vcb, Irp))\n            Status = do_read_job(Irp);\n    }\n\nexit2:\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n"
  },
  {
    "path": "src/registry.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include \"zstd/lib/zstd.h\"\n\nextern UNICODE_STRING log_device, log_file, registry_path;\nextern LIST_ENTRY uid_map_list, gid_map_list;\nextern ERESOURCE mapping_lock;\n\n#ifdef _DEBUG\nextern HANDLE log_handle;\nextern ERESOURCE log_lock;\nextern PFILE_OBJECT comfo;\nextern PDEVICE_OBJECT comdo;\n#endif\n\nWORK_QUEUE_ITEM wqi;\n\nstatic const WCHAR option_mounted[] = L\"Mounted\";\n\nNTSTATUS registry_load_volume_options(device_extension* Vcb) {\n    BTRFS_UUID* uuid = &Vcb->superblock.uuid;\n    mount_options* options = &Vcb->options;\n    UNICODE_STRING path, ignoreus, compressus, compressforceus, compresstypeus, readonlyus, zliblevelus, flushintervalus,\n                   maxinlineus, subvolidus, skipbalanceus, nobarrierus, notrimus, clearcacheus, allowdegradedus, zstdlevelus,\n                   norootdirus, nodatacowus;\n    OBJECT_ATTRIBUTES oa;\n    NTSTATUS Status;\n    ULONG i, j, kvfilen, index, retlen;\n    KEY_VALUE_FULL_INFORMATION* kvfi = NULL;\n    HANDLE h;\n\n    options->compress = mount_compress;\n    options->compress_force = mount_compress_force;\n    options->compress_type = mount_compress_type > BTRFS_COMPRESSION_ZSTD ? 0 : mount_compress_type;\n    options->readonly = mount_readonly;\n    options->zlib_level = mount_zlib_level;\n    options->zstd_level = mount_zstd_level;\n    options->flush_interval = mount_flush_interval;\n    options->max_inline = min(mount_max_inline, Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) - sizeof(EXTENT_DATA) + 1);\n    options->skip_balance = mount_skip_balance;\n    options->no_barrier = mount_no_barrier;\n    options->no_trim = mount_no_trim;\n    options->clear_cache = mount_clear_cache;\n    options->allow_degraded = mount_allow_degraded;\n    options->subvol_id = 0;\n    options->no_root_dir = mount_no_root_dir;\n    options->nodatacow = mount_nodatacow;\n\n    path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR));\n    path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG);\n\n    if (!path.Buffer) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length);\n    i = registry_path.Length / sizeof(WCHAR);\n\n    path.Buffer[i] = '\\\\';\n    i++;\n\n    for (j = 0; j < 16; j++) {\n        path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4);\n        path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF);\n\n        i += 2;\n\n        if (j == 3 || j == 5 || j == 7 || j == 9) {\n            path.Buffer[i] = '-';\n            i++;\n        }\n    }\n\n    kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR));\n    kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);\n    if (!kvfi) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = ZwOpenKey(&h, KEY_QUERY_VALUE, &oa);\n    if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {\n        Status = STATUS_SUCCESS;\n        goto end;\n    } else if (!NT_SUCCESS(Status)) {\n        ERR(\"ZwOpenKey returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    index = 0;\n\n    RtlInitUnicodeString(&ignoreus, L\"Ignore\");\n    RtlInitUnicodeString(&compressus, L\"Compress\");\n    RtlInitUnicodeString(&compressforceus, L\"CompressForce\");\n    RtlInitUnicodeString(&compresstypeus, L\"CompressType\");\n    RtlInitUnicodeString(&readonlyus, L\"Readonly\");\n    RtlInitUnicodeString(&zliblevelus, L\"ZlibLevel\");\n    RtlInitUnicodeString(&flushintervalus, L\"FlushInterval\");\n    RtlInitUnicodeString(&maxinlineus, L\"MaxInline\");\n    RtlInitUnicodeString(&subvolidus, L\"SubvolId\");\n    RtlInitUnicodeString(&skipbalanceus, L\"SkipBalance\");\n    RtlInitUnicodeString(&nobarrierus, L\"NoBarrier\");\n    RtlInitUnicodeString(&notrimus, L\"NoTrim\");\n    RtlInitUnicodeString(&clearcacheus, L\"ClearCache\");\n    RtlInitUnicodeString(&allowdegradedus, L\"AllowDegraded\");\n    RtlInitUnicodeString(&zstdlevelus, L\"ZstdLevel\");\n    RtlInitUnicodeString(&norootdirus, L\"NoRootDir\");\n    RtlInitUnicodeString(&nodatacowus, L\"NoDataCOW\");\n\n    do {\n        Status = ZwEnumerateValueKey(h, index, KeyValueFullInformation, kvfi, kvfilen, &retlen);\n\n        index++;\n\n        if (NT_SUCCESS(Status)) {\n            UNICODE_STRING us;\n\n            us.Length = us.MaximumLength = (USHORT)kvfi->NameLength;\n            us.Buffer = kvfi->Name;\n\n            if (FsRtlAreNamesEqual(&ignoreus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->ignore = *val != 0 ? true : false;\n            } else if (FsRtlAreNamesEqual(&compressus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->compress = *val != 0 ? true : false;\n            } else if (FsRtlAreNamesEqual(&compressforceus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->compress_force = *val != 0 ? true : false;\n            } else if (FsRtlAreNamesEqual(&compresstypeus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->compress_type = (uint8_t)(*val > BTRFS_COMPRESSION_ZSTD ? 0 : *val);\n            } else if (FsRtlAreNamesEqual(&readonlyus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->readonly = *val != 0 ? true : false;\n            } else if (FsRtlAreNamesEqual(&zliblevelus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->zlib_level = *val;\n            } else if (FsRtlAreNamesEqual(&flushintervalus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->flush_interval = *val;\n            } else if (FsRtlAreNamesEqual(&maxinlineus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->max_inline = min(*val, Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node) - sizeof(EXTENT_DATA) + 1);\n            } else if (FsRtlAreNamesEqual(&subvolidus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_QWORD) {\n                uint64_t* val = (uint64_t*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->subvol_id = *val;\n            } else if (FsRtlAreNamesEqual(&skipbalanceus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->skip_balance = *val;\n            } else if (FsRtlAreNamesEqual(&nobarrierus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->no_barrier = *val;\n            } else if (FsRtlAreNamesEqual(&notrimus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->no_trim = *val;\n            } else if (FsRtlAreNamesEqual(&clearcacheus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->clear_cache = *val;\n            } else if (FsRtlAreNamesEqual(&allowdegradedus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->allow_degraded = *val;\n            } else if (FsRtlAreNamesEqual(&zstdlevelus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->zstd_level = *val;\n            } else if (FsRtlAreNamesEqual(&norootdirus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->no_root_dir = *val;\n            } else if (FsRtlAreNamesEqual(&nodatacowus, &us, true, NULL) && kvfi->DataOffset > 0 && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                DWORD* val = (DWORD*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n                options->nodatacow = *val;\n            }\n        } else if (Status != STATUS_NO_MORE_ENTRIES) {\n            ERR(\"ZwEnumerateValueKey returned %08lx\\n\", Status);\n            goto end2;\n        }\n    } while (NT_SUCCESS(Status));\n\n    if (!options->compress && options->compress_force)\n        options->compress = true;\n\n    if (options->zlib_level > 9)\n        options->zlib_level = 9;\n\n    if (options->zstd_level > (uint32_t)ZSTD_maxCLevel())\n        options->zstd_level = ZSTD_maxCLevel();\n\n    if (options->flush_interval == 0)\n        options->flush_interval = mount_flush_interval;\n\n    Status = STATUS_SUCCESS;\n\nend2:\n    ZwClose(h);\n\nend:\n    ExFreePool(path.Buffer);\n\n    if (kvfi)\n        ExFreePool(kvfi);\n\n    return Status;\n}\n\nNTSTATUS registry_mark_volume_mounted(BTRFS_UUID* uuid) {\n    UNICODE_STRING path, mountedus;\n    ULONG i, j;\n    NTSTATUS Status;\n    OBJECT_ATTRIBUTES oa;\n    HANDLE h;\n    DWORD data;\n\n    path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR));\n    path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG);\n\n    if (!path.Buffer) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length);\n    i = registry_path.Length / sizeof(WCHAR);\n\n    path.Buffer[i] = '\\\\';\n    i++;\n\n    for (j = 0; j < 16; j++) {\n        path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4);\n        path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF);\n\n        i += 2;\n\n        if (j == 3 || j == 5 || j == 7 || j == 9) {\n            path.Buffer[i] = '-';\n            i++;\n        }\n    }\n\n    InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = ZwCreateKey(&h, KEY_SET_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ZwCreateKey returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    mountedus.Buffer = (WCHAR*)option_mounted;\n    mountedus.Length = mountedus.MaximumLength = sizeof(option_mounted) - sizeof(WCHAR);\n\n    data = 1;\n\n    Status = ZwSetValueKey(h, &mountedus, 0, REG_DWORD, &data, sizeof(DWORD));\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ZwSetValueKey returned %08lx\\n\", Status);\n        goto end2;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend2:\n    ZwClose(h);\n\nend:\n    ExFreePool(path.Buffer);\n\n    return Status;\n}\n\nstatic NTSTATUS registry_mark_volume_unmounted_path(PUNICODE_STRING path) {\n    HANDLE h;\n    OBJECT_ATTRIBUTES oa;\n    NTSTATUS Status;\n    ULONG index, kvbilen = sizeof(KEY_VALUE_BASIC_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR)), retlen;\n    KEY_VALUE_BASIC_INFORMATION* kvbi;\n    bool has_options = false;\n    UNICODE_STRING mountedus;\n\n    // If a volume key has any options in it, we set Mounted to 0 and return. Otherwise,\n    // we delete the whole thing.\n\n    kvbi = ExAllocatePoolWithTag(PagedPool, kvbilen, ALLOC_TAG);\n    if (!kvbi) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    InitializeObjectAttributes(&oa, path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = ZwOpenKey(&h, KEY_QUERY_VALUE | KEY_SET_VALUE | DELETE, &oa);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ZwOpenKey returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    index = 0;\n\n    mountedus.Buffer = (WCHAR*)option_mounted;\n    mountedus.Length = mountedus.MaximumLength = sizeof(option_mounted) - sizeof(WCHAR);\n\n    do {\n        Status = ZwEnumerateValueKey(h, index, KeyValueBasicInformation, kvbi, kvbilen, &retlen);\n\n        index++;\n\n        if (NT_SUCCESS(Status)) {\n            UNICODE_STRING us;\n\n            us.Length = us.MaximumLength = (USHORT)kvbi->NameLength;\n            us.Buffer = kvbi->Name;\n\n            if (!FsRtlAreNamesEqual(&mountedus, &us, true, NULL)) {\n                has_options = true;\n                break;\n            }\n        } else if (Status != STATUS_NO_MORE_ENTRIES) {\n            ERR(\"ZwEnumerateValueKey returned %08lx\\n\", Status);\n            goto end2;\n        }\n    } while (NT_SUCCESS(Status));\n\n    if (has_options) {\n        DWORD data = 0;\n\n        Status = ZwSetValueKey(h, &mountedus, 0, REG_DWORD, &data, sizeof(DWORD));\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"ZwSetValueKey returned %08lx\\n\", Status);\n            goto end2;\n        }\n    } else {\n        Status = ZwDeleteKey(h);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"ZwDeleteKey returned %08lx\\n\", Status);\n            goto end2;\n        }\n    }\n\n    Status = STATUS_SUCCESS;\n\nend2:\n    ZwClose(h);\n\nend:\n    ExFreePool(kvbi);\n\n    return Status;\n}\n\nNTSTATUS registry_mark_volume_unmounted(BTRFS_UUID* uuid) {\n    UNICODE_STRING path;\n    NTSTATUS Status;\n    ULONG i, j;\n\n    path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR));\n    path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG);\n\n    if (!path.Buffer) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length);\n    i = registry_path.Length / sizeof(WCHAR);\n\n    path.Buffer[i] = '\\\\';\n    i++;\n\n    for (j = 0; j < 16; j++) {\n        path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4);\n        path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF);\n\n        i += 2;\n\n        if (j == 3 || j == 5 || j == 7 || j == 9) {\n            path.Buffer[i] = '-';\n            i++;\n        }\n    }\n\n    Status = registry_mark_volume_unmounted_path(&path);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"registry_mark_volume_unmounted_path returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExFreePool(path.Buffer);\n\n    return Status;\n}\n\n#define is_hex(c) ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'))\n\nstatic bool is_uuid(ULONG namelen, WCHAR* name) {\n    ULONG i;\n\n    if (namelen != 36 * sizeof(WCHAR))\n        return false;\n\n    for (i = 0; i < 36; i++) {\n        if (i == 8 || i == 13 || i == 18 || i == 23) {\n            if (name[i] != '-')\n                return false;\n        } else if (!is_hex(name[i]))\n            return false;\n    }\n\n    return true;\n}\n\ntypedef struct {\n    UNICODE_STRING name;\n    LIST_ENTRY list_entry;\n} key_name;\n\nstatic void reset_subkeys(HANDLE h, PUNICODE_STRING reg_path) {\n    NTSTATUS Status;\n    KEY_BASIC_INFORMATION* kbi;\n    ULONG kbilen = sizeof(KEY_BASIC_INFORMATION) - sizeof(WCHAR) + (255 * sizeof(WCHAR)), retlen, index = 0;\n    LIST_ENTRY key_names, *le;\n\n    InitializeListHead(&key_names);\n\n    kbi = ExAllocatePoolWithTag(PagedPool, kbilen, ALLOC_TAG);\n    if (!kbi) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    do {\n        Status = ZwEnumerateKey(h, index, KeyBasicInformation, kbi, kbilen, &retlen);\n\n        index++;\n\n        if (NT_SUCCESS(Status)) {\n            key_name* kn;\n\n            TRACE(\"key: %.*S\\n\", (int)(kbi->NameLength / sizeof(WCHAR)), kbi->Name);\n\n            if (is_uuid(kbi->NameLength, kbi->Name)) {\n                kn = ExAllocatePoolWithTag(PagedPool, sizeof(key_name), ALLOC_TAG);\n                if (!kn) {\n                    ERR(\"out of memory\\n\");\n                    goto end;\n                }\n\n                kn->name.Length = kn->name.MaximumLength = (USHORT)min(0xffff, kbi->NameLength);\n                kn->name.Buffer = ExAllocatePoolWithTag(PagedPool, kn->name.MaximumLength, ALLOC_TAG);\n\n                if (!kn->name.Buffer) {\n                    ERR(\"out of memory\\n\");\n                    ExFreePool(kn);\n                    goto end;\n                }\n\n                RtlCopyMemory(kn->name.Buffer, kbi->Name, kn->name.Length);\n\n                InsertTailList(&key_names, &kn->list_entry);\n            }\n        } else if (Status != STATUS_NO_MORE_ENTRIES)\n            ERR(\"ZwEnumerateKey returned %08lx\\n\", Status);\n    } while (NT_SUCCESS(Status));\n\n    le = key_names.Flink;\n    while (le != &key_names) {\n        key_name* kn = CONTAINING_RECORD(le, key_name, list_entry);\n        UNICODE_STRING path;\n\n        path.Length = path.MaximumLength = reg_path->Length + sizeof(WCHAR) + kn->name.Length;\n        path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG);\n\n        if (!path.Buffer) {\n            ERR(\"out of memory\\n\");\n            goto end;\n        }\n\n        RtlCopyMemory(path.Buffer, reg_path->Buffer, reg_path->Length);\n        path.Buffer[reg_path->Length / sizeof(WCHAR)] = '\\\\';\n        RtlCopyMemory(&path.Buffer[(reg_path->Length / sizeof(WCHAR)) + 1], kn->name.Buffer, kn->name.Length);\n\n        Status = registry_mark_volume_unmounted_path(&path);\n        if (!NT_SUCCESS(Status))\n            WARN(\"registry_mark_volume_unmounted_path returned %08lx\\n\", Status);\n\n        ExFreePool(path.Buffer);\n\n        le = le->Flink;\n    }\n\nend:\n    while (!IsListEmpty(&key_names)) {\n        key_name* kn;\n\n        le = RemoveHeadList(&key_names);\n        kn = CONTAINING_RECORD(le, key_name, list_entry);\n\n        if (kn->name.Buffer)\n            ExFreePool(kn->name.Buffer);\n\n        ExFreePool(kn);\n    }\n\n    ExFreePool(kbi);\n}\n\nstatic void read_mappings(PUNICODE_STRING regpath) {\n    WCHAR* path;\n    UNICODE_STRING us;\n    HANDLE h;\n    OBJECT_ATTRIBUTES oa;\n    ULONG dispos;\n    NTSTATUS Status;\n\n    static const WCHAR mappings[] = L\"\\\\Mappings\";\n\n    while (!IsListEmpty(&uid_map_list)) {\n        uid_map* um = CONTAINING_RECORD(RemoveHeadList(&uid_map_list), uid_map, listentry);\n\n        if (um->sid) ExFreePool(um->sid);\n        ExFreePool(um);\n    }\n\n    path = ExAllocatePoolWithTag(PagedPool, regpath->Length + sizeof(mappings) - sizeof(WCHAR), ALLOC_TAG);\n    if (!path) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    RtlCopyMemory(path, regpath->Buffer, regpath->Length);\n    RtlCopyMemory((uint8_t*)path + regpath->Length, mappings, sizeof(mappings) - sizeof(WCHAR));\n\n    us.Buffer = path;\n    us.Length = us.MaximumLength = regpath->Length + sizeof(mappings) - sizeof(WCHAR);\n\n    InitializeObjectAttributes(&oa, &us, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = ZwCreateKey(&h, KEY_QUERY_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ZwCreateKey returned %08lx\\n\", Status);\n        ExFreePool(path);\n        return;\n    }\n\n    if (dispos == REG_OPENED_EXISTING_KEY) {\n        KEY_VALUE_FULL_INFORMATION* kvfi;\n        ULONG kvfilen, retlen, i;\n\n        kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) + 256;\n        kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);\n\n        if (!kvfi) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(path);\n            ZwClose(h);\n            return;\n        }\n\n        i = 0;\n        do {\n            Status = ZwEnumerateValueKey(h, i, KeyValueFullInformation, kvfi, kvfilen, &retlen);\n\n            if (NT_SUCCESS(Status) && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                uint32_t val = 0;\n\n                RtlCopyMemory(&val, (uint8_t*)kvfi + kvfi->DataOffset, min(kvfi->DataLength, sizeof(uint32_t)));\n\n                TRACE(\"entry %lu = %.*S = %u\\n\", i, (int)(kvfi->NameLength / sizeof(WCHAR)), kvfi->Name, val);\n\n                add_user_mapping(kvfi->Name, kvfi->NameLength / sizeof(WCHAR), val);\n            }\n\n            i = i + 1;\n        } while (Status != STATUS_NO_MORE_ENTRIES);\n\n        ExFreePool(kvfi);\n    }\n\n    ZwClose(h);\n\n    ExFreePool(path);\n}\n\nstatic void read_group_mappings(PUNICODE_STRING regpath) {\n    WCHAR* path;\n    UNICODE_STRING us;\n    HANDLE h;\n    OBJECT_ATTRIBUTES oa;\n    ULONG dispos;\n    NTSTATUS Status;\n\n    static const WCHAR mappings[] = L\"\\\\GroupMappings\";\n\n    while (!IsListEmpty(&gid_map_list)) {\n        gid_map* gm = CONTAINING_RECORD(RemoveHeadList(&gid_map_list), gid_map, listentry);\n\n        if (gm->sid) ExFreePool(gm->sid);\n        ExFreePool(gm);\n    }\n\n    path = ExAllocatePoolWithTag(PagedPool, regpath->Length + sizeof(mappings) - sizeof(WCHAR), ALLOC_TAG);\n    if (!path) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    RtlCopyMemory(path, regpath->Buffer, regpath->Length);\n    RtlCopyMemory((uint8_t*)path + regpath->Length, mappings, sizeof(mappings) - sizeof(WCHAR));\n\n    us.Buffer = path;\n    us.Length = us.MaximumLength = regpath->Length + sizeof(mappings) - sizeof(WCHAR);\n\n    InitializeObjectAttributes(&oa, &us, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = ZwCreateKey(&h, KEY_QUERY_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ZwCreateKey returned %08lx\\n\", Status);\n        ExFreePool(path);\n        return;\n    }\n\n    ExFreePool(path);\n\n    if (dispos == REG_OPENED_EXISTING_KEY) {\n        KEY_VALUE_FULL_INFORMATION* kvfi;\n        ULONG kvfilen, retlen, i;\n\n        kvfilen = sizeof(KEY_VALUE_FULL_INFORMATION) + 256;\n        kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);\n\n        if (!kvfi) {\n            ERR(\"out of memory\\n\");\n            ZwClose(h);\n            return;\n        }\n\n        i = 0;\n        do {\n            Status = ZwEnumerateValueKey(h, i, KeyValueFullInformation, kvfi, kvfilen, &retlen);\n\n            if (NT_SUCCESS(Status) && kvfi->DataLength > 0 && kvfi->Type == REG_DWORD) {\n                uint32_t val = 0;\n\n                RtlCopyMemory(&val, (uint8_t*)kvfi + kvfi->DataOffset, min(kvfi->DataLength, sizeof(uint32_t)));\n\n                TRACE(\"entry %lu = %.*S = %u\\n\", i, (int)(kvfi->NameLength / sizeof(WCHAR)), kvfi->Name, val);\n\n                add_group_mapping(kvfi->Name, kvfi->NameLength / sizeof(WCHAR), val);\n            }\n\n            i = i + 1;\n        } while (Status != STATUS_NO_MORE_ENTRIES);\n\n        ExFreePool(kvfi);\n    } else if (dispos == REG_CREATED_NEW_KEY) {\n        static const WCHAR builtin_users[] = L\"S-1-5-32-545\";\n\n        UNICODE_STRING us2;\n        DWORD val;\n\n        // If we're creating the key for the first time, we add a default mapping of\n        // BUILTIN\\Users to gid 100, which ought to correspond to the \"users\" group on Linux.\n\n        us2.Length = us2.MaximumLength = sizeof(builtin_users) - sizeof(WCHAR);\n        us2.Buffer = ExAllocatePoolWithTag(PagedPool, us2.MaximumLength, ALLOC_TAG);\n\n        if (us2.Buffer) {\n            RtlCopyMemory(us2.Buffer, builtin_users, us2.Length);\n\n            val = 100;\n            Status = ZwSetValueKey(h, &us2, 0, REG_DWORD, &val, sizeof(DWORD));\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"ZwSetValueKey returned %08lx\\n\", Status);\n                ZwClose(h);\n                return;\n            }\n\n            add_group_mapping(us2.Buffer, us2.Length / sizeof(WCHAR), val);\n\n            ExFreePool(us2.Buffer);\n        }\n    }\n\n    ZwClose(h);\n}\n\nstatic void get_registry_value(HANDLE h, WCHAR* string, ULONG type, void* val, ULONG size) {\n    ULONG kvfilen;\n    KEY_VALUE_FULL_INFORMATION* kvfi;\n    UNICODE_STRING us;\n    NTSTATUS Status;\n\n    RtlInitUnicodeString(&us, string);\n\n    kvfi = NULL;\n    kvfilen = 0;\n    Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen);\n\n    if ((Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_BUFFER_OVERFLOW) && kvfilen > 0) {\n        kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);\n\n        if (!kvfi) {\n            ERR(\"out of memory\\n\");\n            ZwClose(h);\n            return;\n        }\n\n        Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen);\n\n        if (NT_SUCCESS(Status)) {\n            if (kvfi->Type == type && kvfi->DataLength >= size) {\n                RtlCopyMemory(val, ((uint8_t*)kvfi) + kvfi->DataOffset, size);\n            } else {\n                Status = ZwDeleteValueKey(h, &us);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"ZwDeleteValueKey returned %08lx\\n\", Status);\n                }\n\n                Status = ZwSetValueKey(h, &us, 0, type, val, size);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"ZwSetValueKey returned %08lx\\n\", Status);\n                }\n            }\n        }\n\n        ExFreePool(kvfi);\n    } else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {\n        Status = ZwSetValueKey(h, &us, 0, type, val, size);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"ZwSetValueKey returned %08lx\\n\", Status);\n        }\n    } else {\n        ERR(\"ZwQueryValueKey returned %08lx\\n\", Status);\n    }\n}\n\nvoid read_registry(PUNICODE_STRING regpath, bool refresh) {\n    OBJECT_ATTRIBUTES oa;\n    NTSTATUS Status;\n    HANDLE h;\n    ULONG dispos;\n#ifdef _DEBUG\n    KEY_VALUE_FULL_INFORMATION* kvfi;\n    ULONG kvfilen, old_debug_log_level = debug_log_level;\n    UNICODE_STRING us, old_log_file, old_log_device;\n\n    static const WCHAR def_log_file[] = L\"\\\\??\\\\C:\\\\btrfs.log\";\n#endif\n\n    ExAcquireResourceExclusiveLite(&mapping_lock, true);\n\n    read_mappings(regpath);\n    read_group_mappings(regpath);\n\n    ExReleaseResourceLite(&mapping_lock);\n\n    InitializeObjectAttributes(&oa, regpath, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = ZwCreateKey(&h, KEY_QUERY_VALUE | KEY_ENUMERATE_SUB_KEYS, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"ZwCreateKey returned %08lx\\n\", Status);\n        return;\n    }\n\n    if (!refresh)\n        reset_subkeys(h, regpath);\n\n    get_registry_value(h, L\"Compress\", REG_DWORD, &mount_compress, sizeof(mount_compress));\n    get_registry_value(h, L\"CompressForce\", REG_DWORD, &mount_compress_force, sizeof(mount_compress_force));\n    get_registry_value(h, L\"CompressType\", REG_DWORD, &mount_compress_type, sizeof(mount_compress_type));\n    get_registry_value(h, L\"ZlibLevel\", REG_DWORD, &mount_zlib_level, sizeof(mount_zlib_level));\n    get_registry_value(h, L\"FlushInterval\", REG_DWORD, &mount_flush_interval, sizeof(mount_flush_interval));\n    get_registry_value(h, L\"MaxInline\", REG_DWORD, &mount_max_inline, sizeof(mount_max_inline));\n    get_registry_value(h, L\"SkipBalance\", REG_DWORD, &mount_skip_balance, sizeof(mount_skip_balance));\n    get_registry_value(h, L\"NoBarrier\", REG_DWORD, &mount_no_barrier, sizeof(mount_no_barrier));\n    get_registry_value(h, L\"NoTrim\", REG_DWORD, &mount_no_trim, sizeof(mount_no_trim));\n    get_registry_value(h, L\"ClearCache\", REG_DWORD, &mount_clear_cache, sizeof(mount_clear_cache));\n    get_registry_value(h, L\"AllowDegraded\", REG_DWORD, &mount_allow_degraded, sizeof(mount_allow_degraded));\n    get_registry_value(h, L\"Readonly\", REG_DWORD, &mount_readonly, sizeof(mount_readonly));\n    get_registry_value(h, L\"ZstdLevel\", REG_DWORD, &mount_zstd_level, sizeof(mount_zstd_level));\n    get_registry_value(h, L\"NoRootDir\", REG_DWORD, &mount_no_root_dir, sizeof(mount_no_root_dir));\n    get_registry_value(h, L\"NoDataCOW\", REG_DWORD, &mount_nodatacow, sizeof(mount_nodatacow));\n\n    if (!refresh)\n        get_registry_value(h, L\"NoPNP\", REG_DWORD, &no_pnp, sizeof(no_pnp));\n\n    if (mount_flush_interval == 0)\n        mount_flush_interval = 1;\n\n#ifdef _DEBUG\n    get_registry_value(h, L\"DebugLogLevel\", REG_DWORD, &debug_log_level, sizeof(debug_log_level));\n\n    RtlInitUnicodeString(&us, L\"LogDevice\");\n\n    kvfi = NULL;\n    kvfilen = 0;\n    Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen);\n\n    old_log_device = log_device;\n\n    log_device.Length = log_device.MaximumLength = 0;\n    log_device.Buffer = NULL;\n\n    if ((Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_BUFFER_OVERFLOW) && kvfilen > 0) {\n        kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);\n\n        if (!kvfi) {\n            ERR(\"out of memory\\n\");\n            ZwClose(h);\n            return;\n        }\n\n        Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen);\n\n        if (NT_SUCCESS(Status)) {\n            if ((kvfi->Type == REG_SZ || kvfi->Type == REG_EXPAND_SZ) && kvfi->DataLength >= sizeof(WCHAR)) {\n                log_device.Length = log_device.MaximumLength = (USHORT)min(0xffff, kvfi->DataLength);\n                log_device.Buffer = ExAllocatePoolWithTag(PagedPool, log_device.MaximumLength, ALLOC_TAG);\n\n                if (!log_device.Buffer) {\n                    ERR(\"out of memory\\n\");\n                    ExFreePool(kvfi);\n                    ZwClose(h);\n                    return;\n                }\n\n                RtlCopyMemory(log_device.Buffer, ((uint8_t*)kvfi) + kvfi->DataOffset, log_device.Length);\n\n                if (log_device.Buffer[(log_device.Length / sizeof(WCHAR)) - 1] == 0)\n                    log_device.Length -= sizeof(WCHAR);\n            } else {\n                ERR(\"LogDevice was type %lu, length %lu\\n\", kvfi->Type, kvfi->DataLength);\n\n                Status = ZwDeleteValueKey(h, &us);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"ZwDeleteValueKey returned %08lx\\n\", Status);\n                }\n            }\n        }\n\n        ExFreePool(kvfi);\n    } else if (Status != STATUS_OBJECT_NAME_NOT_FOUND) {\n        ERR(\"ZwQueryValueKey returned %08lx\\n\", Status);\n    }\n\n    ExAcquireResourceExclusiveLite(&log_lock, true);\n\n    if (refresh && (log_device.Length != old_log_device.Length || RtlCompareMemory(log_device.Buffer, old_log_device.Buffer, log_device.Length) != log_device.Length ||\n        (!comfo && log_device.Length > 0) || (old_debug_log_level == 0 && debug_log_level > 0) || (old_debug_log_level > 0 && debug_log_level == 0))) {\n        if (comfo)\n            ObDereferenceObject(comfo);\n\n        if (log_handle) {\n            ZwClose(log_handle);\n            log_handle = NULL;\n        }\n\n        comfo = NULL;\n        comdo = NULL;\n\n        if (log_device.Length > 0 && debug_log_level > 0) {\n            Status = IoGetDeviceObjectPointer(&log_device, FILE_WRITE_DATA, &comfo, &comdo);\n            if (!NT_SUCCESS(Status))\n                DbgPrint(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n        }\n    }\n\n    ExReleaseResourceLite(&log_lock);\n\n    if (old_log_device.Buffer)\n        ExFreePool(old_log_device.Buffer);\n\n    RtlInitUnicodeString(&us, L\"LogFile\");\n\n    kvfi = NULL;\n    kvfilen = 0;\n    Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen);\n\n    old_log_file = log_file;\n\n    if ((Status == STATUS_BUFFER_TOO_SMALL || Status == STATUS_BUFFER_OVERFLOW) && kvfilen > 0) {\n        kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);\n\n        if (!kvfi) {\n            ERR(\"out of memory\\n\");\n            ZwClose(h);\n            return;\n        }\n\n        Status = ZwQueryValueKey(h, &us, KeyValueFullInformation, kvfi, kvfilen, &kvfilen);\n\n        if (NT_SUCCESS(Status)) {\n            if ((kvfi->Type == REG_SZ || kvfi->Type == REG_EXPAND_SZ) && kvfi->DataLength >= sizeof(WCHAR)) {\n                log_file.Length = log_file.MaximumLength = (USHORT)min(0xffff, kvfi->DataLength);\n                log_file.Buffer = ExAllocatePoolWithTag(PagedPool, log_file.MaximumLength, ALLOC_TAG);\n\n                if (!log_file.Buffer) {\n                    ERR(\"out of memory\\n\");\n                    ExFreePool(kvfi);\n                    ZwClose(h);\n                    return;\n                }\n\n                RtlCopyMemory(log_file.Buffer, ((uint8_t*)kvfi) + kvfi->DataOffset, log_file.Length);\n\n                if (log_file.Buffer[(log_file.Length / sizeof(WCHAR)) - 1] == 0)\n                    log_file.Length -= sizeof(WCHAR);\n            } else {\n                ERR(\"LogFile was type %lu, length %lu\\n\", kvfi->Type, kvfi->DataLength);\n\n                Status = ZwDeleteValueKey(h, &us);\n                if (!NT_SUCCESS(Status))\n                    ERR(\"ZwDeleteValueKey returned %08lx\\n\", Status);\n\n                log_file.Length = 0;\n            }\n        } else {\n            ERR(\"ZwQueryValueKey returned %08lx\\n\", Status);\n            log_file.Length = 0;\n        }\n\n        ExFreePool(kvfi);\n    } else if (Status == STATUS_OBJECT_NAME_NOT_FOUND) {\n        Status = ZwSetValueKey(h, &us, 0, REG_SZ, (void*)def_log_file, sizeof(def_log_file));\n\n        if (!NT_SUCCESS(Status))\n            ERR(\"ZwSetValueKey returned %08lx\\n\", Status);\n\n        log_file.Length = 0;\n    } else {\n        ERR(\"ZwQueryValueKey returned %08lx\\n\", Status);\n        log_file.Length = 0;\n    }\n\n    if (log_file.Length == 0) {\n        log_file.Length = log_file.MaximumLength = sizeof(def_log_file) - sizeof(WCHAR);\n        log_file.Buffer = ExAllocatePoolWithTag(PagedPool, log_file.MaximumLength, ALLOC_TAG);\n\n        if (!log_file.Buffer) {\n            ERR(\"out of memory\\n\");\n            ZwClose(h);\n            return;\n        }\n\n        RtlCopyMemory(log_file.Buffer, def_log_file, log_file.Length);\n    }\n\n    ExAcquireResourceExclusiveLite(&log_lock, true);\n\n    if (refresh && (log_file.Length != old_log_file.Length || RtlCompareMemory(log_file.Buffer, old_log_file.Buffer, log_file.Length) != log_file.Length ||\n        (!log_handle && log_file.Length > 0) || (old_debug_log_level == 0 && debug_log_level > 0) || (old_debug_log_level > 0 && debug_log_level == 0))) {\n        if (log_handle) {\n            ZwClose(log_handle);\n            log_handle = NULL;\n        }\n\n        if (!comfo && log_file.Length > 0 && refresh && debug_log_level > 0) {\n            IO_STATUS_BLOCK iosb;\n\n            InitializeObjectAttributes(&oa, &log_file, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n\n            Status = ZwCreateFile(&log_handle, FILE_WRITE_DATA, &oa, &iosb, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ,\n                                  FILE_OPEN_IF, FILE_NON_DIRECTORY_FILE | FILE_WRITE_THROUGH | FILE_SYNCHRONOUS_IO_ALERT, NULL, 0);\n            if (!NT_SUCCESS(Status)) {\n                DbgPrint(\"ZwCreateFile returned %08lx\\n\", Status);\n                log_handle = NULL;\n            }\n        }\n    }\n\n    ExReleaseResourceLite(&log_lock);\n\n    if (old_log_file.Buffer)\n        ExFreePool(old_log_file.Buffer);\n#endif\n\n    ZwClose(h);\n}\n\n_Function_class_(WORKER_THREAD_ROUTINE)\nstatic void __stdcall registry_work_item(PVOID Parameter) {\n    NTSTATUS Status;\n    HANDLE regh = (HANDLE)Parameter;\n    IO_STATUS_BLOCK iosb;\n\n    TRACE(\"registry changed\\n\");\n\n    read_registry(&registry_path, true);\n\n    Status = ZwNotifyChangeKey(regh, NULL, (PVOID)&wqi, (PVOID)DelayedWorkQueue, &iosb, REG_NOTIFY_CHANGE_LAST_SET, true, NULL, 0, true);\n    if (!NT_SUCCESS(Status))\n        ERR(\"ZwNotifyChangeKey returned %08lx\\n\", Status);\n}\n\nvoid watch_registry(HANDLE regh) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n\n    ExInitializeWorkItem(&wqi, registry_work_item, regh);\n\n    Status = ZwNotifyChangeKey(regh, NULL, (PVOID)&wqi, (PVOID)DelayedWorkQueue, &iosb, REG_NOTIFY_CHANGE_LAST_SET, true, NULL, 0, true);\n    if (!NT_SUCCESS(Status))\n        ERR(\"ZwNotifyChangeKey returned %08lx\\n\", Status);\n}\n"
  },
  {
    "path": "src/reparse.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n\nextern tFsRtlValidateReparsePointBuffer fFsRtlValidateReparsePointBuffer;\n\ntypedef struct {\n    uint32_t unknown;\n    char name[1];\n} REPARSE_DATA_BUFFER_LX_SYMLINK;\n\nNTSTATUS get_reparse_point(PFILE_OBJECT FileObject, void* buffer, DWORD buflen, ULONG_PTR* retlen) {\n    USHORT subnamelen, printnamelen, i;\n    ULONG stringlen;\n    DWORD reqlen;\n    REPARSE_DATA_BUFFER* rdb = buffer;\n    fcb* fcb = FileObject->FsContext;\n    ccb* ccb = FileObject->FsContext2;\n    NTSTATUS Status;\n\n    TRACE(\"(%p, %p, %lx, %p)\\n\", FileObject, buffer, buflen, retlen);\n\n    if (!ccb)\n        return STATUS_INVALID_PARAMETER;\n\n    ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);\n    ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n\n    if (fcb->type == BTRFS_TYPE_SYMLINK) {\n        if (ccb->lxss) {\n            reqlen = offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + sizeof(uint32_t);\n\n            if (buflen < reqlen) {\n                Status = STATUS_BUFFER_OVERFLOW;\n                goto end;\n            }\n\n            rdb->ReparseTag = IO_REPARSE_TAG_LX_SYMLINK;\n            rdb->ReparseDataLength = offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + sizeof(uint32_t);\n            rdb->Reserved = 0;\n\n            *((uint32_t*)rdb->GenericReparseBuffer.DataBuffer) = 1;\n\n            *retlen = reqlen;\n        } else {\n            char* data;\n\n            if (fcb->inode_item.st_size == 0 || fcb->inode_item.st_size > 0xffff) {\n                Status = STATUS_INVALID_PARAMETER;\n                goto end;\n            }\n\n            data = ExAllocatePoolWithTag(PagedPool, (ULONG)fcb->inode_item.st_size, ALLOC_TAG);\n            if (!data) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            TRACE(\"data = %p, size = %I64x\\n\", data, fcb->inode_item.st_size);\n            Status = read_file(fcb, (uint8_t*)data, 0, fcb->inode_item.st_size, NULL, NULL);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"read_file returned %08lx\\n\", Status);\n                ExFreePool(data);\n                goto end;\n            }\n\n            Status = utf8_to_utf16(NULL, 0, &stringlen, data, (ULONG)fcb->inode_item.st_size);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"utf8_to_utf16 1 returned %08lx\\n\", Status);\n                ExFreePool(data);\n                goto end;\n            }\n\n            subnamelen = (uint16_t)stringlen;\n            printnamelen = (uint16_t)stringlen;\n\n            reqlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + subnamelen + printnamelen;\n\n            if (buflen >= offsetof(REPARSE_DATA_BUFFER, ReparseDataLength))\n                rdb->ReparseTag = IO_REPARSE_TAG_SYMLINK;\n\n            if (buflen >= offsetof(REPARSE_DATA_BUFFER, Reserved))\n                rdb->ReparseDataLength = (USHORT)(reqlen - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer));\n\n            if (buflen >= offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.SubstituteNameOffset))\n                rdb->Reserved = 0;\n\n            if (buflen < reqlen) {\n                ExFreePool(data);\n                Status = STATUS_BUFFER_OVERFLOW;\n                *retlen = min(buflen, offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.SubstituteNameOffset));\n                goto end;\n            }\n\n            rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;\n            rdb->SymbolicLinkReparseBuffer.SubstituteNameLength = subnamelen;\n            rdb->SymbolicLinkReparseBuffer.PrintNameOffset = subnamelen;\n            rdb->SymbolicLinkReparseBuffer.PrintNameLength = printnamelen;\n            rdb->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE;\n\n            Status = utf8_to_utf16(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],\n                                       stringlen, &stringlen, data, (ULONG)fcb->inode_item.st_size);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"utf8_to_utf16 2 returned %08lx\\n\", Status);\n                ExFreePool(data);\n                goto end;\n            }\n\n            for (i = 0; i < stringlen / sizeof(WCHAR); i++) {\n                if (rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] == '/')\n                    rdb->SymbolicLinkReparseBuffer.PathBuffer[(rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)) + i] = '\\\\';\n            }\n\n            RtlCopyMemory(&rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.PrintNameOffset / sizeof(WCHAR)],\n                        &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)],\n                        rdb->SymbolicLinkReparseBuffer.SubstituteNameLength);\n\n            *retlen = reqlen;\n\n            ExFreePool(data);\n        }\n\n        Status = STATUS_SUCCESS;\n    } else if (fcb->atts & FILE_ATTRIBUTE_REPARSE_POINT) {\n        if (fcb->type == BTRFS_TYPE_FILE) {\n            ULONG len;\n\n            Status = read_file(fcb, buffer, 0, buflen, &len, NULL);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"read_file returned %08lx\\n\", Status);\n            }\n\n            *retlen = len;\n        } else if (fcb->type == BTRFS_TYPE_DIRECTORY) {\n            if (!fcb->reparse_xattr.Buffer || fcb->reparse_xattr.Length < sizeof(ULONG)) {\n                Status = STATUS_NOT_A_REPARSE_POINT;\n                goto end;\n            }\n\n            if (buflen > 0) {\n                *retlen = min(buflen, fcb->reparse_xattr.Length);\n                RtlCopyMemory(buffer, fcb->reparse_xattr.Buffer, *retlen);\n            } else\n                *retlen = 0;\n\n            Status = *retlen == fcb->reparse_xattr.Length ? STATUS_SUCCESS : STATUS_BUFFER_OVERFLOW;\n        } else\n            Status = STATUS_NOT_A_REPARSE_POINT;\n    } else {\n        Status = STATUS_NOT_A_REPARSE_POINT;\n    }\n\nend:\n    ExReleaseResourceLite(fcb->Header.Resource);\n    ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS set_symlink(PIRP Irp, file_ref* fileref, fcb* fcb, ccb* ccb, REPARSE_DATA_BUFFER* rdb, ULONG buflen, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    ULONG tlength;\n    ANSI_STRING target;\n    bool target_alloc = false;\n    LARGE_INTEGER offset, time;\n    BTRFS_TIME now;\n\n    if (rdb->ReparseTag == IO_REPARSE_TAG_SYMLINK) {\n        UNICODE_STRING subname;\n        ULONG minlen, len;\n\n        minlen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + sizeof(WCHAR);\n        if (buflen < minlen) {\n            WARN(\"buffer was less than minimum length (%lu < %lu)\\n\", buflen, minlen);\n            return STATUS_INVALID_PARAMETER;\n        }\n\n        if (rdb->SymbolicLinkReparseBuffer.SubstituteNameLength < sizeof(WCHAR)) {\n            WARN(\"rdb->SymbolicLinkReparseBuffer.SubstituteNameLength was too short\\n\");\n            return STATUS_INVALID_PARAMETER;\n        }\n\n        subname.Buffer = &rdb->SymbolicLinkReparseBuffer.PathBuffer[rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset / sizeof(WCHAR)];\n        subname.MaximumLength = subname.Length = rdb->SymbolicLinkReparseBuffer.SubstituteNameLength;\n\n        TRACE(\"substitute name = %.*S\\n\", (int)(subname.Length / sizeof(WCHAR)), subname.Buffer);\n\n        Status = utf16_to_utf8(NULL, 0, &len, subname.Buffer, subname.Length);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"utf16_to_utf8 1 failed with error %08lx\\n\", Status);\n            return Status;\n        }\n\n        target.MaximumLength = target.Length = (USHORT)len;\n        target.Buffer = ExAllocatePoolWithTag(PagedPool, target.MaximumLength, ALLOC_TAG);\n        if (!target.Buffer) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        target_alloc = true;\n\n        Status = utf16_to_utf8(target.Buffer, target.Length, &len, subname.Buffer, subname.Length);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"utf16_to_utf8 2 failed with error %08lx\\n\", Status);\n            ExFreePool(target.Buffer);\n            return Status;\n        }\n\n        for (USHORT i = 0; i < target.Length; i++) {\n            if (target.Buffer[i] == '\\\\')\n                target.Buffer[i] = '/';\n        }\n    } else if (rdb->ReparseTag == IO_REPARSE_TAG_LX_SYMLINK) {\n        REPARSE_DATA_BUFFER_LX_SYMLINK* buf;\n\n        if (buflen < offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + rdb->ReparseDataLength) {\n            WARN(\"buffer was less than expected length (%lu < %lu)\\n\", buflen,\n                 (unsigned long)(offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + rdb->ReparseDataLength));\n            return STATUS_INVALID_PARAMETER;\n        }\n\n        buf = (REPARSE_DATA_BUFFER_LX_SYMLINK*)rdb->GenericReparseBuffer.DataBuffer;\n\n        if (buflen < offsetof(REPARSE_DATA_BUFFER_LX_SYMLINK, name)) {\n            WARN(\"buffer was less than minimum length (%u < %lu)\\n\", rdb->ReparseDataLength,\n                 (unsigned long)(offsetof(REPARSE_DATA_BUFFER_LX_SYMLINK, name)));\n            return STATUS_INVALID_PARAMETER;\n        }\n\n        target.Buffer = buf->name;\n        target.Length = target.MaximumLength = rdb->ReparseDataLength - offsetof(REPARSE_DATA_BUFFER_LX_SYMLINK, name);\n    } else {\n        ERR(\"unexpected reparse tag %08lx\\n\", rdb->ReparseTag);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    fcb->type = BTRFS_TYPE_SYMLINK;\n    fcb->inode_item.st_mode &= ~__S_IFMT;\n    fcb->inode_item.st_mode |= __S_IFLNK;\n    fcb->inode_item.generation = fcb->Vcb->superblock.generation; // so we don't confuse btrfs send on Linux\n\n    if (fileref && fileref->dc)\n        fileref->dc->type = fcb->type;\n\n    Status = truncate_file(fcb, 0, Irp, rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"truncate_file returned %08lx\\n\", Status);\n\n        if (target_alloc)\n            ExFreePool(target.Buffer);\n\n        return Status;\n    }\n\n    offset.QuadPart = 0;\n    tlength = target.Length;\n    Status = write_file2(fcb->Vcb, Irp, offset, target.Buffer, &tlength, false, true,\n                            true, false, false, rollback);\n\n    if (target_alloc)\n        ExFreePool(target.Buffer);\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    fcb->inode_item.transid = fcb->Vcb->superblock.generation;\n    fcb->inode_item.sequence++;\n\n    if (!ccb || !ccb->user_set_change_time)\n        fcb->inode_item.st_ctime = now;\n\n    if (!ccb || !ccb->user_set_write_time)\n        fcb->inode_item.st_mtime = now;\n\n    fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;\n    fcb->subvol->root_item.ctime = now;\n\n    fcb->inode_item_changed = true;\n    mark_fcb_dirty(fcb);\n\n    if (fileref)\n        mark_fileref_dirty(fileref);\n\n    return Status;\n}\n\nNTSTATUS set_reparse_point2(fcb* fcb, REPARSE_DATA_BUFFER* rdb, ULONG buflen, ccb* ccb, file_ref* fileref, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    ULONG tag;\n\n    if (fcb->type == BTRFS_TYPE_SYMLINK) {\n        WARN(\"tried to set a reparse point on an existing symlink\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    // FIXME - fail if we already have the attribute FILE_ATTRIBUTE_REPARSE_POINT\n\n    // FIXME - die if not file or directory\n\n    if (fcb->type == BTRFS_TYPE_DIRECTORY && fcb->inode_item.st_size > 0) {\n        TRACE(\"directory not empty\\n\");\n        return STATUS_DIRECTORY_NOT_EMPTY;\n    }\n\n    if (buflen < sizeof(ULONG)) {\n        WARN(\"buffer was not long enough to hold tag\\n\");\n        return STATUS_INVALID_BUFFER_SIZE;\n    }\n\n    Status = fFsRtlValidateReparsePointBuffer(buflen, rdb);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"FsRtlValidateReparsePointBuffer returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    tag = *(ULONG*)rdb;\n\n    if (tag == IO_REPARSE_TAG_MOUNT_POINT && fcb->type != BTRFS_TYPE_DIRECTORY)\n        return STATUS_NOT_A_DIRECTORY;\n\n    if (fcb->type == BTRFS_TYPE_FILE &&\n        ((tag == IO_REPARSE_TAG_SYMLINK && rdb->SymbolicLinkReparseBuffer.Flags & SYMLINK_FLAG_RELATIVE) || tag == IO_REPARSE_TAG_LX_SYMLINK)) {\n        Status = set_symlink(Irp, fileref, fcb, ccb, rdb, buflen, rollback);\n        fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT;\n    } else {\n        LARGE_INTEGER offset, time;\n        BTRFS_TIME now;\n\n        if (fcb->type == BTRFS_TYPE_DIRECTORY || fcb->type == BTRFS_TYPE_CHARDEV || fcb->type == BTRFS_TYPE_BLOCKDEV) { // store as xattr\n            ANSI_STRING buf;\n\n            buf.Buffer = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG);\n            if (!buf.Buffer) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n            buf.Length = buf.MaximumLength = (uint16_t)buflen;\n\n            if (fcb->reparse_xattr.Buffer)\n                ExFreePool(fcb->reparse_xattr.Buffer);\n\n            fcb->reparse_xattr = buf;\n            RtlCopyMemory(buf.Buffer, rdb, buflen);\n\n            fcb->reparse_xattr_changed = true;\n\n            Status = STATUS_SUCCESS;\n        } else { // otherwise, store as file data\n            Status = truncate_file(fcb, 0, Irp, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"truncate_file returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            offset.QuadPart = 0;\n\n            Status = write_file2(fcb->Vcb, Irp, offset, rdb, &buflen, false, true, true, false, false, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"write_file2 returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        KeQuerySystemTime(&time);\n        win_time_to_unix(time, &now);\n\n        fcb->inode_item.transid = fcb->Vcb->superblock.generation;\n        fcb->inode_item.sequence++;\n\n        if (!ccb || !ccb->user_set_change_time)\n            fcb->inode_item.st_ctime = now;\n\n        if (!ccb || !ccb->user_set_write_time)\n            fcb->inode_item.st_mtime = now;\n\n        fcb->atts |= FILE_ATTRIBUTE_REPARSE_POINT;\n        fcb->atts_changed = true;\n\n        fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;\n        fcb->subvol->root_item.ctime = now;\n\n        fcb->inode_item_changed = true;\n        mark_fcb_dirty(fcb);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS set_reparse_point(PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    void* buffer = Irp->AssociatedIrp.SystemBuffer;\n    REPARSE_DATA_BUFFER* rdb = buffer;\n    DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;\n    NTSTATUS Status = STATUS_SUCCESS;\n    fcb* fcb;\n    ccb* ccb;\n    file_ref* fileref;\n    LIST_ENTRY rollback;\n\n    TRACE(\"(%p)\\n\", Irp);\n\n    InitializeListHead(&rollback);\n\n    if (!FileObject) {\n        ERR(\"FileObject was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    // IFSTest insists on this, for some reason...\n    if (Irp->UserBuffer)\n        return STATUS_INVALID_PARAMETER;\n\n    fcb = FileObject->FsContext;\n    ccb = FileObject->FsContext2;\n\n    if (!ccb) {\n        ERR(\"ccb was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA))) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    fileref = ccb->fileref;\n\n    if (!fileref) {\n        ERR(\"fileref was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (fcb->ads) {\n        fileref = fileref->parent;\n        fcb = fileref->fcb;\n    }\n\n    ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    Status = set_reparse_point2(fcb, rdb, buflen, ccb, fileref, Irp, &rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"set_reparse_point2 returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL);\n\nend:\n    if (NT_SUCCESS(Status))\n        clear_rollback(&rollback);\n    else\n        do_rollback(fcb->Vcb, &rollback);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n    ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n\n    return Status;\n}\n\nNTSTATUS delete_reparse_point(PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    REPARSE_DATA_BUFFER* rdb = Irp->AssociatedIrp.SystemBuffer;\n    DWORD buflen = IrpSp->Parameters.DeviceIoControl.InputBufferLength;\n    NTSTATUS Status;\n    fcb* fcb;\n    ccb* ccb;\n    file_ref* fileref;\n    LIST_ENTRY rollback;\n\n    TRACE(\"(%p)\\n\", Irp);\n\n    InitializeListHead(&rollback);\n\n    if (!FileObject) {\n        ERR(\"FileObject was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    fcb = FileObject->FsContext;\n\n    if (!fcb) {\n        ERR(\"fcb was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    ccb = FileObject->FsContext2;\n\n    if (!ccb) {\n        ERR(\"ccb was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    if (Irp->RequestorMode == UserMode && !(ccb->access & FILE_WRITE_ATTRIBUTES)) {\n        WARN(\"insufficient privileges\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    fileref = ccb->fileref;\n\n    if (!fileref) {\n        ERR(\"fileref was NULL\\n\");\n        return STATUS_INVALID_PARAMETER;\n    }\n\n    ExAcquireResourceSharedLite(&fcb->Vcb->tree_lock, true);\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (buflen < offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer)) {\n        ERR(\"buffer was too short\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (rdb->ReparseDataLength > 0) {\n        WARN(\"rdb->ReparseDataLength was not zero\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (fcb->ads) {\n        WARN(\"tried to delete reparse point on ADS\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (fcb->type == BTRFS_TYPE_SYMLINK) {\n        LARGE_INTEGER time;\n        BTRFS_TIME now;\n\n        if (rdb->ReparseTag != IO_REPARSE_TAG_SYMLINK) {\n            WARN(\"reparse tag was not IO_REPARSE_TAG_SYMLINK\\n\");\n            Status = STATUS_INVALID_PARAMETER;\n            goto end;\n        }\n\n        KeQuerySystemTime(&time);\n        win_time_to_unix(time, &now);\n\n        fileref->fcb->type = BTRFS_TYPE_FILE;\n        fileref->fcb->inode_item.st_mode &= ~__S_IFLNK;\n        fileref->fcb->inode_item.st_mode |= __S_IFREG;\n        fileref->fcb->inode_item.generation = fileref->fcb->Vcb->superblock.generation; // so we don't confuse btrfs send on Linux\n        fileref->fcb->inode_item.transid = fileref->fcb->Vcb->superblock.generation;\n        fileref->fcb->inode_item.sequence++;\n\n        if (!ccb->user_set_change_time)\n            fileref->fcb->inode_item.st_ctime = now;\n\n        if (!ccb->user_set_write_time)\n            fileref->fcb->inode_item.st_mtime = now;\n\n        fileref->fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;\n\n        if (fileref->dc)\n            fileref->dc->type = fileref->fcb->type;\n\n        mark_fileref_dirty(fileref);\n\n        fileref->fcb->inode_item_changed = true;\n        mark_fcb_dirty(fileref->fcb);\n\n        fileref->fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;\n        fileref->fcb->subvol->root_item.ctime = now;\n    } else if (fcb->type == BTRFS_TYPE_FILE) {\n        LARGE_INTEGER time;\n        BTRFS_TIME now;\n\n        // FIXME - do we need to check that the reparse tags match?\n\n        Status = truncate_file(fcb, 0, Irp, &rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"truncate_file returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;\n        fcb->atts_changed = true;\n\n        KeQuerySystemTime(&time);\n        win_time_to_unix(time, &now);\n\n        fcb->inode_item.transid = fcb->Vcb->superblock.generation;\n        fcb->inode_item.sequence++;\n\n        if (!ccb->user_set_change_time)\n            fcb->inode_item.st_ctime = now;\n\n        if (!ccb->user_set_write_time)\n            fcb->inode_item.st_mtime = now;\n\n        fcb->inode_item_changed = true;\n        mark_fcb_dirty(fcb);\n\n        fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;\n        fcb->subvol->root_item.ctime = now;\n    } else if (fcb->type == BTRFS_TYPE_DIRECTORY) {\n        LARGE_INTEGER time;\n        BTRFS_TIME now;\n\n        // FIXME - do we need to check that the reparse tags match?\n\n        fcb->atts &= ~FILE_ATTRIBUTE_REPARSE_POINT;\n        fcb->atts_changed = true;\n\n        if (fcb->reparse_xattr.Buffer) {\n            ExFreePool(fcb->reparse_xattr.Buffer);\n            fcb->reparse_xattr.Buffer = NULL;\n        }\n\n        fcb->reparse_xattr_changed = true;\n\n        KeQuerySystemTime(&time);\n        win_time_to_unix(time, &now);\n\n        fcb->inode_item.transid = fcb->Vcb->superblock.generation;\n        fcb->inode_item.sequence++;\n\n        if (!ccb->user_set_change_time)\n            fcb->inode_item.st_ctime = now;\n\n        if (!ccb->user_set_write_time)\n            fcb->inode_item.st_mtime = now;\n\n        fcb->inode_item_changed = true;\n        mark_fcb_dirty(fcb);\n\n        fcb->subvol->root_item.ctransid = fcb->Vcb->superblock.generation;\n        fcb->subvol->root_item.ctime = now;\n    } else {\n        ERR(\"unsupported file type %u\\n\", fcb->type);\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    Status = STATUS_SUCCESS;\n\n    queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_LAST_WRITE | FILE_NOTIFY_CHANGE_ATTRIBUTES, FILE_ACTION_MODIFIED, NULL);\n\nend:\n    if (NT_SUCCESS(Status))\n        clear_rollback(&rollback);\n    else\n        do_rollback(fcb->Vcb, &rollback);\n\n    ExReleaseResourceLite(fcb->Header.Resource);\n    ExReleaseResourceLite(&fcb->Vcb->tree_lock);\n\n    return Status;\n}\n"
  },
  {
    "path": "src/resource.h",
    "content": "//{{NO_DEPENDENCIES}}\r\n// Microsoft Visual C++ generated include file.\r\n// Used by btrfs.rc\r\n//\r\n\r\n// Next default values for new objects\r\n// \r\n#ifdef APSTUDIO_INVOKED\r\n#ifndef APSTUDIO_READONLY_SYMBOLS\r\n#define _APS_NEXT_RESOURCE_VALUE        101\r\n#define _APS_NEXT_COMMAND_VALUE         40001\r\n#define _APS_NEXT_CONTROL_VALUE         1001\r\n#define _APS_NEXT_SYMED_VALUE           101\r\n#endif\r\n#endif\r\n"
  },
  {
    "path": "src/scrub.c",
    "content": "/* Copyright (c) Mark Harmstone 2017\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n\n#define SCRUB_UNIT 0x100000 // 1 MB\n\nstruct _scrub_context;\n\ntypedef struct {\n    struct _scrub_context* context;\n    PIRP Irp;\n    uint64_t start;\n    uint32_t length;\n    IO_STATUS_BLOCK iosb;\n    uint8_t* buf;\n    bool csum_error;\n    void* bad_csums;\n} scrub_context_stripe;\n\ntypedef struct _scrub_context {\n    KEVENT Event;\n    scrub_context_stripe* stripes;\n    LONG stripes_left;\n} scrub_context;\n\ntypedef struct {\n    ANSI_STRING name;\n    bool orig_subvol;\n    LIST_ENTRY list_entry;\n} path_part;\n\nstatic void log_file_checksum_error(device_extension* Vcb, uint64_t addr, uint64_t devid, uint64_t subvol, uint64_t inode, uint64_t offset) {\n    LIST_ENTRY *le, parts;\n    root* r = NULL;\n    KEY searchkey;\n    traverse_ptr tp;\n    uint64_t dir;\n    bool orig_subvol = true, not_in_tree = false;\n    ANSI_STRING fn;\n    scrub_error* err;\n    NTSTATUS Status;\n    ULONG utf16len;\n\n    le = Vcb->roots.Flink;\n    while (le != &Vcb->roots) {\n        root* r2 = CONTAINING_RECORD(le, root, list_entry);\n\n        if (r2->id == subvol) {\n            r = r2;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!r) {\n        ERR(\"could not find subvol %I64x\\n\", subvol);\n        return;\n    }\n\n    InitializeListHead(&parts);\n\n    dir = inode;\n\n    while (true) {\n        if (dir == r->root_item.objid) {\n            if (r == Vcb->root_fileref->fcb->subvol)\n                break;\n\n            searchkey.obj_id = r->id;\n            searchkey.obj_type = TYPE_ROOT_BACKREF;\n            searchkey.offset = 0xffffffffffffffff;\n\n            Status = find_item(Vcb, Vcb->root_root, &tp, &searchkey, false, NULL);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"find_item returned %08lx\\n\", Status);\n                goto end;\n            }\n\n            if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n                ROOT_REF* rr = (ROOT_REF*)tp.item->data;\n                path_part* pp;\n\n                if (tp.item->size < sizeof(ROOT_REF)) {\n                    ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(ROOT_REF));\n                    goto end;\n                }\n\n                if (tp.item->size < offsetof(ROOT_REF, name[0]) + rr->n) {\n                    ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n                        tp.item->size, offsetof(ROOT_REF, name[0]) + rr->n);\n                    goto end;\n                }\n\n                pp = ExAllocatePoolWithTag(PagedPool, sizeof(path_part), ALLOC_TAG);\n                if (!pp) {\n                    ERR(\"out of memory\\n\");\n                    goto end;\n                }\n\n                pp->name.Buffer = rr->name;\n                pp->name.Length = pp->name.MaximumLength = rr->n;\n                pp->orig_subvol = false;\n\n                InsertTailList(&parts, &pp->list_entry);\n\n                r = NULL;\n\n                le = Vcb->roots.Flink;\n                while (le != &Vcb->roots) {\n                    root* r2 = CONTAINING_RECORD(le, root, list_entry);\n\n                    if (r2->id == tp.item->key.offset) {\n                        r = r2;\n                        break;\n                    }\n\n                    le = le->Flink;\n                }\n\n                if (!r) {\n                    ERR(\"could not find subvol %I64x\\n\", tp.item->key.offset);\n                    goto end;\n                }\n\n                dir = rr->dir;\n                orig_subvol = false;\n            } else {\n                not_in_tree = true;\n                break;\n            }\n        } else {\n            searchkey.obj_id = dir;\n            searchkey.obj_type = TYPE_INODE_EXTREF;\n            searchkey.offset = 0xffffffffffffffff;\n\n            Status = find_item(Vcb, r, &tp, &searchkey, false, NULL);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"find_item returned %08lx\\n\", Status);\n                goto end;\n            }\n\n            if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == TYPE_INODE_REF) {\n                INODE_REF* ir = (INODE_REF*)tp.item->data;\n                path_part* pp;\n\n                if (tp.item->size < sizeof(INODE_REF)) {\n                    ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(INODE_REF));\n                    goto end;\n                }\n\n                if (tp.item->size < offsetof(INODE_REF, name[0]) + ir->n) {\n                    ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n                        tp.item->size, offsetof(INODE_REF, name[0]) + ir->n);\n                    goto end;\n                }\n\n                pp = ExAllocatePoolWithTag(PagedPool, sizeof(path_part), ALLOC_TAG);\n                if (!pp) {\n                    ERR(\"out of memory\\n\");\n                    goto end;\n                }\n\n                pp->name.Buffer = ir->name;\n                pp->name.Length = pp->name.MaximumLength = ir->n;\n                pp->orig_subvol = orig_subvol;\n\n                InsertTailList(&parts, &pp->list_entry);\n\n                if (dir == tp.item->key.offset)\n                    break;\n\n                dir = tp.item->key.offset;\n            } else if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == TYPE_INODE_EXTREF) {\n                INODE_EXTREF* ier = (INODE_EXTREF*)tp.item->data;\n                path_part* pp;\n\n                if (tp.item->size < sizeof(INODE_EXTREF)) {\n                    ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n                                                                                  tp.item->size, sizeof(INODE_EXTREF));\n                    goto end;\n                }\n\n                if (tp.item->size < offsetof(INODE_EXTREF, name[0]) + ier->n) {\n                    ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n                        tp.item->size, offsetof(INODE_EXTREF, name[0]) + ier->n);\n                    goto end;\n                }\n\n                pp = ExAllocatePoolWithTag(PagedPool, sizeof(path_part), ALLOC_TAG);\n                if (!pp) {\n                    ERR(\"out of memory\\n\");\n                    goto end;\n                }\n\n                pp->name.Buffer = ier->name;\n                pp->name.Length = pp->name.MaximumLength = ier->n;\n                pp->orig_subvol = orig_subvol;\n\n                InsertTailList(&parts, &pp->list_entry);\n\n                if (dir == ier->dir)\n                    break;\n\n                dir = ier->dir;\n            } else {\n                ERR(\"could not find INODE_REF for inode %I64x in subvol %I64x\\n\", dir, r->id);\n                goto end;\n            }\n        }\n    }\n\n    fn.MaximumLength = 0;\n\n    if (not_in_tree) {\n        le = parts.Blink;\n        while (le != &parts) {\n            path_part* pp = CONTAINING_RECORD(le, path_part, list_entry);\n            LIST_ENTRY* le2 = le->Blink;\n\n            if (pp->orig_subvol)\n                break;\n\n            RemoveTailList(&parts);\n            ExFreePool(pp);\n\n            le = le2;\n        }\n    }\n\n    le = parts.Flink;\n    while (le != &parts) {\n        path_part* pp = CONTAINING_RECORD(le, path_part, list_entry);\n\n        fn.MaximumLength += pp->name.Length + 1;\n\n        le = le->Flink;\n    }\n\n    fn.Buffer = ExAllocatePoolWithTag(PagedPool, fn.MaximumLength, ALLOC_TAG);\n    if (!fn.Buffer) {\n        ERR(\"out of memory\\n\");\n        goto end;\n    }\n\n    fn.Length = 0;\n\n    le = parts.Blink;\n    while (le != &parts) {\n        path_part* pp = CONTAINING_RECORD(le, path_part, list_entry);\n\n        fn.Buffer[fn.Length] = '\\\\';\n        fn.Length++;\n\n        RtlCopyMemory(&fn.Buffer[fn.Length], pp->name.Buffer, pp->name.Length);\n        fn.Length += pp->name.Length;\n\n        le = le->Blink;\n    }\n\n    if (not_in_tree)\n        ERR(\"subvol %I64x, %.*s, offset %I64x\\n\", subvol, fn.Length, fn.Buffer, offset);\n    else\n        ERR(\"%.*s, offset %I64x\\n\", fn.Length, fn.Buffer, offset);\n\n    Status = utf8_to_utf16(NULL, 0, &utf16len, fn.Buffer, fn.Length);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"utf8_to_utf16 1 returned %08lx\\n\", Status);\n        ExFreePool(fn.Buffer);\n        goto end;\n    }\n\n    err = ExAllocatePoolWithTag(PagedPool, offsetof(scrub_error, data.filename[0]) + utf16len, ALLOC_TAG);\n    if (!err) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(fn.Buffer);\n        goto end;\n    }\n\n    err->address = addr;\n    err->device = devid;\n    err->recovered = false;\n    err->is_metadata = false;\n    err->parity = false;\n\n    err->data.subvol = not_in_tree ? subvol : 0;\n    err->data.offset = offset;\n    err->data.filename_length = (uint16_t)utf16len;\n\n    Status = utf8_to_utf16(err->data.filename, utf16len, &utf16len, fn.Buffer, fn.Length);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"utf8_to_utf16 2 returned %08lx\\n\", Status);\n        ExFreePool(fn.Buffer);\n        ExFreePool(err);\n        goto end;\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, true);\n\n    Vcb->scrub.num_errors++;\n    InsertTailList(&Vcb->scrub.errors, &err->list_entry);\n\n    ExReleaseResourceLite(&Vcb->scrub.stats_lock);\n\n    ExFreePool(fn.Buffer);\n\nend:\n    while (!IsListEmpty(&parts)) {\n        path_part* pp = CONTAINING_RECORD(RemoveHeadList(&parts), path_part, list_entry);\n\n        ExFreePool(pp);\n    }\n}\n\nstatic void log_file_checksum_error_shared(device_extension* Vcb, uint64_t treeaddr, uint64_t addr, uint64_t devid, uint64_t extent) {\n    tree_header* tree;\n    NTSTATUS Status;\n    leaf_node* ln;\n    ULONG i;\n\n    tree = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG);\n    if (!tree) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    Status = read_data(Vcb, treeaddr, Vcb->superblock.node_size, NULL, true, (uint8_t*)tree, NULL, NULL, NULL, 0, false, NormalPagePriority);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"read_data returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (tree->level != 0) {\n        ERR(\"tree level was %x, expected 0\\n\", tree->level);\n        goto end;\n    }\n\n    ln = (leaf_node*)&tree[1];\n\n    for (i = 0; i < tree->num_items; i++) {\n        if (ln[i].key.obj_type == TYPE_EXTENT_DATA && ln[i].size >= sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2)) {\n            EXTENT_DATA* ed = (EXTENT_DATA*)((uint8_t*)tree + sizeof(tree_header) + ln[i].offset);\n            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n\n            if (ed->type == EXTENT_TYPE_REGULAR && ed2->size != 0 && ed2->address == addr)\n                log_file_checksum_error(Vcb, addr, devid, tree->tree_id, ln[i].key.obj_id, ln[i].key.offset + addr - extent);\n        }\n    }\n\nend:\n    ExFreePool(tree);\n}\n\nstatic void log_tree_checksum_error(device_extension* Vcb, uint64_t addr, uint64_t devid, uint64_t root, uint8_t level, KEY* firstitem) {\n    scrub_error* err;\n\n    err = ExAllocatePoolWithTag(PagedPool, sizeof(scrub_error), ALLOC_TAG);\n    if (!err) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    err->address = addr;\n    err->device = devid;\n    err->recovered = false;\n    err->is_metadata = true;\n    err->parity = false;\n\n    err->metadata.root = root;\n    err->metadata.level = level;\n\n    if (firstitem) {\n        ERR(\"root %I64x, level %u, first item (%I64x,%x,%I64x)\\n\", root, level, firstitem->obj_id,\n                                                                firstitem->obj_type, firstitem->offset);\n\n        err->metadata.firstitem = *firstitem;\n    } else {\n        ERR(\"root %I64x, level %u\\n\", root, level);\n\n        RtlZeroMemory(&err->metadata.firstitem, sizeof(KEY));\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, true);\n\n    Vcb->scrub.num_errors++;\n    InsertTailList(&Vcb->scrub.errors, &err->list_entry);\n\n    ExReleaseResourceLite(&Vcb->scrub.stats_lock);\n}\n\nstatic void log_tree_checksum_error_shared(device_extension* Vcb, uint64_t offset, uint64_t address, uint64_t devid) {\n    tree_header* tree;\n    NTSTATUS Status;\n    internal_node* in;\n    ULONG i;\n\n    tree = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG);\n    if (!tree) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    Status = read_data(Vcb, offset, Vcb->superblock.node_size, NULL, true, (uint8_t*)tree, NULL, NULL, NULL, 0, false, NormalPagePriority);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"read_data returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (tree->level == 0) {\n        ERR(\"tree level was 0\\n\");\n        goto end;\n    }\n\n    in = (internal_node*)&tree[1];\n\n    for (i = 0; i < tree->num_items; i++) {\n        if (in[i].address == address) {\n            log_tree_checksum_error(Vcb, address, devid, tree->tree_id, tree->level - 1, &in[i].key);\n            break;\n        }\n    }\n\nend:\n    ExFreePool(tree);\n}\n\nstatic void log_unrecoverable_error(device_extension* Vcb, uint64_t address, uint64_t devid) {\n    KEY searchkey;\n    traverse_ptr tp;\n    NTSTATUS Status;\n    EXTENT_ITEM* ei;\n    EXTENT_ITEM2* ei2 = NULL;\n    uint8_t* ptr;\n    ULONG len;\n    uint64_t rc;\n\n    // FIXME - still log even if rest of this function fails\n\n    searchkey.obj_id = address;\n    searchkey.obj_type = TYPE_METADATA_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return;\n    }\n\n    if ((tp.item->key.obj_type != TYPE_EXTENT_ITEM && tp.item->key.obj_type != TYPE_METADATA_ITEM) ||\n        tp.item->key.obj_id >= address + Vcb->superblock.sector_size ||\n        (tp.item->key.obj_type == TYPE_EXTENT_ITEM && tp.item->key.obj_id + tp.item->key.offset <= address) ||\n        (tp.item->key.obj_type == TYPE_METADATA_ITEM && tp.item->key.obj_id + Vcb->superblock.node_size <= address)\n    )\n        return;\n\n    if (tp.item->size < sizeof(EXTENT_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n        return;\n    }\n\n    ei = (EXTENT_ITEM*)tp.item->data;\n    ptr = (uint8_t*)&ei[1];\n    len = tp.item->size - sizeof(EXTENT_ITEM);\n\n    if (tp.item->key.obj_id == TYPE_EXTENT_ITEM && ei->flags & EXTENT_ITEM_TREE_BLOCK) {\n        if (tp.item->size < sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2)) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n                                                                          tp.item->size, sizeof(EXTENT_ITEM) + sizeof(EXTENT_ITEM2));\n            return;\n        }\n\n        ei2 = (EXTENT_ITEM2*)ptr;\n\n        ptr += sizeof(EXTENT_ITEM2);\n        len -= sizeof(EXTENT_ITEM2);\n    }\n\n    rc = 0;\n\n    while (len > 0) {\n        uint8_t type = *ptr;\n\n        ptr++;\n        len--;\n\n        if (type == TYPE_TREE_BLOCK_REF) {\n            TREE_BLOCK_REF* tbr;\n\n            if (len < sizeof(TREE_BLOCK_REF)) {\n                ERR(\"TREE_BLOCK_REF takes up %Iu bytes, but only %lu remaining\\n\", sizeof(TREE_BLOCK_REF), len);\n                break;\n            }\n\n            tbr = (TREE_BLOCK_REF*)ptr;\n\n            log_tree_checksum_error(Vcb, address, devid, tbr->offset, ei2 ? ei2->level : (uint8_t)tp.item->key.offset, ei2 ? &ei2->firstitem : NULL);\n\n            rc++;\n\n            ptr += sizeof(TREE_BLOCK_REF);\n            len -= sizeof(TREE_BLOCK_REF);\n        } else if (type == TYPE_EXTENT_DATA_REF) {\n            EXTENT_DATA_REF* edr;\n\n            if (len < sizeof(EXTENT_DATA_REF)) {\n                ERR(\"EXTENT_DATA_REF takes up %Iu bytes, but only %lu remaining\\n\", sizeof(EXTENT_DATA_REF), len);\n                break;\n            }\n\n            edr = (EXTENT_DATA_REF*)ptr;\n\n            log_file_checksum_error(Vcb, address, devid, edr->root, edr->objid, edr->offset + address - tp.item->key.obj_id);\n\n            rc += edr->count;\n\n            ptr += sizeof(EXTENT_DATA_REF);\n            len -= sizeof(EXTENT_DATA_REF);\n        } else if (type == TYPE_SHARED_BLOCK_REF) {\n            SHARED_BLOCK_REF* sbr;\n\n            if (len < sizeof(SHARED_BLOCK_REF)) {\n                ERR(\"SHARED_BLOCK_REF takes up %Iu bytes, but only %lu remaining\\n\", sizeof(SHARED_BLOCK_REF), len);\n                break;\n            }\n\n            sbr = (SHARED_BLOCK_REF*)ptr;\n\n            log_tree_checksum_error_shared(Vcb, sbr->offset, address, devid);\n\n            rc++;\n\n            ptr += sizeof(SHARED_BLOCK_REF);\n            len -= sizeof(SHARED_BLOCK_REF);\n        } else if (type == TYPE_SHARED_DATA_REF) {\n            SHARED_DATA_REF* sdr;\n\n            if (len < sizeof(SHARED_DATA_REF)) {\n                ERR(\"SHARED_DATA_REF takes up %Iu bytes, but only %lu remaining\\n\", sizeof(SHARED_DATA_REF), len);\n                break;\n            }\n\n            sdr = (SHARED_DATA_REF*)ptr;\n\n            log_file_checksum_error_shared(Vcb, sdr->offset, address, devid, tp.item->key.obj_id);\n\n            rc += sdr->count;\n\n            ptr += sizeof(SHARED_DATA_REF);\n            len -= sizeof(SHARED_DATA_REF);\n        } else {\n            ERR(\"unknown extent type %x\\n\", type);\n            break;\n        }\n    }\n\n    if (rc < ei->refcount) {\n        do {\n            traverse_ptr next_tp;\n\n            if (find_next_item(Vcb, &tp, &next_tp, false, NULL))\n                tp = next_tp;\n            else\n                break;\n\n            if (tp.item->key.obj_id == address) {\n                if (tp.item->key.obj_type == TYPE_TREE_BLOCK_REF)\n                    log_tree_checksum_error(Vcb, address, devid, tp.item->key.offset, ei2 ? ei2->level : (uint8_t)tp.item->key.offset, ei2 ? &ei2->firstitem : NULL);\n                else if (tp.item->key.obj_type == TYPE_EXTENT_DATA_REF) {\n                    EXTENT_DATA_REF* edr;\n\n                    if (tp.item->size < sizeof(EXTENT_DATA_REF)) {\n                        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n                                                                             tp.item->size, sizeof(EXTENT_DATA_REF));\n                        break;\n                    }\n\n                    edr = (EXTENT_DATA_REF*)tp.item->data;\n\n                    log_file_checksum_error(Vcb, address, devid, edr->root, edr->objid, edr->offset + address - tp.item->key.obj_id);\n                } else if (tp.item->key.obj_type == TYPE_SHARED_BLOCK_REF)\n                    log_tree_checksum_error_shared(Vcb, tp.item->key.offset, address, devid);\n                else if (tp.item->key.obj_type == TYPE_SHARED_DATA_REF)\n                    log_file_checksum_error_shared(Vcb, tp.item->key.offset, address, devid, tp.item->key.obj_id);\n            } else\n                break;\n        } while (true);\n    }\n}\n\nstatic void log_error(device_extension* Vcb, uint64_t addr, uint64_t devid, bool metadata, bool recoverable, bool parity) {\n    if (recoverable) {\n        scrub_error* err;\n\n        if (parity) {\n            ERR(\"recovering from parity error at %I64x on device %I64x\\n\", addr, devid);\n        } else {\n            if (metadata)\n                ERR(\"recovering from metadata checksum error at %I64x on device %I64x\\n\", addr, devid);\n            else\n                ERR(\"recovering from data checksum error at %I64x on device %I64x\\n\", addr, devid);\n        }\n\n        err = ExAllocatePoolWithTag(PagedPool, sizeof(scrub_error), ALLOC_TAG);\n        if (!err) {\n            ERR(\"out of memory\\n\");\n            return;\n        }\n\n        err->address = addr;\n        err->device = devid;\n        err->recovered = true;\n        err->is_metadata = metadata;\n        err->parity = parity;\n\n        if (metadata)\n            RtlZeroMemory(&err->metadata, sizeof(err->metadata));\n        else\n            RtlZeroMemory(&err->data, sizeof(err->data));\n\n        ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, true);\n\n        Vcb->scrub.num_errors++;\n        InsertTailList(&Vcb->scrub.errors, &err->list_entry);\n\n        ExReleaseResourceLite(&Vcb->scrub.stats_lock);\n    } else {\n        if (metadata)\n            ERR(\"unrecoverable metadata checksum error at %I64x\\n\", addr);\n        else\n            ERR(\"unrecoverable data checksum error at %I64x\\n\", addr);\n\n        log_unrecoverable_error(Vcb, addr, devid);\n    }\n}\n\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall scrub_read_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {\n    scrub_context_stripe* stripe = conptr;\n    scrub_context* context = (scrub_context*)stripe->context;\n    ULONG left = InterlockedDecrement(&context->stripes_left);\n\n    UNUSED(DeviceObject);\n\n    stripe->iosb = Irp->IoStatus;\n\n    if (left == 0)\n        KeSetEvent(&context->Event, 0, false);\n\n    return STATUS_MORE_PROCESSING_REQUIRED;\n}\n\nstatic NTSTATUS scrub_extent_dup(device_extension* Vcb, chunk* c, uint64_t offset, void* csum, scrub_context* context) {\n    NTSTATUS Status;\n    bool csum_error = false;\n    ULONG i;\n    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n    uint16_t present_devices = 0;\n\n    if (csum) {\n        ULONG good_stripe = 0xffffffff;\n\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            if (c->devices[i]->devobj) {\n                present_devices++;\n\n                // if first stripe is okay, we only need to check that the others are identical to it\n                if (good_stripe != 0xffffffff) {\n                    if (RtlCompareMemory(context->stripes[i].buf, context->stripes[good_stripe].buf,\n                                        context->stripes[good_stripe].length) != context->stripes[i].length) {\n                        context->stripes[i].csum_error = true;\n                        csum_error = true;\n                        log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                    }\n                } else {\n                    Status = check_csum(Vcb, context->stripes[i].buf, context->stripes[i].length >> Vcb->sector_shift, csum);\n                    if (Status == STATUS_CRC_ERROR) {\n                        context->stripes[i].csum_error = true;\n                        csum_error = true;\n                        log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                    } else if (!NT_SUCCESS(Status)) {\n                        ERR(\"check_csum returned %08lx\\n\", Status);\n                        return Status;\n                    } else\n                        good_stripe = i;\n                }\n            }\n        }\n    } else {\n        ULONG good_stripe = 0xffffffff;\n\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            ULONG j;\n\n            if (c->devices[i]->devobj) {\n                // if first stripe is okay, we only need to check that the others are identical to it\n                if (good_stripe != 0xffffffff) {\n                    if (RtlCompareMemory(context->stripes[i].buf, context->stripes[good_stripe].buf,\n                                         context->stripes[good_stripe].length) != context->stripes[i].length) {\n                        context->stripes[i].csum_error = true;\n                        csum_error = true;\n                        log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                    }\n                } else {\n                    for (j = 0; j < context->stripes[i].length / Vcb->superblock.node_size; j++) {\n                        tree_header* th = (tree_header*)&context->stripes[i].buf[j * Vcb->superblock.node_size];\n\n                        if (!check_tree_checksum(Vcb, th) || th->address != offset + UInt32x32To64(j, Vcb->superblock.node_size)) {\n                            context->stripes[i].csum_error = true;\n                            csum_error = true;\n                            log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                        }\n                    }\n\n                    if (!context->stripes[i].csum_error)\n                        good_stripe = i;\n                }\n            }\n        }\n    }\n\n    if (!csum_error)\n        return STATUS_SUCCESS;\n\n    // handle checksum error\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (context->stripes[i].csum_error) {\n            if (csum) {\n                context->stripes[i].bad_csums = ExAllocatePoolWithTag(PagedPool, (context->stripes[i].length * Vcb->csum_size) >> Vcb->sector_shift, ALLOC_TAG);\n                if (!context->stripes[i].bad_csums) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                do_calc_job(Vcb, context->stripes[i].buf, context->stripes[i].length >> Vcb->sector_shift, context->stripes[i].bad_csums);\n            } else {\n                ULONG j;\n\n                context->stripes[i].bad_csums = ExAllocatePoolWithTag(PagedPool, (context->stripes[i].length * Vcb->csum_size) >> Vcb->sector_shift, ALLOC_TAG);\n                if (!context->stripes[i].bad_csums) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                for (j = 0; j < context->stripes[i].length / Vcb->superblock.node_size; j++) {\n                    tree_header* th = (tree_header*)&context->stripes[i].buf[j * Vcb->superblock.node_size];\n\n                    get_tree_checksum(Vcb, th, (uint8_t*)context->stripes[i].bad_csums + (Vcb->csum_size * j));\n                }\n            }\n        }\n    }\n\n    if (present_devices > 1) {\n        ULONG good_stripe = 0xffffffff;\n\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            if (c->devices[i]->devobj && !context->stripes[i].csum_error) {\n                good_stripe = i;\n                break;\n            }\n        }\n\n        if (good_stripe != 0xffffffff) {\n            // log\n\n            for (i = 0; i < c->chunk_item->num_stripes; i++) {\n                if (context->stripes[i].csum_error) {\n                    ULONG j;\n\n                    if (csum) {\n                        for (j = 0; j < context->stripes[i].length >> Vcb->sector_shift; j++) {\n                            if (RtlCompareMemory((uint8_t*)context->stripes[i].bad_csums + (j * Vcb->csum_size), (uint8_t*)csum + (j + Vcb->csum_size), Vcb->csum_size) != Vcb->csum_size) {\n                                uint64_t addr = offset + ((uint64_t)j << Vcb->sector_shift);\n\n                                log_error(Vcb, addr, c->devices[i]->devitem.dev_id, false, true, false);\n                                log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                            }\n                        }\n                    } else {\n                        for (j = 0; j < context->stripes[i].length / Vcb->superblock.node_size; j++) {\n                            tree_header* th = (tree_header*)&context->stripes[i].buf[j * Vcb->superblock.node_size];\n                            uint64_t addr = offset + UInt32x32To64(j, Vcb->superblock.node_size);\n\n                            if (RtlCompareMemory((uint8_t*)context->stripes[i].bad_csums + (j * Vcb->csum_size), th, Vcb->csum_size) != Vcb->csum_size || th->address != addr) {\n                                log_error(Vcb, addr, c->devices[i]->devitem.dev_id, true, true, false);\n                                log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                            }\n                        }\n                    }\n                }\n            }\n\n            // write good data over bad\n\n            for (i = 0; i < c->chunk_item->num_stripes; i++) {\n                if (context->stripes[i].csum_error && !c->devices[i]->readonly) {\n                    Status = write_data_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + offset - c->offset,\n                                             context->stripes[good_stripe].buf, context->stripes[i].length);\n\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"write_data_phys returned %08lx\\n\", Status);\n                        log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_WRITE_ERRORS);\n                        return Status;\n                    }\n                }\n            }\n\n            return STATUS_SUCCESS;\n        }\n\n        // if csum errors on all stripes, check sector by sector\n\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            if (c->devices[i]->devobj) {\n                if (csum) {\n                    for (ULONG j = 0; j < context->stripes[i].length >> Vcb->sector_shift; j++) {\n                        if (RtlCompareMemory((uint8_t*)context->stripes[i].bad_csums + (j * Vcb->csum_size), (uint8_t*)csum + (j * Vcb->csum_size), Vcb->csum_size) != Vcb->csum_size) {\n                            ULONG k;\n                            uint64_t addr = offset + ((uint64_t)j << Vcb->sector_shift);\n                            bool recovered = false;\n\n                            for (k = 0; k < c->chunk_item->num_stripes; k++) {\n                                if (i != k && c->devices[k]->devobj &&\n                                    RtlCompareMemory((uint8_t*)context->stripes[k].bad_csums + (j * Vcb->csum_size),\n                                                     (uint8_t*)csum + (j * Vcb->csum_size), Vcb->csum_size) == Vcb->csum_size) {\n                                    log_error(Vcb, addr, c->devices[i]->devitem.dev_id, false, true, false);\n                                    log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n\n                                    RtlCopyMemory(context->stripes[i].buf + (j << Vcb->sector_shift),\n                                                  context->stripes[k].buf + (j << Vcb->sector_shift), Vcb->superblock.sector_size);\n\n                                    recovered = true;\n                                    break;\n                                }\n                            }\n\n                            if (!recovered) {\n                                log_error(Vcb, addr, c->devices[i]->devitem.dev_id, false, false, false);\n                                log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                            }\n                        }\n                    }\n                } else {\n                    for (ULONG j = 0; j < context->stripes[i].length / Vcb->superblock.node_size; j++) {\n                        tree_header* th = (tree_header*)&context->stripes[i].buf[j * Vcb->superblock.node_size];\n                        uint64_t addr = offset + UInt32x32To64(j, Vcb->superblock.node_size);\n\n                        if (RtlCompareMemory((uint8_t*)context->stripes[i].bad_csums + (j * Vcb->csum_size), th, Vcb->csum_size) != Vcb->csum_size || th->address != addr) {\n                            ULONG k;\n                            bool recovered = false;\n\n                            for (k = 0; k < c->chunk_item->num_stripes; k++) {\n                                if (i != k && c->devices[k]->devobj) {\n                                    tree_header* th2 = (tree_header*)&context->stripes[k].buf[j * Vcb->superblock.node_size];\n\n                                    if (RtlCompareMemory((uint8_t*)context->stripes[k].bad_csums + (j * Vcb->csum_size), th2, Vcb->csum_size) == Vcb->csum_size && th2->address == addr) {\n                                        log_error(Vcb, addr, c->devices[i]->devitem.dev_id, true, true, false);\n                                        log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n\n                                        RtlCopyMemory(th, th2, Vcb->superblock.node_size);\n\n                                        recovered = true;\n                                        break;\n                                    }\n                                }\n                            }\n\n                            if (!recovered) {\n                                log_error(Vcb, addr, c->devices[i]->devitem.dev_id, true, false, false);\n                                log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        // write good data over bad\n\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            if (c->devices[i]->devobj && !c->devices[i]->readonly) {\n                Status = write_data_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + offset - c->offset,\n                                         context->stripes[i].buf, context->stripes[i].length);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"write_data_phys returned %08lx\\n\", Status);\n                    log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                    return Status;\n                }\n            }\n        }\n\n        return STATUS_SUCCESS;\n    }\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (c->devices[i]->devobj) {\n            ULONG j;\n\n            if (csum) {\n                for (j = 0; j < context->stripes[i].length >> Vcb->sector_shift; j++) {\n                    if (RtlCompareMemory((uint8_t*)context->stripes[i].bad_csums + (j * Vcb->csum_size), (uint8_t*)csum + (j + Vcb->csum_size), Vcb->csum_size) != Vcb->csum_size) {\n                        uint64_t addr = offset + ((uint64_t)j << Vcb->sector_shift);\n\n                        log_error(Vcb, addr, c->devices[i]->devitem.dev_id, false, false, false);\n                    }\n                }\n            } else {\n                for (j = 0; j < context->stripes[i].length / Vcb->superblock.node_size; j++) {\n                    tree_header* th = (tree_header*)&context->stripes[i].buf[j * Vcb->superblock.node_size];\n                    uint64_t addr = offset + UInt32x32To64(j, Vcb->superblock.node_size);\n\n                    if (RtlCompareMemory((uint8_t*)context->stripes[i].bad_csums + (j * Vcb->csum_size), th, Vcb->csum_size) != Vcb->csum_size || th->address != addr)\n                        log_error(Vcb, addr, c->devices[i]->devitem.dev_id, true, false, false);\n                }\n            }\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS scrub_extent_raid0(device_extension* Vcb, chunk* c, uint64_t offset, uint32_t length, uint16_t startoffstripe, void* csum, scrub_context* context) {\n    ULONG j;\n    uint16_t stripe;\n    uint32_t pos, *stripeoff;\n\n    pos = 0;\n    stripeoff = ExAllocatePoolWithTag(NonPagedPool, sizeof(uint32_t) * c->chunk_item->num_stripes, ALLOC_TAG);\n    if (!stripeoff) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(stripeoff, sizeof(uint32_t) * c->chunk_item->num_stripes);\n\n    stripe = startoffstripe;\n    while (pos < length) {\n        uint32_t readlen;\n\n        if (pos == 0)\n            readlen = (uint32_t)min(context->stripes[stripe].length, c->chunk_item->stripe_length - (context->stripes[stripe].start % c->chunk_item->stripe_length));\n        else\n            readlen = min(length - pos, (uint32_t)c->chunk_item->stripe_length);\n\n        if (csum) {\n            for (j = 0; j < readlen; j += Vcb->superblock.sector_size) {\n                if (!check_sector_csum(Vcb, context->stripes[stripe].buf + stripeoff[stripe], (uint8_t*)csum + ((pos * Vcb->csum_size) >> Vcb->sector_shift))) {\n                    uint64_t addr = offset + pos;\n\n                    log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, false, false, false);\n                    log_device_error(Vcb, c->devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                }\n\n                pos += Vcb->superblock.sector_size;\n                stripeoff[stripe] += Vcb->superblock.sector_size;\n            }\n        } else {\n            for (j = 0; j < readlen; j += Vcb->superblock.node_size) {\n                tree_header* th = (tree_header*)(context->stripes[stripe].buf + stripeoff[stripe]);\n                uint64_t addr = offset + pos;\n\n                if (!check_tree_checksum(Vcb, th) || th->address != addr) {\n                    log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, true, false, false);\n                    log_device_error(Vcb, c->devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                }\n\n                pos += Vcb->superblock.node_size;\n                stripeoff[stripe] += Vcb->superblock.node_size;\n            }\n        }\n\n        stripe = (stripe + 1) % c->chunk_item->num_stripes;\n    }\n\n    ExFreePool(stripeoff);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS scrub_extent_raid10(device_extension* Vcb, chunk* c, uint64_t offset, uint32_t length, uint16_t startoffstripe, void* csum, scrub_context* context) {\n    ULONG j;\n    uint16_t stripe, sub_stripes = max(c->chunk_item->sub_stripes, 1);\n    uint32_t pos, *stripeoff;\n    bool csum_error = false;\n    NTSTATUS Status;\n\n    pos = 0;\n    stripeoff = ExAllocatePoolWithTag(NonPagedPool, sizeof(uint32_t) * c->chunk_item->num_stripes / sub_stripes, ALLOC_TAG);\n    if (!stripeoff) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(stripeoff, sizeof(uint32_t) * c->chunk_item->num_stripes / sub_stripes);\n\n    stripe = startoffstripe;\n    while (pos < length) {\n        uint32_t readlen;\n\n        if (pos == 0)\n            readlen = (uint32_t)min(context->stripes[stripe * sub_stripes].length,\n                                  c->chunk_item->stripe_length - (context->stripes[stripe * sub_stripes].start % c->chunk_item->stripe_length));\n        else\n            readlen = min(length - pos, (uint32_t)c->chunk_item->stripe_length);\n\n        if (csum) {\n            ULONG good_stripe = 0xffffffff;\n            uint16_t k;\n\n            for (k = 0; k < sub_stripes; k++) {\n                if (c->devices[(stripe * sub_stripes) + k]->devobj) {\n                    // if first stripe is okay, we only need to check that the others are identical to it\n                    if (good_stripe != 0xffffffff) {\n                        if (RtlCompareMemory(context->stripes[(stripe * sub_stripes) + k].buf + stripeoff[stripe],\n                                            context->stripes[(stripe * sub_stripes) + good_stripe].buf + stripeoff[stripe],\n                                            readlen) != readlen) {\n                            context->stripes[(stripe * sub_stripes) + k].csum_error = true;\n                            csum_error = true;\n                            log_device_error(Vcb, c->devices[(stripe * sub_stripes) + k], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                        }\n                    } else {\n                        for (j = 0; j < readlen; j += Vcb->superblock.sector_size) {\n                            if (!check_sector_csum(Vcb, context->stripes[(stripe * sub_stripes) + k].buf + stripeoff[stripe] + j,\n                                                   (uint8_t*)csum + (((pos + j) * Vcb->csum_size) >> Vcb->sector_shift))) {\n                                csum_error = true;\n                                context->stripes[(stripe * sub_stripes) + k].csum_error = true;\n                                log_device_error(Vcb, c->devices[(stripe * sub_stripes) + k], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                                break;\n                            }\n                        }\n\n                        if (!context->stripes[(stripe * sub_stripes) + k].csum_error)\n                            good_stripe = k;\n                    }\n                }\n            }\n\n            pos += readlen;\n            stripeoff[stripe] += readlen;\n        } else {\n            ULONG good_stripe = 0xffffffff;\n            uint16_t k;\n\n            for (k = 0; k < sub_stripes; k++) {\n                if (c->devices[(stripe * sub_stripes) + k]->devobj) {\n                    // if first stripe is okay, we only need to check that the others are identical to it\n                    if (good_stripe != 0xffffffff) {\n                        if (RtlCompareMemory(context->stripes[(stripe * sub_stripes) + k].buf + stripeoff[stripe],\n                                            context->stripes[(stripe * sub_stripes) + good_stripe].buf + stripeoff[stripe],\n                                            readlen) != readlen) {\n                            context->stripes[(stripe * sub_stripes) + k].csum_error = true;\n                            csum_error = true;\n                            log_device_error(Vcb, c->devices[(stripe * sub_stripes) + k], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                        }\n                    } else {\n                        for (j = 0; j < readlen; j += Vcb->superblock.node_size) {\n                            tree_header* th = (tree_header*)(context->stripes[(stripe * sub_stripes) + k].buf + stripeoff[stripe] + j);\n                            uint64_t addr = offset + pos + j;\n\n                            if (!check_tree_checksum(Vcb, th) || th->address != addr) {\n                                csum_error = true;\n                                context->stripes[(stripe * sub_stripes) + k].csum_error = true;\n                                log_device_error(Vcb, c->devices[(stripe * sub_stripes) + k], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                                break;\n                            }\n                        }\n\n                        if (!context->stripes[(stripe * sub_stripes) + k].csum_error)\n                            good_stripe = k;\n                    }\n                }\n            }\n\n            pos += readlen;\n            stripeoff[stripe] += readlen;\n        }\n\n        stripe = (stripe + 1) % (c->chunk_item->num_stripes / sub_stripes);\n    }\n\n    if (!csum_error) {\n        Status = STATUS_SUCCESS;\n        goto end;\n    }\n\n    for (j = 0; j < c->chunk_item->num_stripes; j += sub_stripes) {\n        ULONG goodstripe = 0xffffffff;\n        uint16_t k;\n        bool hasbadstripe = false;\n\n        if (context->stripes[j].length == 0)\n            continue;\n\n        for (k = 0; k < sub_stripes; k++) {\n            if (c->devices[j + k]->devobj) {\n                if (!context->stripes[j + k].csum_error)\n                    goodstripe = k;\n                else\n                    hasbadstripe = true;\n            }\n        }\n\n        if (hasbadstripe) {\n            if (goodstripe != 0xffffffff) {\n                for (k = 0; k < sub_stripes; k++) {\n                    if (c->devices[j + k]->devobj && context->stripes[j + k].csum_error) {\n                        uint32_t so = 0;\n                        bool recovered = false;\n\n                        pos = 0;\n\n                        stripe = startoffstripe;\n                        while (pos < length) {\n                            uint32_t readlen;\n\n                            if (pos == 0)\n                                readlen = (uint32_t)min(context->stripes[stripe * sub_stripes].length,\n                                              c->chunk_item->stripe_length - (context->stripes[stripe * sub_stripes].start % c->chunk_item->stripe_length));\n                            else\n                                readlen = min(length - pos, (uint32_t)c->chunk_item->stripe_length);\n\n                            if (stripe == j / sub_stripes) {\n                                if (csum) {\n                                    ULONG l;\n\n                                    for (l = 0; l < readlen; l += Vcb->superblock.sector_size) {\n                                        if (RtlCompareMemory(context->stripes[j + k].buf + so,\n                                                             context->stripes[j + goodstripe].buf + so,\n                                                             Vcb->superblock.sector_size) != Vcb->superblock.sector_size) {\n                                            uint64_t addr = offset + pos;\n\n                                            log_error(Vcb, addr, c->devices[j + k]->devitem.dev_id, false, true, false);\n\n                                            recovered = true;\n                                        }\n\n                                        pos += Vcb->superblock.sector_size;\n                                        so += Vcb->superblock.sector_size;\n                                    }\n                                } else {\n                                    ULONG l;\n\n                                    for (l = 0; l < readlen; l += Vcb->superblock.node_size) {\n                                        if (RtlCompareMemory(context->stripes[j + k].buf + so,\n                                                            context->stripes[j + goodstripe].buf + so,\n                                                            Vcb->superblock.node_size) != Vcb->superblock.node_size) {\n                                            uint64_t addr = offset + pos;\n\n                                            log_error(Vcb, addr, c->devices[j + k]->devitem.dev_id, true, true, false);\n\n                                            recovered = true;\n                                        }\n\n                                        pos += Vcb->superblock.node_size;\n                                        so += Vcb->superblock.node_size;\n                                    }\n                                }\n                            } else\n                                pos += readlen;\n\n                            stripe = (stripe + 1) % (c->chunk_item->num_stripes / sub_stripes);\n                        }\n\n                        if (recovered) {\n                            // write good data over bad\n\n                            if (!c->devices[j + k]->readonly) {\n                                CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n\n                                Status = write_data_phys(c->devices[j + k]->devobj, c->devices[j + k]->fileobj, cis[j + k].offset + offset - c->offset,\n                                                         context->stripes[j + goodstripe].buf, context->stripes[j + goodstripe].length);\n\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"write_data_phys returned %08lx\\n\", Status);\n                                    log_device_error(Vcb, c->devices[j + k], BTRFS_DEV_STAT_WRITE_ERRORS);\n                                    goto end;\n                                }\n                            }\n                        }\n                    }\n                }\n            } else {\n                uint32_t so = 0;\n                bool recovered = false;\n\n                if (csum) {\n                    for (k = 0; k < sub_stripes; k++) {\n                        if (c->devices[j + k]->devobj) {\n                            context->stripes[j + k].bad_csums = ExAllocatePoolWithTag(PagedPool, (context->stripes[j + k].length * Vcb->csum_size) >> Vcb->sector_shift,\n                                                                                      ALLOC_TAG);\n                            if (!context->stripes[j + k].bad_csums) {\n                                ERR(\"out of memory\\n\");\n                                Status = STATUS_INSUFFICIENT_RESOURCES;\n                                goto end;\n                            }\n\n                            do_calc_job(Vcb, context->stripes[j + k].buf, context->stripes[j + k].length >> Vcb->sector_shift, context->stripes[j + k].bad_csums);\n                        }\n                    }\n                } else {\n                    for (k = 0; k < sub_stripes; k++) {\n                        if (c->devices[j + k]->devobj) {\n                            ULONG l;\n\n                            context->stripes[j + k].bad_csums = ExAllocatePoolWithTag(PagedPool, context->stripes[j + k].length * Vcb->csum_size / Vcb->superblock.node_size,\n                                                                                      ALLOC_TAG);\n                            if (!context->stripes[j + k].bad_csums) {\n                                ERR(\"out of memory\\n\");\n                                Status = STATUS_INSUFFICIENT_RESOURCES;\n                                goto end;\n                            }\n\n                            for (l = 0; l < context->stripes[j + k].length / Vcb->superblock.node_size; l++) {\n                                tree_header* th = (tree_header*)&context->stripes[j + k].buf[l * Vcb->superblock.node_size];\n\n                                get_tree_checksum(Vcb, th, (uint8_t*)context->stripes[j + k].bad_csums + (Vcb->csum_size * l));\n                            }\n                        }\n                    }\n                }\n\n                pos = 0;\n\n                stripe = startoffstripe;\n                while (pos < length) {\n                    uint32_t readlen;\n\n                    if (pos == 0)\n                        readlen = (uint32_t)min(context->stripes[stripe * sub_stripes].length,\n                                      c->chunk_item->stripe_length - (context->stripes[stripe * sub_stripes].start % c->chunk_item->stripe_length));\n                    else\n                        readlen = min(length - pos, (uint32_t)c->chunk_item->stripe_length);\n\n                    if (stripe == j / sub_stripes) {\n                        ULONG l;\n\n                        if (csum) {\n                            for (l = 0; l < readlen; l += Vcb->superblock.sector_size) {\n                                bool has_error = false;\n\n                                goodstripe = 0xffffffff;\n                                for (k = 0; k < sub_stripes; k++) {\n                                    if (c->devices[j + k]->devobj) {\n                                        if (RtlCompareMemory((uint8_t*)context->stripes[j + k].bad_csums + ((so * Vcb->csum_size) >> Vcb->sector_shift),\n                                            (uint8_t*)csum + ((pos * Vcb->csum_size) >> Vcb->sector_shift),\n                                            Vcb->csum_size) != Vcb->csum_size) {\n                                            has_error = true;\n                                        } else\n                                            goodstripe = k;\n                                    }\n                                }\n\n                                if (has_error) {\n                                    if (goodstripe != 0xffffffff) {\n                                        for (k = 0; k < sub_stripes; k++) {\n                                            if (c->devices[j + k]->devobj &&\n                                                RtlCompareMemory((uint8_t*)context->stripes[j + k].bad_csums + ((so * Vcb->csum_size) >> Vcb->sector_shift),\n                                                                 (uint8_t*)csum + ((pos * Vcb->csum_size) >> Vcb->sector_shift),\n                                                                 Vcb->csum_size) != Vcb->csum_size) {\n                                                uint64_t addr = offset + pos;\n\n                                                log_error(Vcb, addr, c->devices[j + k]->devitem.dev_id, false, true, false);\n\n                                                recovered = true;\n\n                                                RtlCopyMemory(context->stripes[j + k].buf + so, context->stripes[j + goodstripe].buf + so,\n                                                              Vcb->superblock.sector_size);\n                                            }\n                                        }\n                                    } else {\n                                        uint64_t addr = offset + pos;\n\n                                        for (k = 0; k < sub_stripes; k++) {\n                                            if (c->devices[j + j]->devobj) {\n                                                log_error(Vcb, addr, c->devices[j + k]->devitem.dev_id, false, false, false);\n                                                log_device_error(Vcb, c->devices[j + k], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                                            }\n                                        }\n                                    }\n                                }\n\n                                pos += Vcb->superblock.sector_size;\n                                so += Vcb->superblock.sector_size;\n                            }\n                        } else {\n                            for (l = 0; l < readlen; l += Vcb->superblock.node_size) {\n                                for (k = 0; k < sub_stripes; k++) {\n                                    if (c->devices[j + k]->devobj) {\n                                        tree_header* th = (tree_header*)&context->stripes[j + k].buf[so];\n                                        uint64_t addr = offset + pos;\n\n                                        if (RtlCompareMemory((uint8_t*)context->stripes[j + k].bad_csums + (so * Vcb->csum_size / Vcb->superblock.node_size), th, Vcb->csum_size) != Vcb->csum_size || th->address != addr) {\n                                            ULONG m;\n\n                                            recovered = false;\n\n                                            for (m = 0; m < sub_stripes; m++) {\n                                                if (m != k) {\n                                                    tree_header* th2 = (tree_header*)&context->stripes[j + m].buf[so];\n\n                                                    if (RtlCompareMemory((uint8_t*)context->stripes[j + m].bad_csums + (so * Vcb->csum_size / Vcb->superblock.node_size), th2, Vcb->csum_size) == Vcb->csum_size && th2->address == addr) {\n                                                        log_error(Vcb, addr, c->devices[j + k]->devitem.dev_id, true, true, false);\n\n                                                        RtlCopyMemory(th, th2, Vcb->superblock.node_size);\n\n                                                        recovered = true;\n                                                        break;\n                                                    } else\n                                                        log_device_error(Vcb, c->devices[j + m], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                                                }\n                                            }\n\n                                            if (!recovered)\n                                                log_error(Vcb, addr, c->devices[j + k]->devitem.dev_id, true, false, false);\n                                        }\n                                    }\n                                }\n\n                                pos += Vcb->superblock.node_size;\n                                so += Vcb->superblock.node_size;\n                            }\n                        }\n                    } else\n                        pos += readlen;\n\n                    stripe = (stripe + 1) % (c->chunk_item->num_stripes / sub_stripes);\n                }\n\n                if (recovered) {\n                    // write good data over bad\n\n                    for (k = 0; k < sub_stripes; k++) {\n                        if (c->devices[j + k]->devobj && !c->devices[j + k]->readonly) {\n                            CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n\n                            Status = write_data_phys(c->devices[j + k]->devobj, c->devices[j + k]->fileobj, cis[j + k].offset + offset - c->offset,\n                                                     context->stripes[j + k].buf, context->stripes[j + k].length);\n\n                            if (!NT_SUCCESS(Status)) {\n                                ERR(\"write_data_phys returned %08lx\\n\", Status);\n                                log_device_error(Vcb, c->devices[j + k], BTRFS_DEV_STAT_WRITE_ERRORS);\n                                goto end;\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExFreePool(stripeoff);\n\n    return Status;\n}\n\nstatic NTSTATUS scrub_extent(device_extension* Vcb, chunk* c, ULONG type, uint64_t offset, uint32_t size, void* csum) {\n    ULONG i;\n    scrub_context context;\n    CHUNK_ITEM_STRIPE* cis;\n    NTSTATUS Status;\n    uint16_t startoffstripe = 0, num_missing, allowed_missing;\n\n    TRACE(\"(%p, %p, %lx, %I64x, %x, %p)\\n\", Vcb, c, type, offset, size, csum);\n\n    context.stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(scrub_context_stripe) * c->chunk_item->num_stripes, ALLOC_TAG);\n    if (!context.stripes) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    RtlZeroMemory(context.stripes, sizeof(scrub_context_stripe) * c->chunk_item->num_stripes);\n\n    context.stripes_left = 0;\n\n    cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n\n    if (type == BLOCK_FLAG_RAID0) {\n        uint64_t startoff, endoff;\n        uint16_t endoffstripe;\n\n        get_raid0_offset(offset - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &startoff, &startoffstripe);\n        get_raid0_offset(offset + size - c->offset - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &endoff, &endoffstripe);\n\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            if (startoffstripe > i)\n                context.stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n            else if (startoffstripe == i)\n                context.stripes[i].start = startoff;\n            else\n                context.stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length);\n\n            if (endoffstripe > i)\n                context.stripes[i].length = (uint32_t)(endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length - context.stripes[i].start);\n            else if (endoffstripe == i)\n                context.stripes[i].length = (uint32_t)(endoff + 1 - context.stripes[i].start);\n            else\n                context.stripes[i].length = (uint32_t)(endoff - (endoff % c->chunk_item->stripe_length) - context.stripes[i].start);\n        }\n\n        allowed_missing = 0;\n    } else if (type == BLOCK_FLAG_RAID10) {\n        uint64_t startoff, endoff;\n        uint16_t endoffstripe, j, sub_stripes = max(c->chunk_item->sub_stripes, 1);\n\n        get_raid0_offset(offset - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes / sub_stripes, &startoff, &startoffstripe);\n        get_raid0_offset(offset + size - c->offset - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes / sub_stripes, &endoff, &endoffstripe);\n\n        if ((c->chunk_item->num_stripes % sub_stripes) != 0) {\n            ERR(\"chunk %I64x: num_stripes %x was not a multiple of sub_stripes %x!\\n\", c->offset, c->chunk_item->num_stripes, sub_stripes);\n            Status = STATUS_INTERNAL_ERROR;\n            goto end;\n        }\n\n        startoffstripe *= sub_stripes;\n        endoffstripe *= sub_stripes;\n\n        for (i = 0; i < c->chunk_item->num_stripes; i += sub_stripes) {\n            if (startoffstripe > i)\n                context.stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n            else if (startoffstripe == i)\n                context.stripes[i].start = startoff;\n            else\n                context.stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length);\n\n            if (endoffstripe > i)\n                context.stripes[i].length = (uint32_t)(endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length - context.stripes[i].start);\n            else if (endoffstripe == i)\n                context.stripes[i].length = (uint32_t)(endoff + 1 - context.stripes[i].start);\n            else\n                context.stripes[i].length = (uint32_t)(endoff - (endoff % c->chunk_item->stripe_length) - context.stripes[i].start);\n\n            for (j = 1; j < sub_stripes; j++) {\n                context.stripes[i+j].start = context.stripes[i].start;\n                context.stripes[i+j].length = context.stripes[i].length;\n            }\n        }\n\n        startoffstripe /= sub_stripes;\n        allowed_missing = 1;\n    } else\n        allowed_missing = c->chunk_item->num_stripes - 1;\n\n    num_missing = 0;\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        PIO_STACK_LOCATION IrpSp;\n\n        context.stripes[i].context = (struct _scrub_context*)&context;\n\n        if (type == BLOCK_FLAG_DUPLICATE) {\n            context.stripes[i].start = offset - c->offset;\n            context.stripes[i].length = size;\n        } else if (type != BLOCK_FLAG_RAID0 && type != BLOCK_FLAG_RAID10) {\n            ERR(\"unexpected chunk type %lx\\n\", type);\n            Status = STATUS_INTERNAL_ERROR;\n            goto end;\n        }\n\n        if (!c->devices[i]->devobj) {\n            num_missing++;\n\n            if (num_missing > allowed_missing) {\n                ERR(\"too many missing devices (at least %u, maximum allowed %u)\\n\", num_missing, allowed_missing);\n                Status = STATUS_INTERNAL_ERROR;\n                goto end;\n            }\n        } else if (context.stripes[i].length > 0) {\n            context.stripes[i].buf = ExAllocatePoolWithTag(NonPagedPool, context.stripes[i].length, ALLOC_TAG);\n\n            if (!context.stripes[i].buf) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            context.stripes[i].Irp = IoAllocateIrp(c->devices[i]->devobj->StackSize, false);\n\n            if (!context.stripes[i].Irp) {\n                ERR(\"IoAllocateIrp failed\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            IrpSp = IoGetNextIrpStackLocation(context.stripes[i].Irp);\n            IrpSp->MajorFunction = IRP_MJ_READ;\n            IrpSp->FileObject = c->devices[i]->fileobj;\n\n            if (c->devices[i]->devobj->Flags & DO_BUFFERED_IO) {\n                context.stripes[i].Irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPool, context.stripes[i].length, ALLOC_TAG);\n                if (!context.stripes[i].Irp->AssociatedIrp.SystemBuffer) {\n                    ERR(\"out of memory\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto end;\n                }\n\n                context.stripes[i].Irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION;\n\n                context.stripes[i].Irp->UserBuffer = context.stripes[i].buf;\n            } else if (c->devices[i]->devobj->Flags & DO_DIRECT_IO) {\n                context.stripes[i].Irp->MdlAddress = IoAllocateMdl(context.stripes[i].buf, context.stripes[i].length, false, false, NULL);\n                if (!context.stripes[i].Irp->MdlAddress) {\n                    ERR(\"IoAllocateMdl failed\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto end;\n                }\n\n                Status = STATUS_SUCCESS;\n\n                try {\n                    MmProbeAndLockPages(context.stripes[i].Irp->MdlAddress, KernelMode, IoWriteAccess);\n                } except (EXCEPTION_EXECUTE_HANDLER) {\n                    Status = GetExceptionCode();\n                }\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n                    IoFreeMdl(context.stripes[i].Irp->MdlAddress);\n                    context.stripes[i].Irp->MdlAddress = NULL;\n                    goto end;\n                }\n            } else\n                context.stripes[i].Irp->UserBuffer = context.stripes[i].buf;\n\n            IrpSp->Parameters.Read.Length = context.stripes[i].length;\n            IrpSp->Parameters.Read.ByteOffset.QuadPart = context.stripes[i].start + cis[i].offset;\n\n            context.stripes[i].Irp->UserIosb = &context.stripes[i].iosb;\n\n            IoSetCompletionRoutine(context.stripes[i].Irp, scrub_read_completion, &context.stripes[i], true, true, true);\n\n            context.stripes_left++;\n\n            Vcb->scrub.data_scrubbed += context.stripes[i].length;\n        }\n    }\n\n    if (context.stripes_left == 0) {\n        ERR(\"error - not reading any stripes\\n\");\n        Status = STATUS_INTERNAL_ERROR;\n        goto end;\n    }\n\n    KeInitializeEvent(&context.Event, NotificationEvent, false);\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (c->devices[i]->devobj && context.stripes[i].length > 0)\n            IoCallDriver(c->devices[i]->devobj, context.stripes[i].Irp);\n    }\n\n    KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n\n    // return an error if any of the stripes returned an error\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (!NT_SUCCESS(context.stripes[i].iosb.Status)) {\n            Status = context.stripes[i].iosb.Status;\n            log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_READ_ERRORS);\n            goto end;\n        }\n    }\n\n    if (type == BLOCK_FLAG_DUPLICATE) {\n        Status = scrub_extent_dup(Vcb, c, offset, csum, &context);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"scrub_extent_dup returned %08lx\\n\", Status);\n            goto end;\n        }\n    } else if (type == BLOCK_FLAG_RAID0) {\n        Status = scrub_extent_raid0(Vcb, c, offset, size, startoffstripe, csum, &context);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"scrub_extent_raid0 returned %08lx\\n\", Status);\n            goto end;\n        }\n    } else if (type == BLOCK_FLAG_RAID10) {\n        Status = scrub_extent_raid10(Vcb, c, offset, size, startoffstripe, csum, &context);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"scrub_extent_raid10 returned %08lx\\n\", Status);\n            goto end;\n        }\n    }\n\nend:\n    if (context.stripes) {\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            if (context.stripes[i].Irp) {\n                if (c->devices[i]->devobj->Flags & DO_DIRECT_IO && context.stripes[i].Irp->MdlAddress) {\n                    MmUnlockPages(context.stripes[i].Irp->MdlAddress);\n                    IoFreeMdl(context.stripes[i].Irp->MdlAddress);\n                }\n                IoFreeIrp(context.stripes[i].Irp);\n            }\n\n            if (context.stripes[i].buf)\n                ExFreePool(context.stripes[i].buf);\n\n            if (context.stripes[i].bad_csums)\n                ExFreePool(context.stripes[i].bad_csums);\n        }\n\n        ExFreePool(context.stripes);\n    }\n\n    return Status;\n}\n\nstatic NTSTATUS scrub_data_extent(device_extension* Vcb, chunk* c, uint64_t offset, ULONG type, void* csum, RTL_BITMAP* bmp, ULONG bmplen) {\n    NTSTATUS Status;\n    ULONG runlength, index;\n\n    runlength = RtlFindFirstRunClear(bmp, &index);\n\n    while (runlength != 0) {\n        if (index >= bmplen)\n            break;\n\n        if (index + runlength >= bmplen) {\n            runlength = bmplen - index;\n\n            if (runlength == 0)\n                break;\n        }\n\n        do {\n            ULONG rl;\n\n            if (runlength << Vcb->sector_shift > SCRUB_UNIT)\n                rl = SCRUB_UNIT >> Vcb->sector_shift;\n            else\n                rl = runlength;\n\n            Status = scrub_extent(Vcb, c, type, offset + ((uint64_t)index << Vcb->sector_shift),\n                                  rl << Vcb->sector_shift, (uint8_t*)csum + (index * Vcb->csum_size));\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"scrub_data_extent_dup returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            runlength -= rl;\n            index += rl;\n        } while (runlength > 0);\n\n        runlength = RtlFindNextForwardRunClear(bmp, index, &index);\n    }\n\n    return STATUS_SUCCESS;\n}\n\ntypedef struct {\n    uint8_t* buf;\n    PIRP Irp;\n    void* context;\n    IO_STATUS_BLOCK iosb;\n    uint64_t offset;\n    bool rewrite, missing;\n    RTL_BITMAP error;\n    ULONG* errorarr;\n} scrub_context_raid56_stripe;\n\ntypedef struct {\n    scrub_context_raid56_stripe* stripes;\n    LONG stripes_left;\n    KEVENT Event;\n    RTL_BITMAP alloc;\n    RTL_BITMAP has_csum;\n    RTL_BITMAP is_tree;\n    void* csum;\n    uint8_t* parity_scratch;\n    uint8_t* parity_scratch2;\n} scrub_context_raid56;\n\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall scrub_read_completion_raid56(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {\n    scrub_context_raid56_stripe* stripe = conptr;\n    scrub_context_raid56* context = (scrub_context_raid56*)stripe->context;\n    LONG left = InterlockedDecrement(&context->stripes_left);\n\n    UNUSED(DeviceObject);\n\n    stripe->iosb = Irp->IoStatus;\n\n    if (left == 0)\n        KeSetEvent(&context->Event, 0, false);\n\n    return STATUS_MORE_PROCESSING_REQUIRED;\n}\n\nstatic void scrub_raid5_stripe(device_extension* Vcb, chunk* c, scrub_context_raid56* context, uint64_t stripe_start, uint64_t bit_start,\n                               uint64_t num, uint16_t missing_devices) {\n    ULONG sectors_per_stripe = (ULONG)(c->chunk_item->stripe_length >> Vcb->sector_shift), off;\n    uint16_t stripe, parity = (bit_start + num + c->chunk_item->num_stripes - 1) % c->chunk_item->num_stripes;\n    uint64_t stripeoff;\n\n    stripe = (parity + 1) % c->chunk_item->num_stripes;\n    off = (ULONG)(bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 1);\n    stripeoff = num * sectors_per_stripe;\n\n    if (missing_devices == 0)\n        RtlCopyMemory(context->parity_scratch, &context->stripes[parity].buf[num * c->chunk_item->stripe_length], (ULONG)c->chunk_item->stripe_length);\n\n    while (stripe != parity) {\n        RtlClearAllBits(&context->stripes[stripe].error);\n\n        for (ULONG i = 0; i < sectors_per_stripe; i++) {\n            if (c->devices[stripe]->devobj && RtlCheckBit(&context->alloc, off)) {\n                if (RtlCheckBit(&context->is_tree, off)) {\n                    tree_header* th = (tree_header*)&context->stripes[stripe].buf[stripeoff << Vcb->sector_shift];\n                    uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 1) * c->chunk_item->stripe_length) + (off << Vcb->sector_shift);\n\n                    if (!check_tree_checksum(Vcb, th) || th->address != addr) {\n                        RtlSetBits(&context->stripes[stripe].error, i, Vcb->superblock.node_size >> Vcb->sector_shift);\n                        log_device_error(Vcb, c->devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n\n                        if (missing_devices > 0)\n                            log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, true, false, false);\n                    }\n\n                    off += Vcb->superblock.node_size >> Vcb->sector_shift;\n                    stripeoff += Vcb->superblock.node_size >> Vcb->sector_shift;\n                    i += (Vcb->superblock.node_size >> Vcb->sector_shift) - 1;\n\n                    continue;\n                } else if (RtlCheckBit(&context->has_csum, off)) {\n                    if (!check_sector_csum(Vcb, context->stripes[stripe].buf + (stripeoff << Vcb->sector_shift), (uint8_t*)context->csum + (Vcb->csum_size * off))) {\n                        RtlSetBit(&context->stripes[stripe].error, i);\n                        log_device_error(Vcb, c->devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n\n                        if (missing_devices > 0) {\n                            uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 1) * c->chunk_item->stripe_length) + (off << Vcb->sector_shift);\n\n                            log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, false, false, false);\n                        }\n                    }\n                }\n            }\n\n            off++;\n            stripeoff++;\n        }\n\n        if (missing_devices == 0)\n            do_xor(context->parity_scratch, &context->stripes[stripe].buf[num * c->chunk_item->stripe_length], (ULONG)c->chunk_item->stripe_length);\n\n        stripe = (stripe + 1) % c->chunk_item->num_stripes;\n        stripeoff = num * sectors_per_stripe;\n    }\n\n    // check parity\n\n    if (missing_devices == 0) {\n        RtlClearAllBits(&context->stripes[parity].error);\n\n        for (ULONG i = 0; i < sectors_per_stripe; i++) {\n            ULONG o, j;\n\n            o = i << Vcb->sector_shift;\n            for (j = 0; j < Vcb->superblock.sector_size; j++) { // FIXME - use SSE\n                if (context->parity_scratch[o] != 0) {\n                    RtlSetBit(&context->stripes[parity].error, i);\n                    break;\n                }\n                o++;\n            }\n        }\n    }\n\n    // log and fix errors\n\n    if (missing_devices > 0)\n        return;\n\n    for (ULONG i = 0; i < sectors_per_stripe; i++) {\n        ULONG num_errors = 0, bad_off = 0;\n        uint64_t bad_stripe = 0;\n        bool alloc = false;\n\n        stripe = (parity + 1) % c->chunk_item->num_stripes;\n        off = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 1)) + i;\n\n        while (stripe != parity) {\n            if (RtlCheckBit(&context->alloc, off)) {\n                alloc = true;\n\n                if (RtlCheckBit(&context->stripes[stripe].error, i)) {\n                    bad_stripe = stripe;\n                    bad_off = off;\n                    num_errors++;\n                }\n            }\n\n            off += sectors_per_stripe;\n            stripe = (stripe + 1) % c->chunk_item->num_stripes;\n        }\n\n        if (!alloc)\n            continue;\n\n        if (num_errors == 0 && !RtlCheckBit(&context->stripes[parity].error, i)) // everything fine\n            continue;\n\n        if (num_errors == 0 && RtlCheckBit(&context->stripes[parity].error, i)) { // parity error\n            uint64_t addr;\n\n            do_xor(&context->stripes[parity].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                   &context->parity_scratch[i << Vcb->sector_shift],\n                   Vcb->superblock.sector_size);\n\n            bad_off = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 1)) + i;\n            addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 1) * c->chunk_item->stripe_length) + (bad_off << Vcb->sector_shift);\n\n            context->stripes[parity].rewrite = true;\n\n            log_error(Vcb, addr, c->devices[parity]->devitem.dev_id, false, true, true);\n            log_device_error(Vcb, c->devices[parity], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n        } else if (num_errors == 1) {\n            uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 1) * c->chunk_item->stripe_length) + (bad_off << Vcb->sector_shift);\n\n            if (RtlCheckBit(&context->is_tree, bad_off)) {\n                tree_header* th;\n\n                do_xor(&context->parity_scratch[i << Vcb->sector_shift],\n                       &context->stripes[bad_stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                       Vcb->superblock.node_size);\n\n                th = (tree_header*)&context->parity_scratch[i << Vcb->sector_shift];\n\n                if (check_tree_checksum(Vcb, th) && th->address == addr) {\n                    RtlCopyMemory(&context->stripes[bad_stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                  &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.node_size);\n\n                    context->stripes[bad_stripe].rewrite = true;\n\n                    RtlClearBits(&context->stripes[bad_stripe].error, i + 1, (Vcb->superblock.node_size >> Vcb->sector_shift) - 1);\n\n                    log_error(Vcb, addr, c->devices[bad_stripe]->devitem.dev_id, true, true, false);\n                } else\n                    log_error(Vcb, addr, c->devices[bad_stripe]->devitem.dev_id, true, false, false);\n            } else {\n                uint8_t hash[MAX_HASH_SIZE];\n\n                do_xor(&context->parity_scratch[i << Vcb->sector_shift],\n                       &context->stripes[bad_stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                       Vcb->superblock.sector_size);\n\n                get_sector_csum(Vcb, &context->parity_scratch[i << Vcb->sector_shift], hash);\n\n                if (RtlCompareMemory(hash, (uint8_t*)context->csum + (Vcb->csum_size * bad_off), Vcb->csum_size) == Vcb->csum_size) {\n                    RtlCopyMemory(&context->stripes[bad_stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                  &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.sector_size);\n\n                    context->stripes[bad_stripe].rewrite = true;\n\n                    log_error(Vcb, addr, c->devices[bad_stripe]->devitem.dev_id, false, true, false);\n                } else\n                    log_error(Vcb, addr, c->devices[bad_stripe]->devitem.dev_id, false, false, false);\n            }\n        } else {\n            stripe = (parity + 1) % c->chunk_item->num_stripes;\n            off = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 1)) + i;\n\n            while (stripe != parity) {\n                if (RtlCheckBit(&context->alloc, off)) {\n                    if (RtlCheckBit(&context->stripes[stripe].error, i)) {\n                        uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 1) * c->chunk_item->stripe_length) + (off << Vcb->sector_shift);\n\n                        log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, RtlCheckBit(&context->is_tree, off), false, false);\n                    }\n                }\n\n                off += sectors_per_stripe;\n                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n            }\n        }\n    }\n}\n\nstatic void scrub_raid6_stripe(device_extension* Vcb, chunk* c, scrub_context_raid56* context, uint64_t stripe_start, uint64_t bit_start,\n                               uint64_t num, uint16_t missing_devices) {\n    ULONG sectors_per_stripe = (ULONG)(c->chunk_item->stripe_length >> Vcb->sector_shift), off;\n    uint16_t stripe, parity1 = (bit_start + num + c->chunk_item->num_stripes - 2) % c->chunk_item->num_stripes;\n    uint16_t parity2 = (parity1 + 1) % c->chunk_item->num_stripes;\n    uint64_t stripeoff;\n\n    stripe = (parity1 + 2) % c->chunk_item->num_stripes;\n    off = (ULONG)(bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 2);\n    stripeoff = num * sectors_per_stripe;\n\n    if (c->devices[parity1]->devobj)\n        RtlCopyMemory(context->parity_scratch, &context->stripes[parity1].buf[num * c->chunk_item->stripe_length], (ULONG)c->chunk_item->stripe_length);\n\n    if (c->devices[parity2]->devobj)\n        RtlZeroMemory(context->parity_scratch2, (ULONG)c->chunk_item->stripe_length);\n\n    while (stripe != parity1) {\n        RtlClearAllBits(&context->stripes[stripe].error);\n\n        for (ULONG i = 0; i < sectors_per_stripe; i++) {\n            if (c->devices[stripe]->devobj && RtlCheckBit(&context->alloc, off)) {\n                if (RtlCheckBit(&context->is_tree, off)) {\n                    tree_header* th = (tree_header*)&context->stripes[stripe].buf[stripeoff << Vcb->sector_shift];\n                    uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (off << Vcb->sector_shift);\n\n                    if (!check_tree_checksum(Vcb, th) || th->address != addr) {\n                        RtlSetBits(&context->stripes[stripe].error, i, Vcb->superblock.node_size >> Vcb->sector_shift);\n                        log_device_error(Vcb, c->devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n\n                        if (missing_devices == 2)\n                            log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, true, false, false);\n                    }\n\n                    off += Vcb->superblock.node_size >> Vcb->sector_shift;\n                    stripeoff += Vcb->superblock.node_size >> Vcb->sector_shift;\n                    i += (Vcb->superblock.node_size >> Vcb->sector_shift) - 1;\n\n                    continue;\n                } else if (RtlCheckBit(&context->has_csum, off)) {\n                    uint8_t hash[MAX_HASH_SIZE];\n\n                    get_sector_csum(Vcb, context->stripes[stripe].buf + (stripeoff << Vcb->sector_shift), hash);\n\n                    if (RtlCompareMemory(hash, (uint8_t*)context->csum + (Vcb->csum_size * off), Vcb->csum_size) != Vcb->csum_size) {\n                        uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (off << Vcb->sector_shift);\n\n                        RtlSetBit(&context->stripes[stripe].error, i);\n                        log_device_error(Vcb, c->devices[stripe], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n\n                        if (missing_devices == 2)\n                            log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, false, false, false);\n                    }\n                }\n            }\n\n            off++;\n            stripeoff++;\n        }\n\n        if (c->devices[parity1]->devobj)\n            do_xor(context->parity_scratch, &context->stripes[stripe].buf[num * c->chunk_item->stripe_length], (uint32_t)c->chunk_item->stripe_length);\n\n        stripe = (stripe + 1) % c->chunk_item->num_stripes;\n        stripeoff = num * sectors_per_stripe;\n    }\n\n    RtlClearAllBits(&context->stripes[parity1].error);\n\n    if (missing_devices == 0 || (missing_devices == 1 && !c->devices[parity2]->devobj)) {\n        // check parity 1\n\n        for (ULONG i = 0; i < sectors_per_stripe; i++) {\n            ULONG o, j;\n\n            o = i << Vcb->sector_shift;\n            for (j = 0; j < Vcb->superblock.sector_size; j++) { // FIXME - use SSE\n                if (context->parity_scratch[o] != 0) {\n                    RtlSetBit(&context->stripes[parity1].error, i);\n                    break;\n                }\n                o++;\n            }\n        }\n    }\n\n    RtlClearAllBits(&context->stripes[parity2].error);\n\n    if (missing_devices == 0 || (missing_devices == 1 && !c->devices[parity1]->devobj)) {\n        // check parity 2\n\n        stripe = parity1 == 0 ? (c->chunk_item->num_stripes - 1) : (parity1 - 1);\n\n        while (stripe != parity2) {\n            galois_double(context->parity_scratch2, (uint32_t)c->chunk_item->stripe_length);\n            do_xor(context->parity_scratch2, &context->stripes[stripe].buf[num * c->chunk_item->stripe_length], (uint32_t)c->chunk_item->stripe_length);\n\n            stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1);\n        }\n\n        for (ULONG i = 0; i < sectors_per_stripe; i++) {\n            if (RtlCompareMemory(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                 &context->parity_scratch2[i << Vcb->sector_shift], Vcb->superblock.sector_size) != Vcb->superblock.sector_size)\n                RtlSetBit(&context->stripes[parity2].error, i);\n        }\n    }\n\n    if (missing_devices == 2)\n        return;\n\n    // log and fix errors\n\n    for (ULONG i = 0; i < sectors_per_stripe; i++) {\n        ULONG num_errors = 0;\n        uint64_t bad_stripe1 = 0, bad_stripe2 = 0;\n        ULONG bad_off1 = 0, bad_off2 = 0;\n        bool alloc = false;\n\n        stripe = (parity1 + 2) % c->chunk_item->num_stripes;\n        off = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 2)) + i;\n\n        while (stripe != parity1) {\n            if (RtlCheckBit(&context->alloc, off)) {\n                alloc = true;\n\n                if (!c->devices[stripe]->devobj || RtlCheckBit(&context->stripes[stripe].error, i)) {\n                    if (num_errors == 0) {\n                        bad_stripe1 = stripe;\n                        bad_off1 = off;\n                    } else if (num_errors == 1) {\n                        bad_stripe2 = stripe;\n                        bad_off2 = off;\n                    }\n                    num_errors++;\n                }\n            }\n\n            off += sectors_per_stripe;\n            stripe = (stripe + 1) % c->chunk_item->num_stripes;\n        }\n\n        if (!alloc)\n            continue;\n\n        if (num_errors == 0 && !RtlCheckBit(&context->stripes[parity1].error, i) && !RtlCheckBit(&context->stripes[parity2].error, i)) // everything fine\n            continue;\n\n        if (num_errors == 0) { // parity error\n            uint64_t addr;\n\n            if (RtlCheckBit(&context->stripes[parity1].error, i)) {\n                do_xor(&context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                       &context->parity_scratch[i << Vcb->sector_shift],\n                       Vcb->superblock.sector_size);\n\n                bad_off1 = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 2)) + i;\n                addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (bad_off1 << Vcb->sector_shift);\n\n                context->stripes[parity1].rewrite = true;\n\n                log_error(Vcb, addr, c->devices[parity1]->devitem.dev_id, false, true, true);\n                log_device_error(Vcb, c->devices[parity1], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n            }\n\n            if (RtlCheckBit(&context->stripes[parity2].error, i)) {\n                RtlCopyMemory(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                              &context->parity_scratch2[i << Vcb->sector_shift],\n                              Vcb->superblock.sector_size);\n\n                bad_off1 = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 2)) + i;\n                addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (bad_off1 << Vcb->sector_shift);\n\n                context->stripes[parity2].rewrite = true;\n\n                log_error(Vcb, addr, c->devices[parity2]->devitem.dev_id, false, true, true);\n                log_device_error(Vcb, c->devices[parity2], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n            }\n        } else if (num_errors == 1) {\n            uint32_t len;\n            uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (bad_off1 << Vcb->sector_shift);\n            uint8_t* scratch;\n\n            len = RtlCheckBit(&context->is_tree, bad_off1) ? Vcb->superblock.node_size : Vcb->superblock.sector_size;\n\n            scratch = ExAllocatePoolWithTag(PagedPool, len, ALLOC_TAG);\n            if (!scratch) {\n                ERR(\"out of memory\\n\");\n                return;\n            }\n\n            RtlZeroMemory(scratch, len);\n\n            do_xor(&context->parity_scratch[i << Vcb->sector_shift],\n                   &context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len);\n\n            stripe = parity1 == 0 ? (c->chunk_item->num_stripes - 1) : (parity1 - 1);\n\n            if (c->devices[parity2]->devobj) {\n                uint16_t stripe_num, bad_stripe_num = 0;\n\n                stripe_num = c->chunk_item->num_stripes - 3;\n                while (stripe != parity2) {\n                    galois_double(scratch, len);\n\n                    if (stripe != bad_stripe1)\n                        do_xor(scratch, &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len);\n                    else\n                        bad_stripe_num = stripe_num;\n\n                    stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1);\n                    stripe_num--;\n                }\n\n                do_xor(scratch, &context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len);\n\n                if (bad_stripe_num != 0)\n                    galois_divpower(scratch, (uint8_t)bad_stripe_num, len);\n            }\n\n            if (RtlCheckBit(&context->is_tree, bad_off1)) {\n                uint8_t hash1[MAX_HASH_SIZE];\n                uint8_t hash2[MAX_HASH_SIZE];\n                tree_header *th1 = NULL, *th2 = NULL;\n\n                if (c->devices[parity1]->devobj) {\n                    th1 = (tree_header*)&context->parity_scratch[i << Vcb->sector_shift];\n                    get_tree_checksum(Vcb, th1, hash1);\n                }\n\n                if (c->devices[parity2]->devobj) {\n                    th2 = (tree_header*)scratch;\n                    get_tree_checksum(Vcb, th2, hash2);\n                }\n\n                if ((c->devices[parity1]->devobj && RtlCompareMemory(hash1, th1, Vcb->csum_size) == Vcb->csum_size && th1->address == addr) ||\n                    (c->devices[parity2]->devobj && RtlCompareMemory(hash2, th2, Vcb->csum_size) == Vcb->csum_size && th2->address == addr)) {\n                    if (!c->devices[parity1]->devobj || RtlCompareMemory(hash1, th1, Vcb->csum_size) != Vcb->csum_size || th1->address != addr) {\n                        RtlCopyMemory(&context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                      scratch, Vcb->superblock.node_size);\n\n                        if (c->devices[parity1]->devobj) {\n                            // fix parity 1\n\n                            stripe = (parity1 + 2) % c->chunk_item->num_stripes;\n\n                            RtlCopyMemory(&context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                          &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                          Vcb->superblock.node_size);\n\n                            stripe = (stripe + 1) % c->chunk_item->num_stripes;\n\n                            while (stripe != parity1) {\n                                do_xor(&context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                       &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                       Vcb->superblock.node_size);\n\n                                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n                            }\n\n                            context->stripes[parity1].rewrite = true;\n\n                            log_error(Vcb, addr, c->devices[parity1]->devitem.dev_id, false, true, true);\n                            log_device_error(Vcb, c->devices[parity1], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                        }\n                    } else {\n                        RtlCopyMemory(&context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                      &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.node_size);\n\n                        if (!c->devices[parity2]->devobj || RtlCompareMemory(hash2, th2, Vcb->csum_size) != Vcb->csum_size || th2->address != addr) {\n                            // fix parity 2\n                            stripe = parity1 == 0 ? (c->chunk_item->num_stripes - 1) : (parity1 - 1);\n\n                            if (c->devices[parity2]->devobj) {\n                                RtlCopyMemory(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                              &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                              Vcb->superblock.node_size);\n\n                                stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1);\n\n                                while (stripe != parity2) {\n                                    galois_double(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.node_size);\n\n                                    do_xor(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                           &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                           Vcb->superblock.node_size);\n\n                                    stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1);\n                                }\n\n                                context->stripes[parity2].rewrite = true;\n\n                                log_error(Vcb, addr, c->devices[parity2]->devitem.dev_id, false, true, true);\n                                log_device_error(Vcb, c->devices[parity2], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                            }\n                        }\n                    }\n\n                    context->stripes[bad_stripe1].rewrite = true;\n\n                    RtlClearBits(&context->stripes[bad_stripe1].error, i + 1, (Vcb->superblock.node_size >> Vcb->sector_shift) - 1);\n\n                    log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, true, true, false);\n                } else\n                    log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, true, false, false);\n            } else {\n                uint8_t hash1[MAX_HASH_SIZE];\n                uint8_t hash2[MAX_HASH_SIZE];\n\n                if (c->devices[parity1]->devobj)\n                    get_sector_csum(Vcb, &context->parity_scratch[i << Vcb->sector_shift], hash1);\n\n                if (c->devices[parity2]->devobj)\n                    get_sector_csum(Vcb, scratch, hash2);\n\n                if ((c->devices[parity1]->devobj && RtlCompareMemory(hash1, (uint8_t*)context->csum + (bad_off1 * Vcb->csum_size), Vcb->csum_size) == Vcb->csum_size) ||\n                    (c->devices[parity2]->devobj && RtlCompareMemory(hash2, (uint8_t*)context->csum + (bad_off1 * Vcb->csum_size), Vcb->csum_size) == Vcb->csum_size)) {\n                    if (c->devices[parity2]->devobj && RtlCompareMemory(hash2, (uint8_t*)context->csum + (bad_off1 * Vcb->csum_size), Vcb->csum_size) == Vcb->csum_size) {\n                        RtlCopyMemory(&context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                      scratch, Vcb->superblock.sector_size);\n\n                        if (c->devices[parity1]->devobj && RtlCompareMemory(hash1, (uint8_t*)context->csum + (bad_off1 * Vcb->csum_size), Vcb->csum_size) != Vcb->csum_size) {\n                            // fix parity 1\n\n                            stripe = (parity1 + 2) % c->chunk_item->num_stripes;\n\n                            RtlCopyMemory(&context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                          &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                        Vcb->superblock.sector_size);\n\n                            stripe = (stripe + 1) % c->chunk_item->num_stripes;\n\n                            while (stripe != parity1) {\n                                do_xor(&context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                       &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                       Vcb->superblock.sector_size);\n\n                                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n                            }\n\n                            context->stripes[parity1].rewrite = true;\n\n                            log_error(Vcb, addr, c->devices[parity1]->devitem.dev_id, false, true, true);\n                            log_device_error(Vcb, c->devices[parity1], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                        }\n                    } else {\n                        RtlCopyMemory(&context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                      &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.sector_size);\n\n                        if (c->devices[parity2]->devobj && RtlCompareMemory(hash2, (uint8_t*)context->csum + (bad_off1 * Vcb->csum_size), Vcb->csum_size) != Vcb->csum_size) {\n                            // fix parity 2\n                            stripe = parity1 == 0 ? (c->chunk_item->num_stripes - 1) : (parity1 - 1);\n\n                            RtlCopyMemory(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                          &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                          Vcb->superblock.sector_size);\n\n                            stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1);\n\n                            while (stripe != parity2) {\n                                galois_double(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], Vcb->superblock.sector_size);\n\n                                do_xor(&context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                       &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                       Vcb->superblock.sector_size);\n\n                                stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1);\n                            }\n\n                            context->stripes[parity2].rewrite = true;\n\n                            log_error(Vcb, addr, c->devices[parity2]->devitem.dev_id, false, true, true);\n                            log_device_error(Vcb, c->devices[parity2], BTRFS_DEV_STAT_CORRUPTION_ERRORS);\n                        }\n                    }\n\n                    context->stripes[bad_stripe1].rewrite = true;\n\n                    log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, false, true, false);\n                } else\n                    log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, false, false, false);\n            }\n\n            ExFreePool(scratch);\n        } else if (num_errors == 2 && missing_devices == 0) {\n            uint16_t x = 0, y = 0, k;\n            uint64_t addr;\n            uint32_t len = (RtlCheckBit(&context->is_tree, bad_off1) || RtlCheckBit(&context->is_tree, bad_off2)) ? Vcb->superblock.node_size : Vcb->superblock.sector_size;\n            uint8_t gyx, gx, denom, a, b, *p, *q, *pxy, *qxy;\n            uint32_t j;\n\n            stripe = parity1 == 0 ? (c->chunk_item->num_stripes - 1) : (parity1 - 1);\n\n            // put qxy in parity_scratch\n            // put pxy in parity_scratch2\n\n            k = c->chunk_item->num_stripes - 3;\n            if (stripe == bad_stripe1 || stripe == bad_stripe2) {\n                RtlZeroMemory(&context->parity_scratch[i << Vcb->sector_shift], len);\n                RtlZeroMemory(&context->parity_scratch2[i << Vcb->sector_shift], len);\n\n                if (stripe == bad_stripe1)\n                    x = k;\n                else\n                    y = k;\n            } else {\n                RtlCopyMemory(&context->parity_scratch[i << Vcb->sector_shift],\n                              &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len);\n                RtlCopyMemory(&context->parity_scratch2[i << Vcb->sector_shift],\n                              &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len);\n            }\n\n            stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1);\n\n            k--;\n            do {\n                galois_double(&context->parity_scratch[i << Vcb->sector_shift], len);\n\n                if (stripe != bad_stripe1 && stripe != bad_stripe2) {\n                    do_xor(&context->parity_scratch[i << Vcb->sector_shift],\n                           &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len);\n                    do_xor(&context->parity_scratch2[i << Vcb->sector_shift],\n                           &context->stripes[stripe].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len);\n                } else if (stripe == bad_stripe1)\n                    x = k;\n                else if (stripe == bad_stripe2)\n                    y = k;\n\n                stripe = stripe == 0 ? (c->chunk_item->num_stripes - 1) : (stripe - 1);\n                k--;\n            } while (stripe != parity2);\n\n            gyx = gpow2(y > x ? (y-x) : (255-x+y));\n            gx = gpow2(255-x);\n\n            denom = gdiv(1, gyx ^ 1);\n            a = gmul(gyx, denom);\n            b = gmul(gx, denom);\n\n            p = &context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)];\n            q = &context->stripes[parity2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)];\n            pxy = &context->parity_scratch2[i << Vcb->sector_shift];\n            qxy = &context->parity_scratch[i << Vcb->sector_shift];\n\n            for (j = 0; j < len; j++) {\n                *qxy = gmul(a, *p ^ *pxy) ^ gmul(b, *q ^ *qxy);\n\n                p++;\n                q++;\n                pxy++;\n                qxy++;\n            }\n\n            do_xor(&context->parity_scratch2[i << Vcb->sector_shift], &context->parity_scratch[i << Vcb->sector_shift], len);\n            do_xor(&context->parity_scratch2[i << Vcb->sector_shift], &context->stripes[parity1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)], len);\n\n            addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (bad_off1 << Vcb->sector_shift);\n\n            if (RtlCheckBit(&context->is_tree, bad_off1)) {\n                tree_header* th = (tree_header*)&context->parity_scratch[i << Vcb->sector_shift];\n\n                if (check_tree_checksum(Vcb, th) && th->address == addr) {\n                    RtlCopyMemory(&context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                  &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.node_size);\n\n                    context->stripes[bad_stripe1].rewrite = true;\n\n                    RtlClearBits(&context->stripes[bad_stripe1].error, i + 1, (Vcb->superblock.node_size >> Vcb->sector_shift) - 1);\n\n                    log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, true, true, false);\n                } else\n                    log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, true, false, false);\n            } else {\n                if (check_sector_csum(Vcb, &context->parity_scratch[i << Vcb->sector_shift], (uint8_t*)context->csum + (Vcb->csum_size * bad_off1))) {\n                    RtlCopyMemory(&context->stripes[bad_stripe1].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                  &context->parity_scratch[i << Vcb->sector_shift], Vcb->superblock.sector_size);\n\n                    context->stripes[bad_stripe1].rewrite = true;\n\n                    log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, false, true, false);\n                } else\n                    log_error(Vcb, addr, c->devices[bad_stripe1]->devitem.dev_id, false, false, false);\n            }\n\n            addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (bad_off2 << Vcb->sector_shift);\n\n            if (RtlCheckBit(&context->is_tree, bad_off2)) {\n                tree_header* th = (tree_header*)&context->parity_scratch2[i << Vcb->sector_shift];\n\n                if (check_tree_checksum(Vcb, th) && th->address == addr) {\n                    RtlCopyMemory(&context->stripes[bad_stripe2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                  &context->parity_scratch2[i << Vcb->sector_shift], Vcb->superblock.node_size);\n\n                    context->stripes[bad_stripe2].rewrite = true;\n\n                    RtlClearBits(&context->stripes[bad_stripe2].error, i + 1, (Vcb->superblock.node_size >> Vcb->sector_shift) - 1);\n\n                    log_error(Vcb, addr, c->devices[bad_stripe2]->devitem.dev_id, true, true, false);\n                } else\n                    log_error(Vcb, addr, c->devices[bad_stripe2]->devitem.dev_id, true, false, false);\n            } else {\n                if (check_sector_csum(Vcb, &context->parity_scratch2[i << Vcb->sector_shift], (uint8_t*)context->csum + (Vcb->csum_size * bad_off2))) {\n                    RtlCopyMemory(&context->stripes[bad_stripe2].buf[(num * c->chunk_item->stripe_length) + (i << Vcb->sector_shift)],\n                                  &context->parity_scratch2[i << Vcb->sector_shift], Vcb->superblock.sector_size);\n\n                    context->stripes[bad_stripe2].rewrite = true;\n\n                    log_error(Vcb, addr, c->devices[bad_stripe2]->devitem.dev_id, false, true, false);\n                } else\n                    log_error(Vcb, addr, c->devices[bad_stripe2]->devitem.dev_id, false, false, false);\n            }\n        } else {\n            stripe = (parity2 + 1) % c->chunk_item->num_stripes;\n            off = (ULONG)((bit_start + num - stripe_start) * sectors_per_stripe * (c->chunk_item->num_stripes - 2)) + i;\n\n            while (stripe != parity1) {\n                if (c->devices[stripe]->devobj && RtlCheckBit(&context->alloc, off)) {\n                    if (RtlCheckBit(&context->stripes[stripe].error, i)) {\n                        uint64_t addr = c->offset + (stripe_start * (c->chunk_item->num_stripes - 2) * c->chunk_item->stripe_length) + (off << Vcb->sector_shift);\n\n                        log_error(Vcb, addr, c->devices[stripe]->devitem.dev_id, RtlCheckBit(&context->is_tree, off), false, false);\n                    }\n                }\n\n                off += sectors_per_stripe;\n                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n            }\n        }\n    }\n}\n\nstatic NTSTATUS scrub_chunk_raid56_stripe_run(device_extension* Vcb, chunk* c, uint64_t stripe_start, uint64_t stripe_end) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    bool b;\n    uint64_t run_start, run_end, full_stripe_len, stripe;\n    uint32_t max_read, num_sectors;\n    ULONG arrlen, *allocarr, *csumarr = NULL, *treearr, num_parity_stripes = c->chunk_item->type & BLOCK_FLAG_RAID6 ? 2 : 1;\n    scrub_context_raid56 context;\n    uint16_t i;\n    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n\n    TRACE(\"(%p, %p, %I64x, %I64x)\\n\", Vcb, c, stripe_start, stripe_end);\n\n    full_stripe_len = (c->chunk_item->num_stripes - num_parity_stripes) * c->chunk_item->stripe_length;\n    run_start = c->offset + (stripe_start * full_stripe_len);\n    run_end = c->offset + ((stripe_end + 1) * full_stripe_len);\n\n    searchkey.obj_id = run_start;\n    searchkey.obj_type = TYPE_METADATA_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    num_sectors = (uint32_t)(((stripe_end - stripe_start + 1) * full_stripe_len) >> Vcb->sector_shift);\n    arrlen = (ULONG)sector_align((num_sectors / 8) + 1, sizeof(ULONG));\n\n    allocarr = ExAllocatePoolWithTag(PagedPool, arrlen, ALLOC_TAG);\n    if (!allocarr) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    treearr = ExAllocatePoolWithTag(PagedPool, arrlen, ALLOC_TAG);\n    if (!treearr) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(allocarr);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlInitializeBitMap(&context.alloc, allocarr, num_sectors);\n    RtlClearAllBits(&context.alloc);\n\n    RtlInitializeBitMap(&context.is_tree, treearr, num_sectors);\n    RtlClearAllBits(&context.is_tree);\n\n    context.parity_scratch = ExAllocatePoolWithTag(PagedPool, (ULONG)c->chunk_item->stripe_length, ALLOC_TAG);\n    if (!context.parity_scratch) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(allocarr);\n        ExFreePool(treearr);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    if (c->chunk_item->type & BLOCK_FLAG_DATA) {\n        csumarr = ExAllocatePoolWithTag(PagedPool, arrlen, ALLOC_TAG);\n        if (!csumarr) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(allocarr);\n            ExFreePool(treearr);\n            ExFreePool(context.parity_scratch);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlInitializeBitMap(&context.has_csum, csumarr, num_sectors);\n        RtlClearAllBits(&context.has_csum);\n\n        context.csum = ExAllocatePoolWithTag(PagedPool, num_sectors * Vcb->csum_size, ALLOC_TAG);\n        if (!context.csum) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(allocarr);\n            ExFreePool(treearr);\n            ExFreePool(context.parity_scratch);\n            ExFreePool(csumarr);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n    }\n\n    if (c->chunk_item->type & BLOCK_FLAG_RAID6) {\n        context.parity_scratch2 = ExAllocatePoolWithTag(PagedPool, (ULONG)c->chunk_item->stripe_length, ALLOC_TAG);\n        if (!context.parity_scratch2) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(allocarr);\n            ExFreePool(treearr);\n            ExFreePool(context.parity_scratch);\n\n            if (c->chunk_item->type & BLOCK_FLAG_DATA) {\n                ExFreePool(csumarr);\n                ExFreePool(context.csum);\n            }\n\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n    }\n\n    do {\n        traverse_ptr next_tp;\n\n        if (tp.item->key.obj_id >= run_end)\n            break;\n\n        if (tp.item->key.obj_type == TYPE_EXTENT_ITEM || tp.item->key.obj_type == TYPE_METADATA_ITEM) {\n            uint64_t size = tp.item->key.obj_type == TYPE_METADATA_ITEM ? Vcb->superblock.node_size : tp.item->key.offset;\n\n            if (tp.item->key.obj_id + size > run_start) {\n                uint64_t extent_start = max(run_start, tp.item->key.obj_id);\n                uint64_t extent_end = min(tp.item->key.obj_id + size, run_end);\n                bool extent_is_tree = false;\n\n                RtlSetBits(&context.alloc, (ULONG)((extent_start - run_start) >> Vcb->sector_shift), (ULONG)((extent_end - extent_start) >> Vcb->sector_shift));\n\n                if (tp.item->key.obj_type == TYPE_METADATA_ITEM)\n                    extent_is_tree = true;\n                else {\n                    EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data;\n\n                    if (tp.item->size < sizeof(EXTENT_ITEM)) {\n                        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n                        Status = STATUS_INTERNAL_ERROR;\n                        goto end;\n                    }\n\n                    if (ei->flags & EXTENT_ITEM_TREE_BLOCK)\n                        extent_is_tree = true;\n                }\n\n                if (extent_is_tree)\n                    RtlSetBits(&context.is_tree, (ULONG)((extent_start - run_start) >> Vcb->sector_shift), (ULONG)((extent_end - extent_start) >> Vcb->sector_shift));\n                else if (c->chunk_item->type & BLOCK_FLAG_DATA) {\n                    traverse_ptr tp2;\n                    bool b2;\n\n                    searchkey.obj_id = EXTENT_CSUM_ID;\n                    searchkey.obj_type = TYPE_EXTENT_CSUM;\n                    searchkey.offset = extent_start;\n\n                    Status = find_item(Vcb, Vcb->checksum_root, &tp2, &searchkey, false, NULL);\n                    if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) {\n                        ERR(\"find_item returned %08lx\\n\", Status);\n                        goto end;\n                    }\n\n                    do {\n                        traverse_ptr next_tp2;\n\n                        if (tp2.item->key.offset >= extent_end)\n                            break;\n\n                        if (tp2.item->key.offset >= extent_start) {\n                            uint64_t csum_start = max(extent_start, tp2.item->key.offset);\n                            uint64_t csum_end = min(extent_end, tp2.item->key.offset + (((uint64_t)tp2.item->size << Vcb->sector_shift) / Vcb->csum_size));\n\n                            RtlSetBits(&context.has_csum, (ULONG)((csum_start - run_start) >> Vcb->sector_shift), (ULONG)((csum_end - csum_start) >> Vcb->sector_shift));\n\n                            RtlCopyMemory((uint8_t*)context.csum + (((csum_start - run_start) * Vcb->csum_size) >> Vcb->sector_shift),\n                                          tp2.item->data + (((csum_start - tp2.item->key.offset) * Vcb->csum_size) >> Vcb->sector_shift),\n                                          (ULONG)(((csum_end - csum_start) * Vcb->csum_size) >> Vcb->sector_shift));\n                        }\n\n                        b2 = find_next_item(Vcb, &tp2, &next_tp2, false, NULL);\n\n                        if (b2)\n                            tp2 = next_tp2;\n                    } while (b2);\n                }\n            }\n        }\n\n        b = find_next_item(Vcb, &tp, &next_tp, false, NULL);\n\n        if (b)\n            tp = next_tp;\n    } while (b);\n\n    context.stripes = ExAllocatePoolWithTag(PagedPool, sizeof(scrub_context_raid56_stripe) * c->chunk_item->num_stripes, ALLOC_TAG);\n    if (!context.stripes) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    max_read = (uint32_t)min(1048576 / c->chunk_item->stripe_length, stripe_end - stripe_start + 1); // only process 1 MB of data at a time\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        context.stripes[i].buf = ExAllocatePoolWithTag(PagedPool, (ULONG)(max_read * c->chunk_item->stripe_length), ALLOC_TAG);\n        if (!context.stripes[i].buf) {\n            uint64_t j;\n\n            ERR(\"out of memory\\n\");\n\n            for (j = 0; j < i; j++) {\n                ExFreePool(context.stripes[j].buf);\n            }\n            ExFreePool(context.stripes);\n\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        context.stripes[i].errorarr = ExAllocatePoolWithTag(PagedPool, (ULONG)sector_align(((c->chunk_item->stripe_length >> Vcb->sector_shift) / 8) + 1, sizeof(ULONG)), ALLOC_TAG);\n        if (!context.stripes[i].errorarr) {\n            uint64_t j;\n\n            ERR(\"out of memory\\n\");\n\n            ExFreePool(context.stripes[i].buf);\n\n            for (j = 0; j < i; j++) {\n                ExFreePool(context.stripes[j].buf);\n            }\n            ExFreePool(context.stripes);\n\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        RtlInitializeBitMap(&context.stripes[i].error, context.stripes[i].errorarr, (ULONG)(c->chunk_item->stripe_length >> Vcb->sector_shift));\n\n        context.stripes[i].context = &context;\n        context.stripes[i].rewrite = false;\n    }\n\n    stripe = stripe_start;\n\n    Status = STATUS_SUCCESS;\n\n    chunk_lock_range(Vcb, c, run_start, run_end - run_start);\n\n    do {\n        ULONG read_stripes;\n        uint16_t missing_devices = 0;\n        bool need_wait = false;\n\n        if (max_read < stripe_end + 1 - stripe)\n            read_stripes = max_read;\n        else\n            read_stripes = (ULONG)(stripe_end + 1 - stripe);\n\n        context.stripes_left = c->chunk_item->num_stripes;\n\n        // read megabyte by megabyte\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            if (c->devices[i]->devobj) {\n                PIO_STACK_LOCATION IrpSp;\n\n                context.stripes[i].Irp = IoAllocateIrp(c->devices[i]->devobj->StackSize, false);\n\n                if (!context.stripes[i].Irp) {\n                    ERR(\"IoAllocateIrp failed\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto end3;\n                }\n\n                context.stripes[i].Irp->MdlAddress = NULL;\n\n                IrpSp = IoGetNextIrpStackLocation(context.stripes[i].Irp);\n                IrpSp->MajorFunction = IRP_MJ_READ;\n                IrpSp->FileObject = c->devices[i]->fileobj;\n\n                if (c->devices[i]->devobj->Flags & DO_BUFFERED_IO) {\n                    context.stripes[i].Irp->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPool, (ULONG)(read_stripes * c->chunk_item->stripe_length), ALLOC_TAG);\n                    if (!context.stripes[i].Irp->AssociatedIrp.SystemBuffer) {\n                        ERR(\"out of memory\\n\");\n                        Status = STATUS_INSUFFICIENT_RESOURCES;\n                        goto end3;\n                    }\n\n                    context.stripes[i].Irp->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION;\n\n                    context.stripes[i].Irp->UserBuffer = context.stripes[i].buf;\n                } else if (c->devices[i]->devobj->Flags & DO_DIRECT_IO) {\n                    context.stripes[i].Irp->MdlAddress = IoAllocateMdl(context.stripes[i].buf, (ULONG)(read_stripes * c->chunk_item->stripe_length), false, false, NULL);\n                    if (!context.stripes[i].Irp->MdlAddress) {\n                        ERR(\"IoAllocateMdl failed\\n\");\n                        Status = STATUS_INSUFFICIENT_RESOURCES;\n                        goto end3;\n                    }\n\n                    Status = STATUS_SUCCESS;\n\n                    try {\n                        MmProbeAndLockPages(context.stripes[i].Irp->MdlAddress, KernelMode, IoWriteAccess);\n                    } except (EXCEPTION_EXECUTE_HANDLER) {\n                        Status = GetExceptionCode();\n                    }\n\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n                        IoFreeMdl(context.stripes[i].Irp->MdlAddress);\n                        goto end3;\n                    }\n                } else\n                    context.stripes[i].Irp->UserBuffer = context.stripes[i].buf;\n\n                context.stripes[i].offset = stripe * c->chunk_item->stripe_length;\n\n                IrpSp->Parameters.Read.Length = (ULONG)(read_stripes * c->chunk_item->stripe_length);\n                IrpSp->Parameters.Read.ByteOffset.QuadPart = cis[i].offset + context.stripes[i].offset;\n\n                context.stripes[i].Irp->UserIosb = &context.stripes[i].iosb;\n                context.stripes[i].missing = false;\n\n                IoSetCompletionRoutine(context.stripes[i].Irp, scrub_read_completion_raid56, &context.stripes[i], true, true, true);\n\n                Vcb->scrub.data_scrubbed += read_stripes * c->chunk_item->stripe_length;\n                need_wait = true;\n            } else {\n                context.stripes[i].Irp = NULL;\n                context.stripes[i].missing = true;\n                missing_devices++;\n                InterlockedDecrement(&context.stripes_left);\n            }\n        }\n\n        if (c->chunk_item->type & BLOCK_FLAG_RAID5 && missing_devices > 1) {\n            ERR(\"too many missing devices (%u, maximum 1)\\n\", missing_devices);\n            Status = STATUS_UNEXPECTED_IO_ERROR;\n            goto end3;\n        } else if (c->chunk_item->type & BLOCK_FLAG_RAID6 && missing_devices > 2) {\n            ERR(\"too many missing devices (%u, maximum 2)\\n\", missing_devices);\n            Status = STATUS_UNEXPECTED_IO_ERROR;\n            goto end3;\n        }\n\n        if (need_wait) {\n            KeInitializeEvent(&context.Event, NotificationEvent, false);\n\n            for (i = 0; i < c->chunk_item->num_stripes; i++) {\n                if (c->devices[i]->devobj)\n                    IoCallDriver(c->devices[i]->devobj, context.stripes[i].Irp);\n            }\n\n            KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n        }\n\n        // return an error if any of the stripes returned an error\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            if (!context.stripes[i].missing && !NT_SUCCESS(context.stripes[i].iosb.Status)) {\n                Status = context.stripes[i].iosb.Status;\n                log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_READ_ERRORS);\n                goto end3;\n            }\n        }\n\n        if (c->chunk_item->type & BLOCK_FLAG_RAID6) {\n            for (i = 0; i < read_stripes; i++) {\n                scrub_raid6_stripe(Vcb, c, &context, stripe_start, stripe, i, missing_devices);\n            }\n        } else {\n            for (i = 0; i < read_stripes; i++) {\n                scrub_raid5_stripe(Vcb, c, &context, stripe_start, stripe, i, missing_devices);\n            }\n        }\n        stripe += read_stripes;\n\nend3:\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            if (context.stripes[i].Irp) {\n                if (c->devices[i]->devobj->Flags & DO_DIRECT_IO && context.stripes[i].Irp->MdlAddress) {\n                    MmUnlockPages(context.stripes[i].Irp->MdlAddress);\n                    IoFreeMdl(context.stripes[i].Irp->MdlAddress);\n                }\n                IoFreeIrp(context.stripes[i].Irp);\n                context.stripes[i].Irp = NULL;\n\n                if (context.stripes[i].rewrite) {\n                    Status = write_data_phys(c->devices[i]->devobj, c->devices[i]->fileobj, cis[i].offset + context.stripes[i].offset,\n                                             context.stripes[i].buf, (uint32_t)(read_stripes * c->chunk_item->stripe_length));\n\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"write_data_phys returned %08lx\\n\", Status);\n                        log_device_error(Vcb, c->devices[i], BTRFS_DEV_STAT_WRITE_ERRORS);\n                        goto end2;\n                    }\n                }\n            }\n        }\n\n        if (!NT_SUCCESS(Status))\n            break;\n    } while (stripe < stripe_end);\n\nend2:\n    chunk_unlock_range(Vcb, c, run_start, run_end - run_start);\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        ExFreePool(context.stripes[i].buf);\n        ExFreePool(context.stripes[i].errorarr);\n    }\n    ExFreePool(context.stripes);\n\nend:\n    ExFreePool(treearr);\n    ExFreePool(allocarr);\n    ExFreePool(context.parity_scratch);\n\n    if (c->chunk_item->type & BLOCK_FLAG_RAID6)\n        ExFreePool(context.parity_scratch2);\n\n    if (c->chunk_item->type & BLOCK_FLAG_DATA) {\n        ExFreePool(csumarr);\n        ExFreePool(context.csum);\n    }\n\n    return Status;\n}\n\nstatic NTSTATUS scrub_chunk_raid56(device_extension* Vcb, chunk* c, uint64_t* offset, bool* changed) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    bool b;\n    uint64_t full_stripe_len, stripe, stripe_start = 0, stripe_end = 0, total_data = 0;\n    ULONG num_extents = 0, num_parity_stripes = c->chunk_item->type & BLOCK_FLAG_RAID6 ? 2 : 1;\n\n    full_stripe_len = (c->chunk_item->num_stripes - num_parity_stripes) * c->chunk_item->stripe_length;\n    stripe = (*offset - c->offset) / full_stripe_len;\n\n    *offset = c->offset + (stripe * full_stripe_len);\n\n    searchkey.obj_id = *offset;\n    searchkey.obj_type = TYPE_METADATA_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    *changed = false;\n\n    do {\n        traverse_ptr next_tp;\n\n        if (tp.item->key.obj_id >= c->offset + c->chunk_item->size)\n            break;\n\n        if (tp.item->key.obj_id >= *offset && (tp.item->key.obj_type == TYPE_EXTENT_ITEM || tp.item->key.obj_type == TYPE_METADATA_ITEM)) {\n            uint64_t size = tp.item->key.obj_type == TYPE_METADATA_ITEM ? Vcb->superblock.node_size : tp.item->key.offset;\n\n            TRACE(\"%I64x\\n\", tp.item->key.obj_id);\n\n            if (size < Vcb->superblock.sector_size) {\n                ERR(\"extent %I64x has size less than sector_size (%I64x < %x)\\n\", tp.item->key.obj_id, size, Vcb->superblock.sector_size);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            stripe = (tp.item->key.obj_id - c->offset) / full_stripe_len;\n\n            if (*changed) {\n                if (stripe > stripe_end + 1) {\n                    Status = scrub_chunk_raid56_stripe_run(Vcb, c, stripe_start, stripe_end);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"scrub_chunk_raid56_stripe_run returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    stripe_start = stripe;\n                }\n            } else\n                stripe_start = stripe;\n\n            stripe_end = (tp.item->key.obj_id + size - 1 - c->offset) / full_stripe_len;\n\n            *changed = true;\n\n            total_data += size;\n            num_extents++;\n\n            // only do so much at a time\n            if (num_extents >= 64 || total_data >= 0x8000000) // 128 MB\n                break;\n        }\n\n        b = find_next_item(Vcb, &tp, &next_tp, false, NULL);\n\n        if (b)\n            tp = next_tp;\n    } while (b);\n\n    if (*changed) {\n        Status = scrub_chunk_raid56_stripe_run(Vcb, c, stripe_start, stripe_end);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"scrub_chunk_raid56_stripe_run returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        *offset = c->offset + ((stripe_end + 1) * full_stripe_len);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS scrub_chunk(device_extension* Vcb, chunk* c, uint64_t* offset, bool* changed) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    bool b = false, tree_run = false;\n    ULONG type, num_extents = 0;\n    uint64_t total_data = 0, tree_run_start = 0, tree_run_end = 0;\n\n    TRACE(\"chunk %I64x\\n\", c->offset);\n\n    ExAcquireResourceSharedLite(&Vcb->tree_lock, true);\n\n    if (c->chunk_item->type & BLOCK_FLAG_DUPLICATE)\n        type = BLOCK_FLAG_DUPLICATE;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID0)\n        type = BLOCK_FLAG_RAID0;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID1)\n        type = BLOCK_FLAG_DUPLICATE;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID10)\n        type = BLOCK_FLAG_RAID10;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID5) {\n        Status = scrub_chunk_raid56(Vcb, c, offset, changed);\n        goto end;\n    } else if (c->chunk_item->type & BLOCK_FLAG_RAID6) {\n        Status = scrub_chunk_raid56(Vcb, c, offset, changed);\n        goto end;\n    } else if (c->chunk_item->type & BLOCK_FLAG_RAID1C3)\n        type = BLOCK_FLAG_DUPLICATE;\n    else if (c->chunk_item->type & BLOCK_FLAG_RAID1C4)\n        type = BLOCK_FLAG_DUPLICATE;\n    else // SINGLE\n        type = BLOCK_FLAG_DUPLICATE;\n\n    searchkey.obj_id = *offset;\n    searchkey.obj_type = TYPE_METADATA_ITEM;\n    searchkey.offset = 0xffffffffffffffff;\n\n    Status = find_item(Vcb, Vcb->extent_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error - find_item returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    do {\n        traverse_ptr next_tp;\n\n        if (tp.item->key.obj_id >= c->offset + c->chunk_item->size)\n            break;\n\n        if (tp.item->key.obj_id >= *offset && (tp.item->key.obj_type == TYPE_EXTENT_ITEM || tp.item->key.obj_type == TYPE_METADATA_ITEM)) {\n            uint64_t size = tp.item->key.obj_type == TYPE_METADATA_ITEM ? Vcb->superblock.node_size : tp.item->key.offset;\n            bool is_tree;\n            void* csum = NULL;\n            RTL_BITMAP bmp;\n            ULONG* bmparr = NULL, bmplen;\n\n            TRACE(\"%I64x\\n\", tp.item->key.obj_id);\n\n            is_tree = false;\n\n            if (tp.item->key.obj_type == TYPE_METADATA_ITEM)\n                is_tree = true;\n            else {\n                EXTENT_ITEM* ei = (EXTENT_ITEM*)tp.item->data;\n\n                if (tp.item->size < sizeof(EXTENT_ITEM)) {\n                    ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n                    Status = STATUS_INTERNAL_ERROR;\n                    goto end;\n                }\n\n                if (ei->flags & EXTENT_ITEM_TREE_BLOCK)\n                    is_tree = true;\n            }\n\n            if (size < Vcb->superblock.sector_size) {\n                ERR(\"extent %I64x has size less than sector_size (%I64x < %x)\\n\", tp.item->key.obj_id, size, Vcb->superblock.sector_size);\n                Status = STATUS_INTERNAL_ERROR;\n                goto end;\n            }\n\n            // load csum\n            if (!is_tree) {\n                traverse_ptr tp2;\n\n                csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((Vcb->csum_size * size) >> Vcb->sector_shift), ALLOC_TAG);\n                if (!csum) {\n                    ERR(\"out of memory\\n\");\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto end;\n                }\n\n                bmplen = (ULONG)(size >> Vcb->sector_shift);\n\n                bmparr = ExAllocatePoolWithTag(PagedPool, (ULONG)(sector_align((bmplen >> 3) + 1, sizeof(ULONG))), ALLOC_TAG);\n                if (!bmparr) {\n                    ERR(\"out of memory\\n\");\n                    ExFreePool(csum);\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto end;\n                }\n\n                RtlInitializeBitMap(&bmp, bmparr, bmplen);\n                RtlSetAllBits(&bmp); // 1 = no csum, 0 = csum\n\n                searchkey.obj_id = EXTENT_CSUM_ID;\n                searchkey.obj_type = TYPE_EXTENT_CSUM;\n                searchkey.offset = tp.item->key.obj_id;\n\n                Status = find_item(Vcb, Vcb->checksum_root, &tp2, &searchkey, false, NULL);\n                if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) {\n                    ERR(\"find_item returned %08lx\\n\", Status);\n                    ExFreePool(csum);\n                    ExFreePool(bmparr);\n                    goto end;\n                }\n\n                if (Status != STATUS_NOT_FOUND) {\n                    do {\n                        traverse_ptr next_tp2;\n\n                        if (tp2.item->key.obj_type == TYPE_EXTENT_CSUM) {\n                            if (tp2.item->key.offset >= tp.item->key.obj_id + size)\n                                break;\n                            else if (tp2.item->size >= Vcb->csum_size && tp2.item->key.offset + (((uint64_t)tp2.item->size << Vcb->sector_shift) / Vcb->csum_size) >= tp.item->key.obj_id) {\n                                uint64_t cs = max(tp.item->key.obj_id, tp2.item->key.offset);\n                                uint64_t ce = min(tp.item->key.obj_id + size, tp2.item->key.offset + (((uint64_t)tp2.item->size << Vcb->sector_shift) / Vcb->csum_size));\n\n                                RtlCopyMemory((uint8_t*)csum + (((cs - tp.item->key.obj_id) * Vcb->csum_size) >> Vcb->sector_shift),\n                                              tp2.item->data + (((cs - tp2.item->key.offset) * Vcb->csum_size) >> Vcb->sector_shift),\n                                              (ULONG)(((ce - cs) * Vcb->csum_size) >> Vcb->sector_shift));\n\n                                RtlClearBits(&bmp, (ULONG)((cs - tp.item->key.obj_id) >> Vcb->sector_shift), (ULONG)((ce - cs) >> Vcb->sector_shift));\n\n                                if (ce == tp.item->key.obj_id + size)\n                                    break;\n                            }\n                        }\n\n                        if (find_next_item(Vcb, &tp2, &next_tp2, false, NULL))\n                            tp2 = next_tp2;\n                        else\n                            break;\n                    } while (true);\n                }\n            }\n\n            if (tree_run) {\n                if (!is_tree || tp.item->key.obj_id > tree_run_end) {\n                    Status = scrub_extent(Vcb, c, type, tree_run_start, (uint32_t)(tree_run_end - tree_run_start), NULL);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"scrub_extent returned %08lx\\n\", Status);\n                        goto end;\n                    }\n\n                    if (!is_tree)\n                        tree_run = false;\n                    else {\n                        tree_run_start = tp.item->key.obj_id;\n                        tree_run_end = tp.item->key.obj_id + Vcb->superblock.node_size;\n                    }\n                } else\n                    tree_run_end = tp.item->key.obj_id + Vcb->superblock.node_size;\n            } else if (is_tree) {\n                tree_run = true;\n                tree_run_start = tp.item->key.obj_id;\n                tree_run_end = tp.item->key.obj_id + Vcb->superblock.node_size;\n            }\n\n            if (!is_tree) {\n                Status = scrub_data_extent(Vcb, c, tp.item->key.obj_id, type, csum, &bmp, bmplen);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"scrub_data_extent returned %08lx\\n\", Status);\n                    ExFreePool(csum);\n                    ExFreePool(bmparr);\n                    goto end;\n                }\n\n                ExFreePool(csum);\n                ExFreePool(bmparr);\n            }\n\n            *offset = tp.item->key.obj_id + size;\n            *changed = true;\n\n            total_data += size;\n            num_extents++;\n\n            // only do so much at a time\n            if (num_extents >= 64 || total_data >= 0x8000000) // 128 MB\n                break;\n        }\n\n        b = find_next_item(Vcb, &tp, &next_tp, false, NULL);\n\n        if (b)\n            tp = next_tp;\n    } while (b);\n\n    if (tree_run) {\n        Status = scrub_extent(Vcb, c, type, tree_run_start, (uint32_t)(tree_run_end - tree_run_start), NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"scrub_extent returned %08lx\\n\", Status);\n            goto end;\n        }\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    return Status;\n}\n\n_Function_class_(KSTART_ROUTINE)\nstatic void __stdcall scrub_thread(void* context) {\n    device_extension* Vcb = context;\n    LIST_ENTRY chunks, *le;\n    NTSTATUS Status;\n    LARGE_INTEGER time;\n\n    KeInitializeEvent(&Vcb->scrub.finished, NotificationEvent, false);\n\n    InitializeListHead(&chunks);\n\n    ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n    if (Vcb->need_write && !Vcb->readonly)\n        Status = do_write(Vcb, NULL);\n    else\n        Status = STATUS_SUCCESS;\n\n    free_trees(Vcb);\n\n    if (!NT_SUCCESS(Status)) {\n        ExReleaseResourceLite(&Vcb->tree_lock);\n        ERR(\"do_write returned %08lx\\n\", Status);\n        Vcb->scrub.error = Status;\n        goto end;\n    }\n\n    ExConvertExclusiveToSharedLite(&Vcb->tree_lock);\n\n    ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, true);\n\n    KeQuerySystemTime(&Vcb->scrub.start_time);\n    Vcb->scrub.finish_time.QuadPart = 0;\n    Vcb->scrub.resume_time.QuadPart = Vcb->scrub.start_time.QuadPart;\n    Vcb->scrub.duration.QuadPart = 0;\n    Vcb->scrub.total_chunks = 0;\n    Vcb->scrub.chunks_left = 0;\n    Vcb->scrub.data_scrubbed = 0;\n    Vcb->scrub.num_errors = 0;\n\n    while (!IsListEmpty(&Vcb->scrub.errors)) {\n        scrub_error* err = CONTAINING_RECORD(RemoveHeadList(&Vcb->scrub.errors), scrub_error, list_entry);\n        ExFreePool(err);\n    }\n\n    ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        acquire_chunk_lock(c, Vcb);\n\n        if (!c->readonly) {\n            InsertTailList(&chunks, &c->list_entry_balance);\n            Vcb->scrub.total_chunks++;\n            Vcb->scrub.chunks_left++;\n        }\n\n        release_chunk_lock(c, Vcb);\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&Vcb->chunk_lock);\n\n    ExReleaseResource(&Vcb->scrub.stats_lock);\n\n    ExReleaseResourceLite(&Vcb->tree_lock);\n\n    while (!IsListEmpty(&chunks)) {\n        chunk* c = CONTAINING_RECORD(RemoveHeadList(&chunks), chunk, list_entry_balance);\n        uint64_t offset = c->offset;\n        bool changed;\n\n        c->reloc = true;\n\n        KeWaitForSingleObject(&Vcb->scrub.event, Executive, KernelMode, false, NULL);\n\n        if (!Vcb->scrub.stopping) {\n            do {\n                changed = false;\n\n                Status = scrub_chunk(Vcb, c, &offset, &changed);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"scrub_chunk returned %08lx\\n\", Status);\n                    Vcb->scrub.stopping = true;\n                    Vcb->scrub.error = Status;\n                    break;\n                }\n\n                if (offset == c->offset + c->chunk_item->size || Vcb->scrub.stopping)\n                    break;\n\n                KeWaitForSingleObject(&Vcb->scrub.event, Executive, KernelMode, false, NULL);\n            } while (changed);\n        }\n\n        ExAcquireResourceExclusiveLite(&Vcb->scrub.stats_lock, true);\n\n        if (!Vcb->scrub.stopping)\n            Vcb->scrub.chunks_left--;\n\n        if (IsListEmpty(&chunks))\n            KeQuerySystemTime(&Vcb->scrub.finish_time);\n\n        ExReleaseResource(&Vcb->scrub.stats_lock);\n\n        c->reloc = false;\n        c->list_entry_balance.Flink = NULL;\n    }\n\n    KeQuerySystemTime(&time);\n    Vcb->scrub.duration.QuadPart += time.QuadPart - Vcb->scrub.resume_time.QuadPart;\n\nend:\n    ZwClose(Vcb->scrub.thread);\n    Vcb->scrub.thread = NULL;\n\n    KeSetEvent(&Vcb->scrub.finished, 0, false);\n}\n\nNTSTATUS start_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode) {\n    NTSTATUS Status;\n    OBJECT_ATTRIBUTES oa;\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (Vcb->locked) {\n        WARN(\"cannot start scrub while locked\\n\");\n        return STATUS_DEVICE_NOT_READY;\n    }\n\n    if (Vcb->balance.thread) {\n        WARN(\"cannot start scrub while balance running\\n\");\n        return STATUS_DEVICE_NOT_READY;\n    }\n\n    if (Vcb->scrub.thread) {\n        WARN(\"scrub already running\\n\");\n        return STATUS_DEVICE_NOT_READY;\n    }\n\n    if (Vcb->readonly)\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n    Vcb->scrub.stopping = false;\n    Vcb->scrub.paused = false;\n    Vcb->scrub.error = STATUS_SUCCESS;\n    KeInitializeEvent(&Vcb->scrub.event, NotificationEvent, !Vcb->scrub.paused);\n\n    InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = PsCreateSystemThread(&Vcb->scrub.thread, 0, &oa, NULL, NULL, scrub_thread, Vcb);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"PsCreateSystemThread returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS query_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode, void* data, ULONG length) {\n    btrfs_query_scrub* bqs = (btrfs_query_scrub*)data;\n    ULONG len;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    btrfs_scrub_error* bse = NULL;\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (length < offsetof(btrfs_query_scrub, errors))\n        return STATUS_BUFFER_TOO_SMALL;\n\n    ExAcquireResourceSharedLite(&Vcb->scrub.stats_lock, true);\n\n    if (Vcb->scrub.thread && Vcb->scrub.chunks_left > 0)\n        bqs->status = Vcb->scrub.paused ? BTRFS_SCRUB_PAUSED : BTRFS_SCRUB_RUNNING;\n    else\n        bqs->status = BTRFS_SCRUB_STOPPED;\n\n    bqs->start_time.QuadPart = Vcb->scrub.start_time.QuadPart;\n    bqs->finish_time.QuadPart = Vcb->scrub.finish_time.QuadPart;\n    bqs->chunks_left = Vcb->scrub.chunks_left;\n    bqs->total_chunks = Vcb->scrub.total_chunks;\n    bqs->data_scrubbed = Vcb->scrub.data_scrubbed;\n\n    bqs->duration = Vcb->scrub.duration.QuadPart;\n\n    if (bqs->status == BTRFS_SCRUB_RUNNING) {\n        LARGE_INTEGER time;\n\n        KeQuerySystemTime(&time);\n        bqs->duration += time.QuadPart - Vcb->scrub.resume_time.QuadPart;\n    }\n\n    bqs->error = Vcb->scrub.error;\n\n    bqs->num_errors = Vcb->scrub.num_errors;\n\n    len = length - offsetof(btrfs_query_scrub, errors);\n\n    le = Vcb->scrub.errors.Flink;\n    while (le != &Vcb->scrub.errors) {\n        scrub_error* err = CONTAINING_RECORD(le, scrub_error, list_entry);\n        ULONG errlen;\n\n        if (err->is_metadata)\n            errlen = offsetof(btrfs_scrub_error, metadata.firstitem) + sizeof(KEY);\n        else\n            errlen = offsetof(btrfs_scrub_error, data.filename) + err->data.filename_length;\n\n        if (len < errlen) {\n            Status = STATUS_BUFFER_OVERFLOW;\n            goto end;\n        }\n\n        if (!bse)\n            bse = &bqs->errors;\n        else {\n            ULONG lastlen;\n\n            if (bse->is_metadata)\n                lastlen = offsetof(btrfs_scrub_error, metadata.firstitem) + sizeof(KEY);\n            else\n                lastlen = offsetof(btrfs_scrub_error, data.filename) + bse->data.filename_length;\n\n            bse->next_entry = lastlen;\n            bse = (btrfs_scrub_error*)(((uint8_t*)bse) + lastlen);\n        }\n\n        bse->next_entry = 0;\n        bse->address = err->address;\n        bse->device = err->device;\n        bse->recovered = err->recovered;\n        bse->is_metadata = err->is_metadata;\n        bse->parity = err->parity;\n\n        if (err->is_metadata) {\n            bse->metadata.root = err->metadata.root;\n            bse->metadata.level = err->metadata.level;\n            bse->metadata.firstitem = err->metadata.firstitem;\n        } else {\n            bse->data.subvol = err->data.subvol;\n            bse->data.offset = err->data.offset;\n            bse->data.filename_length = err->data.filename_length;\n            RtlCopyMemory(bse->data.filename, err->data.filename, err->data.filename_length);\n        }\n\n        len -= errlen;\n        le = le->Flink;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&Vcb->scrub.stats_lock);\n\n    return Status;\n}\n\nNTSTATUS pause_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode) {\n    LARGE_INTEGER time;\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (!Vcb->scrub.thread)\n        return STATUS_DEVICE_NOT_READY;\n\n    if (Vcb->scrub.paused)\n        return STATUS_DEVICE_NOT_READY;\n\n    Vcb->scrub.paused = true;\n    KeClearEvent(&Vcb->scrub.event);\n\n    KeQuerySystemTime(&time);\n    Vcb->scrub.duration.QuadPart += time.QuadPart - Vcb->scrub.resume_time.QuadPart;\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS resume_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode) {\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (!Vcb->scrub.thread)\n        return STATUS_DEVICE_NOT_READY;\n\n    if (!Vcb->scrub.paused)\n        return STATUS_DEVICE_NOT_READY;\n\n    Vcb->scrub.paused = false;\n    KeSetEvent(&Vcb->scrub.event, 0, false);\n\n    KeQuerySystemTime(&Vcb->scrub.resume_time);\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS stop_scrub(device_extension* Vcb, KPROCESSOR_MODE processor_mode) {\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (!Vcb->scrub.thread)\n        return STATUS_DEVICE_NOT_READY;\n\n    Vcb->scrub.paused = false;\n    Vcb->scrub.stopping = true;\n    KeSetEvent(&Vcb->scrub.event, 0, false);\n\n    return STATUS_SUCCESS;\n}\n"
  },
  {
    "path": "src/search.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n\n#include <ntddk.h>\n#include <ntifs.h>\n#include <mountmgr.h>\n#include <windef.h>\n#include <ntddstor.h>\n#include <ntdddisk.h>\n#include <ntddvol.h>\n\n#include <initguid.h>\n#include <wdmguid.h>\n#include <ioevent.h>\n\nextern ERESOURCE pdo_list_lock;\nextern LIST_ENTRY pdo_list;\nextern UNICODE_STRING registry_path;\nextern KEVENT mountmgr_thread_event;\nextern HANDLE mountmgr_thread_handle;\nextern bool shutting_down;\nextern PDEVICE_OBJECT busobj;\nextern tIoUnregisterPlugPlayNotificationEx fIoUnregisterPlugPlayNotificationEx;\nextern ERESOURCE boot_lock;\nextern PDRIVER_OBJECT drvobj;\n\ntypedef void (*pnp_callback)(PUNICODE_STRING devpath);\n\n// not in mingw yet\n#ifndef _MSC_VER\nDEFINE_GUID(GUID_IO_VOLUME_FVE_STATUS_CHANGE, 0x062998b2, 0xee1f, 0x4b6a, 0xb8, 0x57, 0xe7, 0x6c, 0xbb, 0xe9, 0xa6, 0xda);\n#endif\n\nextern PDEVICE_OBJECT master_devobj;\n\ntypedef struct {\n    LIST_ENTRY list_entry;\n    PDEVICE_OBJECT devobj;\n    void* notification_entry;\n    UNICODE_STRING devpath;\n    WCHAR buf[1];\n} fve_data;\n\nstatic LIST_ENTRY fve_data_list = { &fve_data_list, &fve_data_list };\nKSPIN_LOCK fve_data_lock;\n\nstatic bool fs_ignored(BTRFS_UUID* uuid) {\n    UNICODE_STRING path, ignoreus;\n    NTSTATUS Status;\n    OBJECT_ATTRIBUTES oa;\n    KEY_VALUE_FULL_INFORMATION* kvfi;\n    ULONG dispos, retlen, kvfilen, i, j;\n    HANDLE h;\n    bool ret = false;\n\n    path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR));\n\n    path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG);\n    if (!path.Buffer) {\n        ERR(\"out of memory\\n\");\n        return false;\n    }\n\n    RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length);\n\n    i = registry_path.Length / sizeof(WCHAR);\n\n    path.Buffer[i] = '\\\\';\n    i++;\n\n    for (j = 0; j < 16; j++) {\n        path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4);\n        path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF);\n\n        i += 2;\n\n        if (j == 3 || j == 5 || j == 7 || j == 9) {\n            path.Buffer[i] = '-';\n            i++;\n        }\n    }\n\n    InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = ZwCreateKey(&h, KEY_QUERY_VALUE, &oa, 0, NULL, REG_OPTION_NON_VOLATILE, &dispos);\n\n    if (!NT_SUCCESS(Status)) {\n        TRACE(\"ZwCreateKey returned %08lx\\n\", Status);\n        ExFreePool(path.Buffer);\n        return false;\n    }\n\n    RtlInitUnicodeString(&ignoreus, L\"Ignore\");\n\n    kvfilen = (ULONG)offsetof(KEY_VALUE_FULL_INFORMATION, Name[0]) + (255 * sizeof(WCHAR));\n    kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);\n    if (!kvfi) {\n        ERR(\"out of memory\\n\");\n        ZwClose(h);\n        ExFreePool(path.Buffer);\n        return false;\n    }\n\n    Status = ZwQueryValueKey(h, &ignoreus, KeyValueFullInformation, kvfi, kvfilen, &retlen);\n    if (NT_SUCCESS(Status)) {\n        if (kvfi->Type == REG_DWORD && kvfi->DataLength >= sizeof(uint32_t)) {\n            uint32_t* pr = (uint32_t*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n            ret = *pr;\n        }\n    }\n\n    ZwClose(h);\n    ExFreePool(kvfi);\n    ExFreePool(path.Buffer);\n\n    return ret;\n}\n\ntypedef struct {\n    PIO_WORKITEM work_item;\n    PFILE_OBJECT fileobj;\n    PDEVICE_OBJECT devobj;\n    UNICODE_STRING devpath;\n    WCHAR buf[1];\n} fve_callback_context;\n\n_Function_class_(IO_WORKITEM_ROUTINE)\nstatic void __stdcall fve_callback(PDEVICE_OBJECT DeviceObject, PVOID con) {\n    fve_callback_context* ctx = con;\n\n    UNUSED(DeviceObject);\n\n    if (volume_arrival(&ctx->devpath, true)) {\n        KIRQL irql;\n        LIST_ENTRY* le;\n        fve_data* d = NULL;\n\n        // volume no longer locked - unregister notification\n\n        KeAcquireSpinLock(&fve_data_lock, &irql);\n\n        le = fve_data_list.Flink;\n        while (le != &fve_data_list) {\n            fve_data* d2 = CONTAINING_RECORD(le, fve_data, list_entry);\n\n            if (d2->devobj == ctx->devobj) {\n                RemoveEntryList(&d2->list_entry);\n                d = d2;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        KeReleaseSpinLock(&fve_data_lock, irql);\n\n        if (d) {\n            IoUnregisterPlugPlayNotification(d->notification_entry);\n            ExFreePool(d);\n        }\n    }\n\n    IoFreeWorkItem(ctx->work_item);\n    ExFreePool(ctx);\n}\n\nstatic NTSTATUS __stdcall event_notification(PVOID NotificationStructure, PVOID Context) {\n    TARGET_DEVICE_REMOVAL_NOTIFICATION* tdrn = NotificationStructure;\n    PDEVICE_OBJECT devobj = Context;\n    PIO_WORKITEM work_item;\n    fve_callback_context* ctx;\n    LIST_ENTRY* le;\n    KIRQL irql;\n\n    if (RtlCompareMemory(&tdrn->Event, &GUID_IO_VOLUME_FVE_STATUS_CHANGE, sizeof(GUID)) != sizeof(GUID))\n        return STATUS_SUCCESS;\n\n    /* The FVE event has trailing data, presumably telling us whether the volume has\n     * been unlocked or whatever, but unfortunately it's undocumented! */\n\n    work_item = IoAllocateWorkItem(master_devobj);\n    if (!work_item) {\n        ERR(\"out of memory\\n\");\n        return STATUS_SUCCESS;\n    }\n\n    KeAcquireSpinLock(&fve_data_lock, &irql);\n\n    le = fve_data_list.Flink;\n    while (le != &fve_data_list) {\n        fve_data* d = CONTAINING_RECORD(le, fve_data, list_entry);\n\n        if (d->devobj == devobj) {\n            ctx = ExAllocatePoolWithTag(NonPagedPool, offsetof(fve_callback_context, buf) + d->devpath.Length,\n                                        ALLOC_TAG);\n\n            if (!ctx) {\n                KeReleaseSpinLock(&fve_data_lock, irql);\n                ERR(\"out of memory\\n\");\n                IoFreeWorkItem(work_item);\n                return STATUS_SUCCESS;\n            }\n\n            RtlCopyMemory(ctx->buf, d->devpath.Buffer, d->devpath.Length);\n            ctx->devpath.Length = ctx->devpath.MaximumLength = d->devpath.Length;\n\n            KeReleaseSpinLock(&fve_data_lock, irql);\n\n            ctx->devpath.Buffer = ctx->buf;\n\n            ctx->fileobj = tdrn->FileObject;\n            ctx->devobj = devobj;\n            ctx->work_item = work_item;\n\n            IoQueueWorkItem(work_item, fve_callback, DelayedWorkQueue, ctx);\n\n            return STATUS_SUCCESS;\n        }\n\n        le = le->Flink;\n    }\n\n    KeReleaseSpinLock(&fve_data_lock, irql);\n\n    IoFreeWorkItem(work_item);\n\n    return STATUS_SUCCESS;\n}\n\nstatic void register_fve_callback(PDEVICE_OBJECT devobj, PFILE_OBJECT fileobj,\n                                  PUNICODE_STRING devpath) {\n    NTSTATUS Status;\n    KIRQL irql;\n    LIST_ENTRY* le;\n\n    fve_data* d = ExAllocatePoolWithTag(NonPagedPool, offsetof(fve_data, buf) + devpath->Length, ALLOC_TAG);\n    if (!d) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    d->devpath.Buffer = d->buf;\n    d->devpath.Length = d->devpath.MaximumLength = devpath->Length;\n    RtlCopyMemory(d->devpath.Buffer, devpath->Buffer, devpath->Length);\n\n    KeAcquireSpinLock(&fve_data_lock, &irql);\n\n    le = fve_data_list.Flink;\n    while (le != &fve_data_list) {\n        fve_data* d2 = CONTAINING_RECORD(le, fve_data, list_entry);\n\n        if (d2->devobj == devobj) {\n            KeReleaseSpinLock(&fve_data_lock, irql);\n            ExFreePool(d);\n            return;\n        }\n\n        le = le->Flink;\n    }\n\n    KeReleaseSpinLock(&fve_data_lock, irql);\n\n    Status = IoRegisterPlugPlayNotification(EventCategoryTargetDeviceChange, 0, fileobj, drvobj, event_notification,\n                                            devobj, &d->notification_entry);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoRegisterPlugPlayNotification returned %08lx\\n\", Status);\n        return;\n    }\n\n    KeAcquireSpinLock(&fve_data_lock, &irql);\n\n    le = fve_data_list.Flink;\n    while (le != &fve_data_list) {\n        fve_data* d2 = CONTAINING_RECORD(le, fve_data, list_entry);\n\n        if (d2->devobj == devobj) {\n            KeReleaseSpinLock(&fve_data_lock, irql);\n            IoUnregisterPlugPlayNotification(d->notification_entry);\n            ExFreePool(d);\n            return;\n        }\n\n        le = le->Flink;\n    }\n\n    d->devobj = devobj;\n    InsertTailList(&fve_data_list, &d->list_entry);\n\n    KeReleaseSpinLock(&fve_data_lock, irql);\n}\n\nstatic bool test_vol(PDEVICE_OBJECT DeviceObject, PFILE_OBJECT FileObject,\n                     PUNICODE_STRING devpath, DWORD disk_num, DWORD part_num, uint64_t length,\n                     bool fve_callback) {\n    NTSTATUS Status;\n    ULONG toread;\n    uint8_t* data = NULL;\n    uint32_t sector_size;\n    bool ret = true;\n\n    TRACE(\"%.*S\\n\", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer);\n\n    sector_size = DeviceObject->SectorSize;\n\n    if (sector_size == 0) {\n        DISK_GEOMETRY geometry;\n        IO_STATUS_BLOCK iosb;\n\n        Status = dev_ioctl(DeviceObject, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0,\n                           &geometry, sizeof(DISK_GEOMETRY), true, &iosb);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"%.*S had a sector size of 0, and IOCTL_DISK_GET_DRIVE_GEOMETRY returned %08lx\\n\",\n                (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer, Status);\n            goto deref;\n        }\n\n        if (iosb.Information < sizeof(DISK_GEOMETRY)) {\n            ERR(\"%.*S: IOCTL_DISK_GET_DRIVE_GEOMETRY returned %Iu bytes, expected %Iu\\n\",\n                (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer, iosb.Information, sizeof(DISK_GEOMETRY));\n        }\n\n        sector_size = geometry.BytesPerSector;\n\n        if (sector_size == 0) {\n            ERR(\"%.*S had a sector size of 0\\n\", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer);\n            goto deref;\n        }\n    }\n\n    toread = (ULONG)sector_align(sizeof(superblock), sector_size);\n    data = ExAllocatePoolWithTag(NonPagedPool, toread, ALLOC_TAG);\n    if (!data) {\n        ERR(\"out of memory\\n\");\n        goto deref;\n    }\n\n    Status = sync_read_phys(DeviceObject, FileObject, superblock_addrs[0], toread, data, true);\n\n    if (NT_SUCCESS(Status) && ((superblock*)data)->magic == BTRFS_MAGIC) {\n        superblock* sb = (superblock*)data;\n\n        if (check_superblock_checksum(sb)) {\n            TRACE(\"volume found\\n\");\n\n            if (length >= superblock_addrs[1] + toread) {\n                ULONG i = 1;\n\n                superblock* sb2 = ExAllocatePoolWithTag(NonPagedPool, toread, ALLOC_TAG);\n                if (!sb2) {\n                    ERR(\"out of memory\\n\");\n                    goto deref;\n                }\n\n                while (superblock_addrs[i] > 0 && length >= superblock_addrs[i] + toread) {\n                    Status = sync_read_phys(DeviceObject, FileObject, superblock_addrs[i], toread, (PUCHAR)sb2, true);\n\n                    if (NT_SUCCESS(Status) && sb2->magic == BTRFS_MAGIC) {\n                        if (check_superblock_checksum(sb2) && sb2->generation > sb->generation)\n                            RtlCopyMemory(sb, sb2, toread);\n                    }\n\n                    i++;\n                }\n\n                ExFreePool(sb2);\n            }\n\n            if (!fs_ignored(&sb->uuid)) {\n                DeviceObject->Flags &= ~DO_VERIFY_VOLUME;\n                add_volume_device(sb, devpath, length, disk_num, part_num);\n            }\n        }\n    } else if (Status == STATUS_FVE_LOCKED_VOLUME) {\n        if (fve_callback)\n            ret = false;\n        else\n            register_fve_callback(DeviceObject, FileObject, devpath);\n    }\n\nderef:\n    if (data)\n        ExFreePool(data);\n\n    return ret;\n}\n\nNTSTATUS remove_drive_letter(PDEVICE_OBJECT mountmgr, PUNICODE_STRING devpath) {\n    NTSTATUS Status;\n    MOUNTMGR_MOUNT_POINT* mmp;\n    ULONG mmpsize;\n    MOUNTMGR_MOUNT_POINTS mmps1, *mmps2;\n\n    TRACE(\"removing drive letter\\n\");\n\n    mmpsize = sizeof(MOUNTMGR_MOUNT_POINT) + devpath->Length;\n\n    mmp = ExAllocatePoolWithTag(PagedPool, mmpsize, ALLOC_TAG);\n    if (!mmp) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(mmp, mmpsize);\n\n    mmp->DeviceNameOffset = sizeof(MOUNTMGR_MOUNT_POINT);\n    mmp->DeviceNameLength = devpath->Length;\n    RtlCopyMemory(&mmp[1], devpath->Buffer, devpath->Length);\n\n    Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_DELETE_POINTS, mmp, mmpsize, &mmps1, sizeof(MOUNTMGR_MOUNT_POINTS), false, NULL);\n\n    if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {\n        ERR(\"IOCTL_MOUNTMGR_DELETE_POINTS 1 returned %08lx\\n\", Status);\n        ExFreePool(mmp);\n        return Status;\n    }\n\n    if (Status != STATUS_BUFFER_OVERFLOW || mmps1.Size == 0) {\n        ExFreePool(mmp);\n        return STATUS_NOT_FOUND;\n    }\n\n    mmps2 = ExAllocatePoolWithTag(PagedPool, mmps1.Size, ALLOC_TAG);\n    if (!mmps2) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(mmp);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_DELETE_POINTS, mmp, mmpsize, mmps2, mmps1.Size, false, NULL);\n\n    if (!NT_SUCCESS(Status))\n        ERR(\"IOCTL_MOUNTMGR_DELETE_POINTS 2 returned %08lx\\n\", Status);\n\n    ExFreePool(mmps2);\n    ExFreePool(mmp);\n\n    return Status;\n}\n\nvoid disk_arrival(PUNICODE_STRING devpath) {\n    PFILE_OBJECT fileobj;\n    PDEVICE_OBJECT devobj;\n    NTSTATUS Status;\n    STORAGE_DEVICE_NUMBER sdn;\n    ULONG dlisize;\n    DRIVE_LAYOUT_INFORMATION_EX* dli = NULL;\n    IO_STATUS_BLOCK iosb;\n    GET_LENGTH_INFORMATION gli;\n\n    ExAcquireResourceSharedLite(&boot_lock, TRUE);\n\n    Status = IoGetDeviceObjectPointer(devpath, FILE_READ_ATTRIBUTES, &fileobj, &devobj);\n    if (!NT_SUCCESS(Status)) {\n        ExReleaseResourceLite(&boot_lock);\n        ERR(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n        return;\n    }\n\n    dlisize = 0;\n\n    do {\n        dlisize += 1024;\n\n        if (dli)\n            ExFreePool(dli);\n\n        dli = ExAllocatePoolWithTag(PagedPool, dlisize, ALLOC_TAG);\n        if (!dli) {\n            ERR(\"out of memory\\n\");\n            goto end;\n        }\n\n        Status = dev_ioctl(devobj, IOCTL_DISK_GET_DRIVE_LAYOUT_EX, NULL, 0,\n                           dli, dlisize, true, &iosb);\n    } while (Status == STATUS_BUFFER_TOO_SMALL);\n\n    // only consider disk as a potential filesystem if it has no partitions\n    if (NT_SUCCESS(Status) && dli->PartitionCount > 0) {\n        ExFreePool(dli);\n        goto end;\n    }\n\n    ExFreePool(dli);\n\n    Status = dev_ioctl(devobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0,\n                        &gli, sizeof(gli), true, NULL);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"error reading length information: %08lx\\n\", Status);\n        goto end;\n    }\n\n    Status = dev_ioctl(devobj, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0,\n                       &sdn, sizeof(STORAGE_DEVICE_NUMBER), true, NULL);\n    if (!NT_SUCCESS(Status)) {\n        TRACE(\"IOCTL_STORAGE_GET_DEVICE_NUMBER returned %08lx\\n\", Status);\n        sdn.DeviceNumber = 0xffffffff;\n        sdn.PartitionNumber = 0;\n    } else\n        TRACE(\"DeviceType = %lu, DeviceNumber = %lu, PartitionNumber = %lu\\n\", sdn.DeviceType, sdn.DeviceNumber, sdn.PartitionNumber);\n\n    test_vol(devobj, fileobj, devpath, sdn.DeviceNumber, sdn.PartitionNumber,\n             gli.Length.QuadPart, false);\n\nend:\n    ObDereferenceObject(fileobj);\n\n    ExReleaseResourceLite(&boot_lock);\n}\n\nvoid remove_volume_child(_Inout_ _Requires_exclusive_lock_held_(_Curr_->child_lock) _Releases_exclusive_lock_(_Curr_->child_lock) _In_ volume_device_extension* vde,\n                         _In_ volume_child* vc, _In_ bool skip_dev) {\n    NTSTATUS Status;\n    pdo_device_extension* pdode = vde->pdode;\n    device_extension* Vcb = vde->mounted_device ? vde->mounted_device->DeviceExtension : NULL;\n\n    if (vc->notification_entry) {\n        if (fIoUnregisterPlugPlayNotificationEx)\n            fIoUnregisterPlugPlayNotificationEx(vc->notification_entry);\n        else\n            IoUnregisterPlugPlayNotification(vc->notification_entry);\n    }\n\n    if (vde->mounted_device && (!Vcb || !Vcb->options.allow_degraded)) {\n        Status = pnp_surprise_removal(vde->mounted_device, NULL);\n        if (!NT_SUCCESS(Status))\n            ERR(\"pnp_surprise_removal returned %08lx\\n\", Status);\n    }\n\n    if (!Vcb || !Vcb->options.allow_degraded) {\n        Status = IoSetDeviceInterfaceState(&vde->bus_name, false);\n        if (!NT_SUCCESS(Status))\n            WARN(\"IoSetDeviceInterfaceState returned %08lx\\n\", Status);\n    }\n\n    if (pdode->children_loaded > 0) {\n        UNICODE_STRING mmdevpath;\n        PFILE_OBJECT FileObject;\n        PDEVICE_OBJECT mountmgr;\n        LIST_ENTRY* le;\n\n        if (!Vcb || !Vcb->options.allow_degraded) {\n            RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);\n            Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr);\n            if (!NT_SUCCESS(Status))\n                ERR(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n            else {\n                le = pdode->children.Flink;\n\n                while (le != &pdode->children) {\n                    volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry);\n\n                    if (vc2->had_drive_letter) { // re-add entry to mountmgr\n                        MOUNTDEV_NAME mdn;\n\n                        Status = dev_ioctl(vc2->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL);\n                        if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)\n                            ERR(\"IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\\n\", Status);\n                        else {\n                            MOUNTDEV_NAME* mdn2;\n                            ULONG mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength;\n\n                            mdn2 = ExAllocatePoolWithTag(PagedPool, mdnsize, ALLOC_TAG);\n                            if (!mdn2)\n                                ERR(\"out of memory\\n\");\n                            else {\n                                Status = dev_ioctl(vc2->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, true, NULL);\n                                if (!NT_SUCCESS(Status))\n                                    ERR(\"IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\\n\", Status);\n                                else {\n                                    UNICODE_STRING name;\n\n                                    name.Buffer = mdn2->Name;\n                                    name.Length = name.MaximumLength = mdn2->NameLength;\n\n                                    Status = mountmgr_add_drive_letter(mountmgr, &name);\n                                    if (!NT_SUCCESS(Status))\n                                        WARN(\"mountmgr_add_drive_letter returned %08lx\\n\", Status);\n                                }\n\n                                ExFreePool(mdn2);\n                            }\n                        }\n                    }\n\n                    le = le->Flink;\n                }\n\n                ObDereferenceObject(FileObject);\n            }\n        } else if (!skip_dev) {\n            ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n            le = Vcb->devices.Flink;\n            while (le != &Vcb->devices) {\n                device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n                if (dev->devobj == vc->devobj) {\n                    dev->devobj = NULL; // mark as missing\n                    break;\n                }\n\n                le = le->Flink;\n            }\n\n            ExReleaseResourceLite(&Vcb->tree_lock);\n        }\n\n        if (vde->device->Characteristics & FILE_REMOVABLE_MEDIA) {\n            vde->device->Characteristics &= ~FILE_REMOVABLE_MEDIA;\n\n            le = pdode->children.Flink;\n            while (le != &pdode->children) {\n                volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry);\n\n                if (vc2 != vc && vc2->devobj->Characteristics & FILE_REMOVABLE_MEDIA) {\n                    vde->device->Characteristics |= FILE_REMOVABLE_MEDIA;\n                    break;\n                }\n\n                le = le->Flink;\n            }\n        }\n    }\n\n    ObDereferenceObject(vc->fileobj);\n    ExFreePool(vc->pnp_name.Buffer);\n    RemoveEntryList(&vc->list_entry);\n    ExFreePool(vc);\n\n    pdode->children_loaded--;\n\n    if (pdode->children_loaded == 0) { // remove volume device\n        bool remove = false;\n\n        RemoveEntryList(&pdode->list_entry);\n\n        vde->removing = true;\n\n        Status = IoSetDeviceInterfaceState(&vde->bus_name, false);\n        if (!NT_SUCCESS(Status))\n            WARN(\"IoSetDeviceInterfaceState returned %08lx\\n\", Status);\n\n        if (vde->pdo->AttachedDevice)\n            IoDetachDevice(vde->pdo);\n\n        if (vde->open_count == 0)\n            remove = true;\n\n        ExReleaseResourceLite(&pdode->child_lock);\n\n        if (!no_pnp) {\n            bus_device_extension* bde = busobj->DeviceExtension;\n\n            IoInvalidateDeviceRelations(bde->buspdo, BusRelations);\n        }\n\n        if (remove) {\n            if (vde->name.Buffer)\n                ExFreePool(vde->name.Buffer);\n\n            if (Vcb)\n                Vcb->vde = NULL;\n\n            ExDeleteResourceLite(&pdode->child_lock);\n\n            IoDeleteDevice(vde->device);\n        }\n    } else\n        ExReleaseResourceLite(&pdode->child_lock);\n}\n\nbool volume_arrival(PUNICODE_STRING devpath, bool fve_callback) {\n    STORAGE_DEVICE_NUMBER sdn;\n    PFILE_OBJECT fileobj;\n    PDEVICE_OBJECT devobj;\n    GET_LENGTH_INFORMATION gli;\n    NTSTATUS Status;\n    bool ret = true;\n\n    TRACE(\"%.*S\\n\", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer);\n\n    ExAcquireResourceSharedLite(&boot_lock, TRUE);\n\n    Status = IoGetDeviceObjectPointer(devpath, FILE_READ_ATTRIBUTES, &fileobj, &devobj);\n    if (!NT_SUCCESS(Status)) {\n        ExReleaseResourceLite(&boot_lock);\n        ERR(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n        return false;\n    }\n\n    // make sure we're not processing devices we've created ourselves\n\n    if (devobj->DriverObject == drvobj)\n        goto end;\n\n    Status = dev_ioctl(devobj, IOCTL_VOLUME_ONLINE, NULL, 0, NULL, 0, true, NULL);\n    if (!NT_SUCCESS(Status))\n        TRACE(\"IOCTL_VOLUME_ONLINE returned %08lx\\n\", Status);\n\n    Status = dev_ioctl(devobj, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli), true, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IOCTL_DISK_GET_LENGTH_INFO returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    Status = dev_ioctl(devobj, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0,\n                       &sdn, sizeof(STORAGE_DEVICE_NUMBER), true, NULL);\n    if (!NT_SUCCESS(Status)) {\n        TRACE(\"IOCTL_STORAGE_GET_DEVICE_NUMBER returned %08lx\\n\", Status);\n        sdn.DeviceNumber = 0xffffffff;\n        sdn.PartitionNumber = 0;\n    } else\n        TRACE(\"DeviceType = %lu, DeviceNumber = %lu, PartitionNumber = %lu\\n\", sdn.DeviceType, sdn.DeviceNumber, sdn.PartitionNumber);\n\n    // If we've just added a partition to a whole-disk filesystem, unmount it\n    if (sdn.DeviceNumber != 0xffffffff && sdn.PartitionNumber != 0) {\n        LIST_ENTRY* le;\n\n        ExAcquireResourceExclusiveLite(&pdo_list_lock, true);\n\n        le = pdo_list.Flink;\n        while (le != &pdo_list) {\n            pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);\n            LIST_ENTRY* le2;\n            bool changed = false;\n\n            if (pdode->vde) {\n                ExAcquireResourceExclusiveLite(&pdode->child_lock, true);\n\n                le2 = pdode->children.Flink;\n                while (le2 != &pdode->children) {\n                    volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);\n                    LIST_ENTRY* le3 = le2->Flink;\n\n                    if (vc->disk_num == sdn.DeviceNumber && vc->part_num == 0) {\n                        TRACE(\"removing device\\n\");\n\n                        remove_volume_child(pdode->vde, vc, false);\n                        changed = true;\n\n                        break;\n                    }\n\n                    le2 = le3;\n                }\n\n                if (!changed)\n                    ExReleaseResourceLite(&pdode->child_lock);\n                else\n                    break;\n            }\n\n            le = le->Flink;\n        }\n\n        ExReleaseResourceLite(&pdo_list_lock);\n    }\n\n    ret = test_vol(devobj, fileobj, devpath, sdn.DeviceNumber, sdn.PartitionNumber,\n                   gli.Length.QuadPart, fve_callback);\n\nend:\n    ObDereferenceObject(fileobj);\n\n    ExReleaseResourceLite(&boot_lock);\n\n    return ret;\n}\n\nstatic void volume_arrival2(PUNICODE_STRING devpath) {\n    volume_arrival(devpath, false);\n}\n\nvoid volume_removal(PUNICODE_STRING devpath) {\n    LIST_ENTRY* le;\n    UNICODE_STRING devpath2;\n\n    TRACE(\"%.*S\\n\", (int)(devpath->Length / sizeof(WCHAR)), devpath->Buffer);\n\n    devpath2 = *devpath;\n\n    if (devpath->Length > 4 * sizeof(WCHAR) && devpath->Buffer[0] == '\\\\' && (devpath->Buffer[1] == '\\\\' || devpath->Buffer[1] == '?') &&\n        devpath->Buffer[2] == '?' && devpath->Buffer[3] == '\\\\') {\n        devpath2.Buffer = &devpath2.Buffer[3];\n        devpath2.Length -= 3 * sizeof(WCHAR);\n        devpath2.MaximumLength -= 3 * sizeof(WCHAR);\n    }\n\n    ExAcquireResourceExclusiveLite(&pdo_list_lock, true);\n\n    le = pdo_list.Flink;\n    while (le != &pdo_list) {\n        pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);\n        LIST_ENTRY* le2;\n        bool changed = false;\n\n        if (pdode->vde) {\n            ExAcquireResourceExclusiveLite(&pdode->child_lock, true);\n\n            le2 = pdode->children.Flink;\n            while (le2 != &pdode->children) {\n                volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);\n                LIST_ENTRY* le3 = le2->Flink;\n\n                if (vc->pnp_name.Length == devpath2.Length && RtlCompareMemory(vc->pnp_name.Buffer, devpath2.Buffer, devpath2.Length) == devpath2.Length) {\n                    TRACE(\"removing device\\n\");\n\n                    if (!vc->boot_volume) {\n                        remove_volume_child(pdode->vde, vc, false);\n                        changed = true;\n                    }\n\n                    break;\n                }\n\n                le2 = le3;\n            }\n\n            if (!changed)\n                ExReleaseResourceLite(&pdode->child_lock);\n            else\n                break;\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&pdo_list_lock);\n}\n\ntypedef struct {\n    UNICODE_STRING name;\n    pnp_callback func;\n    PIO_WORKITEM work_item;\n} pnp_callback_context;\n\n_Function_class_(IO_WORKITEM_ROUTINE)\nstatic void __stdcall do_pnp_callback(PDEVICE_OBJECT DeviceObject, PVOID con) {\n    pnp_callback_context* context = con;\n\n    UNUSED(DeviceObject);\n\n    context->func(&context->name);\n\n    if (context->name.Buffer)\n        ExFreePool(context->name.Buffer);\n\n    IoFreeWorkItem(context->work_item);\n\n    ExFreePool(context);\n}\n\nstatic void enqueue_pnp_callback(PUNICODE_STRING name, pnp_callback func) {\n    PIO_WORKITEM work_item;\n    pnp_callback_context* context;\n\n    work_item = IoAllocateWorkItem(master_devobj);\n    if (!work_item) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    context = ExAllocatePoolWithTag(PagedPool, sizeof(pnp_callback_context), ALLOC_TAG);\n\n    if (!context) {\n        ERR(\"out of memory\\n\");\n        IoFreeWorkItem(work_item);\n        return;\n    }\n\n    if (name->Length > 0) {\n        context->name.Buffer = ExAllocatePoolWithTag(PagedPool, name->Length, ALLOC_TAG);\n        if (!context->name.Buffer) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(context);\n            IoFreeWorkItem(work_item);\n            return;\n        }\n\n        RtlCopyMemory(context->name.Buffer, name->Buffer, name->Length);\n        context->name.Length = context->name.MaximumLength = name->Length;\n    } else {\n        context->name.Length = context->name.MaximumLength = 0;\n        context->name.Buffer = NULL;\n    }\n\n    context->func = func;\n    context->work_item = work_item;\n\n    IoQueueWorkItem(work_item, do_pnp_callback, DelayedWorkQueue, context);\n}\n\n_Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE)\nNTSTATUS __stdcall volume_notification(PVOID NotificationStructure, PVOID Context) {\n    DEVICE_INTERFACE_CHANGE_NOTIFICATION* dicn = (DEVICE_INTERFACE_CHANGE_NOTIFICATION*)NotificationStructure;\n\n    UNUSED(Context);\n\n    if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_ARRIVAL, sizeof(GUID)) == sizeof(GUID))\n        enqueue_pnp_callback(dicn->SymbolicLinkName, volume_arrival2);\n    else if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_REMOVAL, sizeof(GUID)) == sizeof(GUID))\n        enqueue_pnp_callback(dicn->SymbolicLinkName, volume_removal);\n\n    return STATUS_SUCCESS;\n}\n\n_Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE)\nNTSTATUS __stdcall pnp_notification(PVOID NotificationStructure, PVOID Context) {\n    DEVICE_INTERFACE_CHANGE_NOTIFICATION* dicn = (DEVICE_INTERFACE_CHANGE_NOTIFICATION*)NotificationStructure;\n\n    UNUSED(Context);\n\n    if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_ARRIVAL, sizeof(GUID)) == sizeof(GUID))\n        enqueue_pnp_callback(dicn->SymbolicLinkName, disk_arrival);\n    else if (RtlCompareMemory(&dicn->Event, &GUID_DEVICE_INTERFACE_REMOVAL, sizeof(GUID)) == sizeof(GUID))\n        enqueue_pnp_callback(dicn->SymbolicLinkName, volume_removal);\n\n    return STATUS_SUCCESS;\n}\n\nstatic void mountmgr_process_drive(PDEVICE_OBJECT mountmgr, PUNICODE_STRING device_name) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    bool need_remove = false;\n    volume_child* vc2 = NULL;\n\n    ExAcquireResourceSharedLite(&pdo_list_lock, true);\n\n    le = pdo_list.Flink;\n    while (le != &pdo_list) {\n        pdo_device_extension* pdode = CONTAINING_RECORD(le, pdo_device_extension, list_entry);\n        LIST_ENTRY* le2;\n\n        ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n        le2 = pdode->children.Flink;\n\n        while (le2 != &pdode->children) {\n            volume_child* vc = CONTAINING_RECORD(le2, volume_child, list_entry);\n\n            if (vc->devobj) {\n                MOUNTDEV_NAME mdn;\n\n                Status = dev_ioctl(vc->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, &mdn, sizeof(MOUNTDEV_NAME), true, NULL);\n                if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)\n                    ERR(\"IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\\n\", Status);\n                else {\n                    MOUNTDEV_NAME* mdn2;\n                    ULONG mdnsize = (ULONG)offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength;\n\n                    mdn2 = ExAllocatePoolWithTag(NonPagedPool, mdnsize, ALLOC_TAG);\n                    if (!mdn2)\n                        ERR(\"out of memory\\n\");\n                    else {\n                        Status = dev_ioctl(vc->devobj, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME, NULL, 0, mdn2, mdnsize, true, NULL);\n                        if (!NT_SUCCESS(Status))\n                            ERR(\"IOCTL_MOUNTDEV_QUERY_DEVICE_NAME returned %08lx\\n\", Status);\n                        else {\n                            if (mdn2->NameLength == device_name->Length && RtlCompareMemory(mdn2->Name, device_name->Buffer, device_name->Length) == device_name->Length) {\n                                vc2 = vc;\n                                need_remove = true;\n                                break;\n                            }\n                        }\n\n                        ExFreePool(mdn2);\n                    }\n                }\n            }\n\n            le2 = le2->Flink;\n        }\n\n        ExReleaseResourceLite(&pdode->child_lock);\n\n        if (need_remove)\n            break;\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&pdo_list_lock);\n\n    if (need_remove) {\n        Status = remove_drive_letter(mountmgr, device_name);\n        if (!NT_SUCCESS(Status))\n            ERR(\"remove_drive_letter returned %08lx\\n\", Status);\n        else\n            vc2->had_drive_letter = true;\n    }\n}\n\nstatic void mountmgr_updated(PDEVICE_OBJECT mountmgr, MOUNTMGR_MOUNT_POINTS* mmps) {\n    ULONG i;\n\n    static const WCHAR pref[] = L\"\\\\DosDevices\\\\\";\n\n    for (i = 0; i < mmps->NumberOfMountPoints; i++) {\n        UNICODE_STRING symlink, device_name;\n\n        if (mmps->MountPoints[i].SymbolicLinkNameOffset != 0) {\n            symlink.Buffer = (WCHAR*)(((uint8_t*)mmps) + mmps->MountPoints[i].SymbolicLinkNameOffset);\n            symlink.Length = symlink.MaximumLength = mmps->MountPoints[i].SymbolicLinkNameLength;\n        } else {\n            symlink.Buffer = NULL;\n            symlink.Length = symlink.MaximumLength = 0;\n        }\n\n        if (mmps->MountPoints[i].DeviceNameOffset != 0) {\n            device_name.Buffer = (WCHAR*)(((uint8_t*)mmps) + mmps->MountPoints[i].DeviceNameOffset);\n            device_name.Length = device_name.MaximumLength = mmps->MountPoints[i].DeviceNameLength;\n        } else {\n            device_name.Buffer = NULL;\n            device_name.Length = device_name.MaximumLength = 0;\n        }\n\n        if (symlink.Length > sizeof(pref) - sizeof(WCHAR) &&\n            RtlCompareMemory(symlink.Buffer, pref, sizeof(pref) - sizeof(WCHAR)) == sizeof(pref) - sizeof(WCHAR))\n            mountmgr_process_drive(mountmgr, &device_name);\n    }\n}\n\n_Function_class_(KSTART_ROUTINE)\nvoid __stdcall mountmgr_thread(_In_ void* context) {\n    UNICODE_STRING mmdevpath;\n    NTSTATUS Status;\n    PFILE_OBJECT FileObject;\n    PDEVICE_OBJECT mountmgr;\n    MOUNTMGR_CHANGE_NOTIFY_INFO mcni;\n\n    UNUSED(context);\n\n    RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);\n    Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &FileObject, &mountmgr);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n        return;\n    }\n\n    mcni.EpicNumber = 0;\n\n    while (true) {\n        PIRP Irp;\n        MOUNTMGR_MOUNT_POINT mmp;\n        MOUNTMGR_MOUNT_POINTS mmps;\n        IO_STATUS_BLOCK iosb;\n\n        KeClearEvent(&mountmgr_thread_event);\n\n        Irp = IoBuildDeviceIoControlRequest(IOCTL_MOUNTMGR_CHANGE_NOTIFY, mountmgr, &mcni, sizeof(MOUNTMGR_CHANGE_NOTIFY_INFO),\n                                            &mcni, sizeof(MOUNTMGR_CHANGE_NOTIFY_INFO), false, &mountmgr_thread_event, &iosb);\n\n        if (!Irp) {\n            ERR(\"out of memory\\n\");\n            break;\n        }\n\n        Status = IoCallDriver(mountmgr, Irp);\n\n        if (Status == STATUS_PENDING) {\n            KeWaitForSingleObject(&mountmgr_thread_event, Executive, KernelMode, false, NULL);\n            Status = iosb.Status;\n        }\n\n        if (shutting_down)\n            break;\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"IOCTL_MOUNTMGR_CHANGE_NOTIFY returned %08lx\\n\", Status);\n            break;\n        }\n\n        TRACE(\"mountmgr changed\\n\");\n\n        RtlZeroMemory(&mmp, sizeof(MOUNTMGR_MOUNT_POINT));\n\n        Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_QUERY_POINTS, &mmp, sizeof(MOUNTMGR_MOUNT_POINT), &mmps, sizeof(MOUNTMGR_MOUNT_POINTS),\n                           false, NULL);\n\n        if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)\n            ERR(\"IOCTL_MOUNTMGR_QUERY_POINTS 1 returned %08lx\\n\", Status);\n        else if (mmps.Size > 0) {\n            MOUNTMGR_MOUNT_POINTS* mmps2;\n\n            mmps2 = ExAllocatePoolWithTag(NonPagedPool, mmps.Size, ALLOC_TAG);\n            if (!mmps2) {\n                ERR(\"out of memory\\n\");\n                break;\n            }\n\n            Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_QUERY_POINTS, &mmp, sizeof(MOUNTMGR_MOUNT_POINT), mmps2, mmps.Size,\n                               false, NULL);\n            if (!NT_SUCCESS(Status))\n                ERR(\"IOCTL_MOUNTMGR_QUERY_POINTS returned %08lx\\n\", Status);\n            else\n                mountmgr_updated(mountmgr, mmps2);\n\n            ExFreePool(mmps2);\n        }\n    }\n\n    ObDereferenceObject(FileObject);\n\n    mountmgr_thread_handle = NULL;\n\n    PsTerminateSystemThread(STATUS_SUCCESS);\n}\n"
  },
  {
    "path": "src/security.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n\n#define SEF_DACL_AUTO_INHERIT 0x01\n#define SEF_SACL_AUTO_INHERIT 0x02\n\ntypedef struct {\n    UCHAR revision;\n    UCHAR elements;\n    UCHAR auth[6];\n    uint32_t nums[8];\n} sid_header;\n\nstatic sid_header sid_BA = { 1, 2, SECURITY_NT_AUTHORITY, {32, 544}}; // BUILTIN\\Administrators\nstatic sid_header sid_SY = { 1, 1, SECURITY_NT_AUTHORITY, {18}};      // NT AUTHORITY\\SYSTEM\nstatic sid_header sid_BU = { 1, 2, SECURITY_NT_AUTHORITY, {32, 545}}; // BUILTIN\\Users\nstatic sid_header sid_AU = { 1, 1, SECURITY_NT_AUTHORITY, {11}};      // NT AUTHORITY\\Authenticated Users\n\ntypedef struct {\n    UCHAR flags;\n    ACCESS_MASK mask;\n    sid_header* sid;\n} dacl;\n\nstatic dacl def_dacls[] = {\n    { 0, FILE_ALL_ACCESS, &sid_BA },\n    { OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE, FILE_ALL_ACCESS, &sid_BA },\n    { 0, FILE_ALL_ACCESS, &sid_SY },\n    { OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE, FILE_ALL_ACCESS, &sid_SY },\n    { OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE, FILE_GENERIC_READ | FILE_GENERIC_EXECUTE, &sid_BU },\n    { OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE | INHERIT_ONLY_ACE, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, &sid_AU },\n    { 0, FILE_GENERIC_READ | FILE_GENERIC_WRITE | FILE_GENERIC_EXECUTE | DELETE, &sid_AU },\n    // FIXME - Mandatory Label\\High Mandatory Level:(OI)(NP)(IO)(NW)\n    { 0, 0, NULL }\n};\n\nextern LIST_ENTRY uid_map_list, gid_map_list;\nextern ERESOURCE mapping_lock;\n\nvoid add_user_mapping(WCHAR* sidstring, ULONG sidstringlength, uint32_t uid) {\n    unsigned int i, np;\n    uint8_t numdashes;\n    uint64_t val;\n    ULONG sidsize;\n    sid_header* sid;\n    uid_map* um;\n\n    if (sidstringlength < 4 ||\n        sidstring[0] != 'S' ||\n        sidstring[1] != '-' ||\n        sidstring[2] != '1' ||\n        sidstring[3] != '-') {\n        ERR(\"invalid SID\\n\");\n        return;\n    }\n\n    sidstring = &sidstring[4];\n    sidstringlength -= 4;\n\n    numdashes = 0;\n    for (i = 0; i < sidstringlength; i++) {\n        if (sidstring[i] == '-') {\n            numdashes++;\n            sidstring[i] = 0;\n        }\n    }\n\n    sidsize = 8 + (numdashes * 4);\n    sid = ExAllocatePoolWithTag(PagedPool, sidsize, ALLOC_TAG);\n    if (!sid) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    sid->revision = 0x01;\n    sid->elements = numdashes;\n\n    np = 0;\n    while (sidstringlength > 0) {\n        val = 0;\n        i = 0;\n        while (sidstring[i] != '-' && i < sidstringlength) {\n            if (sidstring[i] >= '0' && sidstring[i] <= '9') {\n                val *= 10;\n                val += sidstring[i] - '0';\n            } else\n                break;\n\n            i++;\n        }\n\n        i++;\n        TRACE(\"val = %u, i = %u, ssl = %lu\\n\", (uint32_t)val, i, sidstringlength);\n\n        if (np == 0) {\n            sid->auth[0] = (uint8_t)((val & 0xff0000000000) >> 40);\n            sid->auth[1] = (uint8_t)((val & 0xff00000000) >> 32);\n            sid->auth[2] = (uint8_t)((val & 0xff000000) >> 24);\n            sid->auth[3] = (uint8_t)((val & 0xff0000) >> 16);\n            sid->auth[4] = (uint8_t)((val & 0xff00) >> 8);\n            sid->auth[5] = val & 0xff;\n        } else {\n            sid->nums[np-1] = (uint32_t)val;\n        }\n\n        np++;\n\n        if (sidstringlength > i) {\n            sidstringlength -= i;\n\n            sidstring = &sidstring[i];\n        } else\n            break;\n    }\n\n    um = ExAllocatePoolWithTag(PagedPool, sizeof(uid_map), ALLOC_TAG);\n    if (!um) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(sid);\n        return;\n    }\n\n    um->sid = sid;\n    um->uid = uid;\n\n    InsertTailList(&uid_map_list, &um->listentry);\n}\n\nvoid add_group_mapping(WCHAR* sidstring, ULONG sidstringlength, uint32_t gid) {\n    unsigned int i, np;\n    uint8_t numdashes;\n    uint64_t val;\n    ULONG sidsize;\n    sid_header* sid;\n    gid_map* gm;\n\n    if (sidstringlength < 4 || sidstring[0] != 'S' || sidstring[1] != '-' || sidstring[2] != '1' || sidstring[3] != '-') {\n        ERR(\"invalid SID\\n\");\n        return;\n    }\n\n    sidstring = &sidstring[4];\n    sidstringlength -= 4;\n\n    numdashes = 0;\n    for (i = 0; i < sidstringlength; i++) {\n        if (sidstring[i] == '-') {\n            numdashes++;\n            sidstring[i] = 0;\n        }\n    }\n\n    sidsize = 8 + (numdashes * 4);\n    sid = ExAllocatePoolWithTag(PagedPool, sidsize, ALLOC_TAG);\n    if (!sid) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    sid->revision = 0x01;\n    sid->elements = numdashes;\n\n    np = 0;\n    while (sidstringlength > 0) {\n        val = 0;\n        i = 0;\n        while (i < sidstringlength && sidstring[i] != '-') {\n            if (sidstring[i] >= '0' && sidstring[i] <= '9') {\n                val *= 10;\n                val += sidstring[i] - '0';\n            } else\n                break;\n\n            i++;\n        }\n\n        i++;\n        TRACE(\"val = %u, i = %u, ssl = %lu\\n\", (uint32_t)val, i, sidstringlength);\n\n        if (np == 0) {\n            sid->auth[0] = (uint8_t)((val & 0xff0000000000) >> 40);\n            sid->auth[1] = (uint8_t)((val & 0xff00000000) >> 32);\n            sid->auth[2] = (uint8_t)((val & 0xff000000) >> 24);\n            sid->auth[3] = (uint8_t)((val & 0xff0000) >> 16);\n            sid->auth[4] = (uint8_t)((val & 0xff00) >> 8);\n            sid->auth[5] = val & 0xff;\n        } else\n            sid->nums[np-1] = (uint32_t)val;\n\n        np++;\n\n        if (sidstringlength > i) {\n            sidstringlength -= i;\n\n            sidstring = &sidstring[i];\n        } else\n            break;\n    }\n\n    gm = ExAllocatePoolWithTag(PagedPool, sizeof(gid_map), ALLOC_TAG);\n    if (!gm) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(sid);\n        return;\n    }\n\n    gm->sid = sid;\n    gm->gid = gid;\n\n    InsertTailList(&gid_map_list, &gm->listentry);\n}\n\nNTSTATUS uid_to_sid(uint32_t uid, PSID* sid) {\n    LIST_ENTRY* le;\n    sid_header* sh;\n    UCHAR els;\n\n    ExAcquireResourceSharedLite(&mapping_lock, true);\n\n    le = uid_map_list.Flink;\n    while (le != &uid_map_list) {\n        uid_map* um = CONTAINING_RECORD(le, uid_map, listentry);\n\n        if (um->uid == uid) {\n            *sid = ExAllocatePoolWithTag(PagedPool, RtlLengthSid(um->sid), ALLOC_TAG);\n            if (!*sid) {\n                ERR(\"out of memory\\n\");\n                ExReleaseResourceLite(&mapping_lock);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(*sid, um->sid, RtlLengthSid(um->sid));\n            ExReleaseResourceLite(&mapping_lock);\n            return STATUS_SUCCESS;\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&mapping_lock);\n\n    if (uid == 0) { // root\n        // FIXME - find actual Administrator account, rather than SYSTEM (S-1-5-18)\n        // (of form S-1-5-21-...-500)\n\n        els = 1;\n\n        sh = ExAllocatePoolWithTag(PagedPool, sizeof(sid_header) + ((els - 1) * sizeof(uint32_t)), ALLOC_TAG);\n        if (!sh) {\n            ERR(\"out of memory\\n\");\n            *sid = NULL;\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        sh->revision = 1;\n        sh->elements = els;\n\n        sh->auth[0] = 0;\n        sh->auth[1] = 0;\n        sh->auth[2] = 0;\n        sh->auth[3] = 0;\n        sh->auth[4] = 0;\n        sh->auth[5] = 5;\n\n        sh->nums[0] = 18;\n    } else {\n        // fallback to S-1-22-1-X, Samba's SID scheme\n        sh = ExAllocatePoolWithTag(PagedPool, sizeof(sid_header), ALLOC_TAG);\n        if (!sh) {\n            ERR(\"out of memory\\n\");\n            *sid = NULL;\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        sh->revision = 1;\n        sh->elements = 2;\n\n        sh->auth[0] = 0;\n        sh->auth[1] = 0;\n        sh->auth[2] = 0;\n        sh->auth[3] = 0;\n        sh->auth[4] = 0;\n        sh->auth[5] = 22;\n\n        sh->nums[0] = 1;\n        sh->nums[1] = uid;\n    }\n\n    *sid = sh;\n\n    return STATUS_SUCCESS;\n}\n\nuint32_t sid_to_uid(PSID sid) {\n    LIST_ENTRY* le;\n    sid_header* sh = sid;\n\n    ExAcquireResourceSharedLite(&mapping_lock, true);\n\n    le = uid_map_list.Flink;\n    while (le != &uid_map_list) {\n        uid_map* um = CONTAINING_RECORD(le, uid_map, listentry);\n\n        if (RtlEqualSid(sid, um->sid)) {\n            ExReleaseResourceLite(&mapping_lock);\n            return um->uid;\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&mapping_lock);\n\n    if (RtlEqualSid(sid, &sid_SY))\n        return 0; // root\n\n    // Samba's SID scheme: S-1-22-1-X\n    if (sh->revision == 1 && sh->elements == 2 && sh->auth[0] == 0 && sh->auth[1] == 0 && sh->auth[2] == 0 && sh->auth[3] == 0 &&\n        sh->auth[4] == 0 && sh->auth[5] == 22 && sh->nums[0] == 1)\n        return sh->nums[1];\n\n    return UID_NOBODY;\n}\n\nstatic void gid_to_sid(uint32_t gid, PSID* sid) {\n    sid_header* sh;\n    UCHAR els;\n\n    // FIXME - do this properly?\n\n    // fallback to S-1-22-2-X, Samba's SID scheme\n    els = 2;\n    sh = ExAllocatePoolWithTag(PagedPool, sizeof(sid_header) + ((els - 1) * sizeof(uint32_t)), ALLOC_TAG);\n    if (!sh) {\n        ERR(\"out of memory\\n\");\n        *sid = NULL;\n        return;\n    }\n\n    sh->revision = 1;\n    sh->elements = els;\n\n    sh->auth[0] = 0;\n    sh->auth[1] = 0;\n    sh->auth[2] = 0;\n    sh->auth[3] = 0;\n    sh->auth[4] = 0;\n    sh->auth[5] = 22;\n\n    sh->nums[0] = 2;\n    sh->nums[1] = gid;\n\n    *sid = sh;\n}\n\nstatic ACL* load_default_acl() {\n    uint16_t size, i;\n    ACL* acl;\n    ACCESS_ALLOWED_ACE* aaa;\n\n    size = sizeof(ACL);\n    i = 0;\n    while (def_dacls[i].sid) {\n        size += sizeof(ACCESS_ALLOWED_ACE);\n        size += 8 + (def_dacls[i].sid->elements * sizeof(uint32_t)) - sizeof(ULONG);\n        i++;\n    }\n\n    acl = ExAllocatePoolWithTag(PagedPool, size, ALLOC_TAG);\n    if (!acl) {\n        ERR(\"out of memory\\n\");\n        return NULL;\n    }\n\n    acl->AclRevision = ACL_REVISION;\n    acl->Sbz1 = 0;\n    acl->AclSize = size;\n    acl->AceCount = i;\n    acl->Sbz2 = 0;\n\n    aaa = (ACCESS_ALLOWED_ACE*)&acl[1];\n    i = 0;\n    while (def_dacls[i].sid) {\n        aaa->Header.AceType = ACCESS_ALLOWED_ACE_TYPE;\n        aaa->Header.AceFlags = def_dacls[i].flags;\n        aaa->Header.AceSize = sizeof(ACCESS_ALLOWED_ACE) - sizeof(ULONG) + 8 + (def_dacls[i].sid->elements * sizeof(uint32_t));\n        aaa->Mask = def_dacls[i].mask;\n\n        RtlCopyMemory(&aaa->SidStart, def_dacls[i].sid, 8 + (def_dacls[i].sid->elements * sizeof(uint32_t)));\n\n        aaa = (ACCESS_ALLOWED_ACE*)((uint8_t*)aaa + aaa->Header.AceSize);\n\n        i++;\n    }\n\n    return acl;\n}\n\nstatic void get_top_level_sd(fcb* fcb) {\n    NTSTATUS Status;\n    SECURITY_DESCRIPTOR sd;\n    ULONG buflen;\n    ACL* acl = NULL;\n    PSID usersid = NULL, groupsid = NULL;\n\n    Status = RtlCreateSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlCreateSecurityDescriptor returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    Status = uid_to_sid(fcb->inode_item.st_uid, &usersid);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"uid_to_sid returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    Status = RtlSetOwnerSecurityDescriptor(&sd, usersid, false);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlSetOwnerSecurityDescriptor returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    gid_to_sid(fcb->inode_item.st_gid, &groupsid);\n    if (!groupsid) {\n        ERR(\"out of memory\\n\");\n        goto end;\n    }\n\n    Status = RtlSetGroupSecurityDescriptor(&sd, groupsid, false);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlSetGroupSecurityDescriptor returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    acl = load_default_acl();\n\n    if (!acl) {\n        ERR(\"out of memory\\n\");\n        goto end;\n    }\n\n    Status = RtlSetDaclSecurityDescriptor(&sd, true, acl, false);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlSetDaclSecurityDescriptor returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    // FIXME - SACL_SECURITY_INFORMATION\n\n    buflen = 0;\n\n    // get sd size\n    Status = RtlAbsoluteToSelfRelativeSD(&sd, NULL, &buflen);\n    if (Status != STATUS_SUCCESS && Status != STATUS_BUFFER_TOO_SMALL) {\n        ERR(\"RtlAbsoluteToSelfRelativeSD 1 returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    if (buflen == 0 || Status == STATUS_SUCCESS) {\n        TRACE(\"RtlAbsoluteToSelfRelativeSD said SD is zero-length\\n\");\n        goto end;\n    }\n\n    fcb->sd = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG);\n    if (!fcb->sd) {\n        ERR(\"out of memory\\n\");\n        goto end;\n    }\n\n    Status = RtlAbsoluteToSelfRelativeSD(&sd, fcb->sd, &buflen);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlAbsoluteToSelfRelativeSD 2 returned %08lx\\n\", Status);\n        ExFreePool(fcb->sd);\n        fcb->sd = NULL;\n        goto end;\n    }\n\nend:\n    if (acl)\n        ExFreePool(acl);\n\n    if (usersid)\n        ExFreePool(usersid);\n\n    if (groupsid)\n        ExFreePool(groupsid);\n}\n\nvoid fcb_get_sd(fcb* fcb, struct _fcb* parent, bool look_for_xattr, PIRP Irp) {\n    NTSTATUS Status;\n    PSID usersid = NULL, groupsid = NULL;\n    SECURITY_SUBJECT_CONTEXT subjcont;\n    ULONG buflen;\n    PSECURITY_DESCRIPTOR* abssd;\n    PSECURITY_DESCRIPTOR newsd;\n    PACL dacl, sacl;\n    PSID owner, group;\n    ULONG abssdlen = 0, dacllen = 0, sacllen = 0, ownerlen = 0, grouplen = 0;\n    uint8_t* buf;\n\n    if (look_for_xattr && get_xattr(fcb->Vcb, fcb->subvol, fcb->inode, EA_NTACL, EA_NTACL_HASH, (uint8_t**)&fcb->sd, (uint16_t*)&buflen, Irp))\n        return;\n\n    if (!parent) {\n        get_top_level_sd(fcb);\n        return;\n    }\n\n    SeCaptureSubjectContext(&subjcont);\n\n    Status = SeAssignSecurityEx(parent->sd, NULL, (void**)&fcb->sd, NULL, fcb->type == BTRFS_TYPE_DIRECTORY, SEF_DACL_AUTO_INHERIT,\n                                &subjcont, IoGetFileObjectGenericMapping(), PagedPool);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"SeAssignSecurityEx returned %08lx\\n\", Status);\n        return;\n    }\n\n    Status = RtlSelfRelativeToAbsoluteSD(fcb->sd, NULL, &abssdlen, NULL, &dacllen, NULL, &sacllen, NULL, &ownerlen,\n                                         NULL, &grouplen);\n    if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_TOO_SMALL) {\n        ERR(\"RtlSelfRelativeToAbsoluteSD returned %08lx\\n\", Status);\n        return;\n    }\n\n    if (abssdlen + dacllen + sacllen + ownerlen + grouplen == 0) {\n        ERR(\"RtlSelfRelativeToAbsoluteSD returned zero lengths\\n\");\n        return;\n    }\n\n    buf = (uint8_t*)ExAllocatePoolWithTag(PagedPool, abssdlen + dacllen + sacllen + ownerlen + grouplen, ALLOC_TAG);\n    if (!buf) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    abssd = (PSECURITY_DESCRIPTOR)buf;\n    dacl = (PACL)(buf + abssdlen);\n    sacl = (PACL)(buf + abssdlen + dacllen);\n    owner = (PSID)(buf + abssdlen + dacllen + sacllen);\n    group = (PSID)(buf + abssdlen + dacllen + sacllen + ownerlen);\n\n    Status = RtlSelfRelativeToAbsoluteSD(fcb->sd, abssd, &abssdlen, dacl, &dacllen, sacl, &sacllen, owner, &ownerlen,\n                                         group, &grouplen);\n    if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_TOO_SMALL) {\n        ERR(\"RtlSelfRelativeToAbsoluteSD returned %08lx\\n\", Status);\n        ExFreePool(buf);\n        return;\n    }\n\n    Status = uid_to_sid(fcb->inode_item.st_uid, &usersid);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"uid_to_sid returned %08lx\\n\", Status);\n        ExFreePool(buf);\n        return;\n    }\n\n    RtlSetOwnerSecurityDescriptor(abssd, usersid, false);\n\n    gid_to_sid(fcb->inode_item.st_gid, &groupsid);\n    if (!groupsid) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(usersid);\n        ExFreePool(buf);\n        return;\n    }\n\n    RtlSetGroupSecurityDescriptor(abssd, groupsid, false);\n\n    buflen = 0;\n\n    Status = RtlAbsoluteToSelfRelativeSD(abssd, NULL, &buflen);\n    if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_TOO_SMALL) {\n        ERR(\"RtlAbsoluteToSelfRelativeSD returned %08lx\\n\", Status);\n        ExFreePool(usersid);\n        ExFreePool(groupsid);\n        ExFreePool(buf);\n        return;\n    }\n\n    if (buflen == 0) {\n        ERR(\"RtlAbsoluteToSelfRelativeSD returned a buffer size of 0\\n\");\n        ExFreePool(usersid);\n        ExFreePool(groupsid);\n        ExFreePool(buf);\n        return;\n    }\n\n    newsd = ExAllocatePoolWithTag(PagedPool, buflen, ALLOC_TAG);\n    if (!newsd) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(usersid);\n        ExFreePool(groupsid);\n        ExFreePool(buf);\n        return;\n    }\n\n    Status = RtlAbsoluteToSelfRelativeSD(abssd, newsd, &buflen);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlAbsoluteToSelfRelativeSD returned %08lx\\n\", Status);\n        ExFreePool(usersid);\n        ExFreePool(groupsid);\n        ExFreePool(buf);\n        return;\n    }\n\n    ExFreePool(fcb->sd);\n    fcb->sd = newsd;\n\n    ExFreePool(usersid);\n    ExFreePool(groupsid);\n    ExFreePool(buf);\n}\n\nstatic NTSTATUS get_file_security(PFILE_OBJECT FileObject, SECURITY_DESCRIPTOR* relsd, ULONG* buflen, SECURITY_INFORMATION flags) {\n    NTSTATUS Status;\n    fcb* fcb = FileObject->FsContext;\n    ccb* ccb = FileObject->FsContext2;\n    file_ref* fileref = ccb ? ccb->fileref : NULL;\n\n    if (fcb->ads) {\n        if (fileref && fileref->parent)\n            fcb = fileref->parent->fcb;\n        else {\n            ERR(\"could not get parent fcb for stream\\n\");\n            return STATUS_INTERNAL_ERROR;\n        }\n    }\n\n    // Why (void**)? Is this a bug in mingw?\n    Status = SeQuerySecurityDescriptorInfo(&flags, relsd, buflen, (void**)&fcb->sd);\n\n    if (Status == STATUS_BUFFER_TOO_SMALL)\n        TRACE(\"SeQuerySecurityDescriptorInfo returned %08lx\\n\", Status);\n    else if (!NT_SUCCESS(Status))\n        ERR(\"SeQuerySecurityDescriptorInfo returned %08lx\\n\", Status);\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_QUERY_SECURITY)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_query_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    NTSTATUS Status;\n    SECURITY_DESCRIPTOR* sd;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    ULONG buflen;\n    bool top_level;\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    ccb* ccb = FileObject ? FileObject->FsContext2 : NULL;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"query security\\n\");\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (!ccb) {\n        ERR(\"no ccb\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (Irp->RequestorMode == UserMode && !(ccb->access & READ_CONTROL)) {\n        WARN(\"insufficient permissions\\n\");\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    Status = STATUS_SUCCESS;\n\n    Irp->IoStatus.Information = 0;\n\n    if (IrpSp->Parameters.QuerySecurity.SecurityInformation & OWNER_SECURITY_INFORMATION)\n        TRACE(\"OWNER_SECURITY_INFORMATION\\n\");\n\n    if (IrpSp->Parameters.QuerySecurity.SecurityInformation & GROUP_SECURITY_INFORMATION)\n        TRACE(\"GROUP_SECURITY_INFORMATION\\n\");\n\n    if (IrpSp->Parameters.QuerySecurity.SecurityInformation & DACL_SECURITY_INFORMATION)\n        TRACE(\"DACL_SECURITY_INFORMATION\\n\");\n\n    if (IrpSp->Parameters.QuerySecurity.SecurityInformation & SACL_SECURITY_INFORMATION)\n        TRACE(\"SACL_SECURITY_INFORMATION\\n\");\n\n    TRACE(\"length = %lu\\n\", IrpSp->Parameters.QuerySecurity.Length);\n\n    sd = map_user_buffer(Irp, NormalPagePriority);\n    TRACE(\"sd = %p\\n\", sd);\n\n    if (Irp->MdlAddress && !sd) {\n        ERR(\"MmGetSystemAddressForMdlSafe returned NULL\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    buflen = IrpSp->Parameters.QuerySecurity.Length;\n\n    Status = get_file_security(IrpSp->FileObject, sd, &buflen, IrpSp->Parameters.QuerySecurity.SecurityInformation);\n\n    if (NT_SUCCESS(Status))\n        Irp->IoStatus.Information = IrpSp->Parameters.QuerySecurity.Length;\n    else if (Status == STATUS_BUFFER_TOO_SMALL) {\n        Irp->IoStatus.Information = buflen;\n        Status = STATUS_BUFFER_OVERFLOW;\n    } else\n        Irp->IoStatus.Information = 0;\n\nend:\n    TRACE(\"Irp->IoStatus.Information = %Iu\\n\", Irp->IoStatus.Information);\n\n    Irp->IoStatus.Status = Status;\n\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    TRACE(\"returning %08lx\\n\", Status);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\nstatic NTSTATUS set_file_security(device_extension* Vcb, PFILE_OBJECT FileObject, SECURITY_DESCRIPTOR* sd, PSECURITY_INFORMATION flags, PIRP Irp) {\n    NTSTATUS Status;\n    fcb* fcb = FileObject->FsContext;\n    ccb* ccb = FileObject->FsContext2;\n    file_ref* fileref = ccb ? ccb->fileref : NULL;\n    SECURITY_DESCRIPTOR* oldsd;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n\n    TRACE(\"(%p, %p, %p, %lx)\\n\", Vcb, FileObject, sd, *flags);\n\n    if (Vcb->readonly)\n        return STATUS_MEDIA_WRITE_PROTECTED;\n\n    if (fcb->ads) {\n        if (fileref && fileref->parent)\n            fcb = fileref->parent->fcb;\n        else {\n            ERR(\"could not find parent fcb for stream\\n\");\n            return STATUS_INTERNAL_ERROR;\n        }\n    }\n\n    if (!fcb || !ccb)\n        return STATUS_INVALID_PARAMETER;\n\n    ExAcquireResourceExclusiveLite(fcb->Header.Resource, true);\n\n    if (is_subvol_readonly(fcb->subvol, Irp)) {\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    oldsd = fcb->sd;\n\n    Status = SeSetSecurityDescriptorInfo(NULL, flags, sd, (void**)&fcb->sd, PagedPool, IoGetFileObjectGenericMapping());\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"SeSetSecurityDescriptorInfo returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    ExFreePool(oldsd);\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    fcb->inode_item.transid = Vcb->superblock.generation;\n\n    if (!ccb->user_set_change_time)\n        fcb->inode_item.st_ctime = now;\n\n    fcb->inode_item.sequence++;\n\n    fcb->sd_dirty = true;\n    fcb->sd_deleted = false;\n    fcb->inode_item_changed = true;\n\n    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;\n    fcb->subvol->root_item.ctime = now;\n\n    mark_fcb_dirty(fcb);\n\n    queue_notification_fcb(fileref, FILE_NOTIFY_CHANGE_SECURITY, FILE_ACTION_MODIFIED, NULL);\n\nend:\n    ExReleaseResourceLite(fcb->Header.Resource);\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_SET_SECURITY)\n_Function_class_(DRIVER_DISPATCH)\nNTSTATUS __stdcall drv_set_security(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    NTSTATUS Status;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    ccb* ccb = FileObject ? FileObject->FsContext2 : NULL;\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    ULONG access_req = 0;\n    bool top_level;\n\n    FsRtlEnterFileSystem();\n\n    TRACE(\"set security\\n\");\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (!ccb) {\n        ERR(\"no ccb\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    Status = STATUS_SUCCESS;\n\n    Irp->IoStatus.Information = 0;\n\n    if (IrpSp->Parameters.SetSecurity.SecurityInformation & OWNER_SECURITY_INFORMATION) {\n        TRACE(\"OWNER_SECURITY_INFORMATION\\n\");\n        access_req |= WRITE_OWNER;\n    }\n\n    if (IrpSp->Parameters.SetSecurity.SecurityInformation & GROUP_SECURITY_INFORMATION) {\n        TRACE(\"GROUP_SECURITY_INFORMATION\\n\");\n        access_req |= WRITE_OWNER;\n    }\n\n    if (IrpSp->Parameters.SetSecurity.SecurityInformation & DACL_SECURITY_INFORMATION) {\n        TRACE(\"DACL_SECURITY_INFORMATION\\n\");\n        access_req |= WRITE_DAC;\n    }\n\n    if (IrpSp->Parameters.SetSecurity.SecurityInformation & SACL_SECURITY_INFORMATION) {\n        TRACE(\"SACL_SECURITY_INFORMATION\\n\");\n        access_req |= ACCESS_SYSTEM_SECURITY;\n    }\n\n    if (Irp->RequestorMode == UserMode && (ccb->access & access_req) != access_req) {\n        Status = STATUS_ACCESS_DENIED;\n        WARN(\"insufficient privileges\\n\");\n        goto end;\n    }\n\n    Status = set_file_security(DeviceObject->DeviceExtension, FileObject, IrpSp->Parameters.SetSecurity.SecurityDescriptor,\n                               &IrpSp->Parameters.SetSecurity.SecurityInformation, Irp);\n\nend:\n    Irp->IoStatus.Status = Status;\n\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\n    TRACE(\"returning %08lx\\n\", Status);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n\nstatic bool search_for_gid(fcb* fcb, PSID sid) {\n    LIST_ENTRY* le;\n\n    le = gid_map_list.Flink;\n    while (le != &gid_map_list) {\n        gid_map* gm = CONTAINING_RECORD(le, gid_map, listentry);\n\n        if (RtlEqualSid(sid, gm->sid)) {\n            fcb->inode_item.st_gid = gm->gid;\n            return true;\n        }\n\n        le = le->Flink;\n    }\n\n    return false;\n}\n\nvoid find_gid(struct _fcb* fcb, struct _fcb* parfcb, PSECURITY_SUBJECT_CONTEXT subjcont) {\n    NTSTATUS Status;\n    TOKEN_OWNER* to;\n    TOKEN_PRIMARY_GROUP* tpg;\n    TOKEN_GROUPS* tg;\n\n    if (parfcb && parfcb->inode_item.st_mode & S_ISGID) {\n        fcb->inode_item.st_gid = parfcb->inode_item.st_gid;\n        return;\n    }\n\n    ExAcquireResourceSharedLite(&mapping_lock, true);\n\n    if (!subjcont || !subjcont->PrimaryToken || IsListEmpty(&gid_map_list)) {\n        ExReleaseResourceLite(&mapping_lock);\n        return;\n    }\n\n    Status = SeQueryInformationToken(subjcont->PrimaryToken, TokenOwner, (void**)&to);\n    if (!NT_SUCCESS(Status))\n        ERR(\"SeQueryInformationToken returned %08lx\\n\", Status);\n    else {\n        if (search_for_gid(fcb, to->Owner)) {\n            ExReleaseResourceLite(&mapping_lock);\n            ExFreePool(to);\n            return;\n        }\n\n        ExFreePool(to);\n    }\n\n    Status = SeQueryInformationToken(subjcont->PrimaryToken, TokenPrimaryGroup, (void**)&tpg);\n    if (!NT_SUCCESS(Status))\n        ERR(\"SeQueryInformationToken returned %08lx\\n\", Status);\n    else {\n        if (search_for_gid(fcb, tpg->PrimaryGroup)) {\n            ExReleaseResourceLite(&mapping_lock);\n            ExFreePool(tpg);\n            return;\n        }\n\n        ExFreePool(tpg);\n    }\n\n    Status = SeQueryInformationToken(subjcont->PrimaryToken, TokenGroups, (void**)&tg);\n    if (!NT_SUCCESS(Status))\n        ERR(\"SeQueryInformationToken returned %08lx\\n\", Status);\n    else {\n        ULONG i;\n\n        for (i = 0; i < tg->GroupCount; i++) {\n            if (search_for_gid(fcb, tg->Groups[i].Sid)) {\n                ExReleaseResourceLite(&mapping_lock);\n                ExFreePool(tg);\n                return;\n            }\n        }\n\n        ExFreePool(tg);\n    }\n\n    ExReleaseResourceLite(&mapping_lock);\n}\n\nNTSTATUS fcb_get_new_sd(fcb* fcb, file_ref* parfileref, ACCESS_STATE* as) {\n    NTSTATUS Status;\n    PSID owner;\n    BOOLEAN defaulted;\n\n    Status = SeAssignSecurityEx(parfileref ? parfileref->fcb->sd : NULL, as->SecurityDescriptor, (void**)&fcb->sd, NULL, fcb->type == BTRFS_TYPE_DIRECTORY,\n                                SEF_SACL_AUTO_INHERIT, &as->SubjectSecurityContext, IoGetFileObjectGenericMapping(), PagedPool);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"SeAssignSecurityEx returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    Status = RtlGetOwnerSecurityDescriptor(fcb->sd, &owner, &defaulted);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"RtlGetOwnerSecurityDescriptor returned %08lx\\n\", Status);\n        fcb->inode_item.st_uid = UID_NOBODY;\n    } else {\n        fcb->inode_item.st_uid = sid_to_uid(owner);\n    }\n\n    find_gid(fcb, parfileref ? parfileref->fcb : NULL, &as->SubjectSecurityContext);\n\n    return STATUS_SUCCESS;\n}\n"
  },
  {
    "path": "src/send.c",
    "content": "/* Copyright (c) Mark Harmstone 2017\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include \"crc32c.h\"\n\ntypedef struct send_dir {\n    LIST_ENTRY list_entry;\n    uint64_t inode;\n    bool dummy;\n    BTRFS_TIME atime;\n    BTRFS_TIME mtime;\n    BTRFS_TIME ctime;\n    struct send_dir* parent;\n    uint16_t namelen;\n    char* name;\n    LIST_ENTRY deleted_children;\n} send_dir;\n\ntypedef struct {\n    LIST_ENTRY list_entry;\n    uint64_t inode;\n    bool dir;\n    send_dir* sd;\n    char tmpname[64];\n} orphan;\n\ntypedef struct {\n    LIST_ENTRY list_entry;\n    ULONG namelen;\n    char name[1];\n} deleted_child;\n\ntypedef struct {\n    LIST_ENTRY list_entry;\n    send_dir* sd;\n    uint16_t namelen;\n    char name[1];\n} ref;\n\ntypedef struct {\n    send_dir* sd;\n    uint64_t last_child_inode;\n    LIST_ENTRY list_entry;\n} pending_rmdir;\n\ntypedef struct {\n    uint64_t offset;\n    LIST_ENTRY list_entry;\n    ULONG datalen;\n    EXTENT_DATA data;\n} send_ext;\n\ntypedef struct {\n    device_extension* Vcb;\n    root* root;\n    root* parent;\n    uint8_t* data;\n    ULONG datalen;\n    ULONG num_clones;\n    root** clones;\n    LIST_ENTRY orphans;\n    LIST_ENTRY dirs;\n    LIST_ENTRY pending_rmdirs;\n    KEVENT buffer_event;\n    send_dir* root_dir;\n    send_info* send;\n\n    struct {\n        uint64_t inode;\n        bool deleting;\n        bool new;\n        uint64_t gen;\n        uint64_t uid;\n        uint64_t olduid;\n        uint64_t gid;\n        uint64_t oldgid;\n        uint64_t mode;\n        uint64_t oldmode;\n        uint64_t size;\n        uint64_t flags;\n        BTRFS_TIME atime;\n        BTRFS_TIME mtime;\n        BTRFS_TIME ctime;\n        bool file;\n        char* path;\n        orphan* o;\n        send_dir* sd;\n        LIST_ENTRY refs;\n        LIST_ENTRY oldrefs;\n        LIST_ENTRY exts;\n        LIST_ENTRY oldexts;\n    } lastinode;\n} send_context;\n\n#define MAX_SEND_WRITE 0xc000 // 48 KB\n#define SEND_BUFFER_LENGTH 0x100000 // 1 MB\n\nstatic NTSTATUS find_send_dir(send_context* context, uint64_t dir, uint64_t generation, send_dir** psd, bool* added_dummy);\nstatic NTSTATUS wait_for_flush(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2);\n\nstatic void send_command(send_context* context, uint16_t cmd) {\n    btrfs_send_command* bsc = (btrfs_send_command*)&context->data[context->datalen];\n\n    bsc->cmd = cmd;\n    bsc->csum = 0;\n\n    context->datalen += sizeof(btrfs_send_command);\n}\n\nstatic void send_command_finish(send_context* context, ULONG pos) {\n    btrfs_send_command* bsc = (btrfs_send_command*)&context->data[pos];\n\n    bsc->length = context->datalen - pos - sizeof(btrfs_send_command);\n    bsc->csum = calc_crc32c(0, (uint8_t*)bsc, context->datalen - pos);\n}\n\nstatic void send_add_tlv(send_context* context, uint16_t type, void* data, uint16_t length) {\n    btrfs_send_tlv* tlv = (btrfs_send_tlv*)&context->data[context->datalen];\n\n    tlv->type = type;\n    tlv->length = length;\n\n    if (length > 0 && data)\n        RtlCopyMemory(&tlv[1], data, length);\n\n    context->datalen += sizeof(btrfs_send_tlv) + length;\n}\n\nstatic char* uint64_to_char(uint64_t num, char* buf) {\n    char *tmp, tmp2[20];\n\n    if (num == 0) {\n        buf[0] = '0';\n        return buf + 1;\n    }\n\n    tmp = &tmp2[20];\n    while (num > 0) {\n        tmp--;\n        *tmp = (num % 10) + '0';\n        num /= 10;\n    }\n\n    RtlCopyMemory(buf, tmp, tmp2 + sizeof(tmp2) - tmp);\n\n    return &buf[tmp2 + sizeof(tmp2) - tmp];\n}\n\nstatic NTSTATUS get_orphan_name(send_context* context, uint64_t inode, uint64_t generation, char* name) {\n    char *ptr, *ptr2;\n    uint64_t index = 0;\n    KEY searchkey;\n\n    name[0] = 'o';\n\n    ptr = uint64_to_char(inode, &name[1]);\n    *ptr = '-'; ptr++;\n    ptr = uint64_to_char(generation, ptr);\n    *ptr = '-'; ptr++;\n    ptr2 = ptr;\n\n    searchkey.obj_id = SUBVOL_ROOT_INODE;\n    searchkey.obj_type = TYPE_DIR_ITEM;\n\n    do {\n        NTSTATUS Status;\n        traverse_ptr tp;\n\n        ptr = uint64_to_char(index, ptr);\n        *ptr = 0;\n\n        searchkey.offset = calc_crc32c(0xfffffffe, (uint8_t*)name, (ULONG)(ptr - name));\n\n        Status = find_item(context->Vcb, context->root, &tp, &searchkey, false, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (!keycmp(searchkey, tp.item->key))\n            goto cont;\n\n        if (context->parent) {\n            Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"find_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (!keycmp(searchkey, tp.item->key))\n                goto cont;\n        }\n\n        return STATUS_SUCCESS;\n\ncont:\n        index++;\n        ptr = ptr2;\n    } while (true);\n}\n\nstatic void add_orphan(send_context* context, orphan* o) {\n    LIST_ENTRY* le;\n\n    le = context->orphans.Flink;\n    while (le != &context->orphans) {\n        orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry);\n\n        if (o2->inode > o->inode) {\n            InsertHeadList(o2->list_entry.Blink, &o->list_entry);\n            return;\n        }\n\n        le = le->Flink;\n    }\n\n    InsertTailList(&context->orphans, &o->list_entry);\n}\n\nstatic NTSTATUS send_read_symlink(send_context* context, uint64_t inode, char** link, uint16_t* linklen) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    EXTENT_DATA* ed;\n\n    searchkey.obj_id = inode;\n    searchkey.obj_type = TYPE_EXTENT_DATA;\n    searchkey.offset = 0;\n\n    Status = find_item(context->Vcb, context->root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (keycmp(tp.item->key, searchkey)) {\n        ERR(\"could not find (%I64x,%x,%I64x)\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (tp.item->size < sizeof(EXTENT_DATA)) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n            tp.item->size, sizeof(EXTENT_DATA));\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    ed = (EXTENT_DATA*)tp.item->data;\n\n    if (ed->type != EXTENT_TYPE_INLINE) {\n        WARN(\"symlink data was not inline, returning blank string\\n\");\n        *link = NULL;\n        *linklen = 0;\n        return STATUS_SUCCESS;\n    }\n\n    if (tp.item->size < offsetof(EXTENT_DATA, data[0]) + ed->decoded_size) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %I64u\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n            tp.item->size, offsetof(EXTENT_DATA, data[0]) + ed->decoded_size);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    *link = (char*)ed->data;\n    *linklen = (uint16_t)ed->decoded_size;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS send_inode(send_context* context, traverse_ptr* tp, traverse_ptr* tp2) {\n    NTSTATUS Status;\n    INODE_ITEM* ii;\n\n    if (tp2 && !tp) {\n        INODE_ITEM* ii2 = (INODE_ITEM*)tp2->item->data;\n\n        if (tp2->item->size < sizeof(INODE_ITEM)) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset,\n                tp2->item->size, sizeof(INODE_ITEM));\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        context->lastinode.inode = tp2->item->key.obj_id;\n        context->lastinode.deleting = true;\n        context->lastinode.gen = ii2->generation;\n        context->lastinode.mode = ii2->st_mode;\n        context->lastinode.flags = ii2->flags;\n        context->lastinode.o = NULL;\n        context->lastinode.sd = NULL;\n\n        return STATUS_SUCCESS;\n    }\n\n    ii = (INODE_ITEM*)tp->item->data;\n\n    if (tp->item->size < sizeof(INODE_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,\n            tp->item->size, sizeof(INODE_ITEM));\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    context->lastinode.inode = tp->item->key.obj_id;\n    context->lastinode.deleting = false;\n    context->lastinode.gen = ii->generation;\n    context->lastinode.uid = ii->st_uid;\n    context->lastinode.gid = ii->st_gid;\n    context->lastinode.mode = ii->st_mode;\n    context->lastinode.size = ii->st_size;\n    context->lastinode.atime = ii->st_atime;\n    context->lastinode.mtime = ii->st_mtime;\n    context->lastinode.ctime = ii->st_ctime;\n    context->lastinode.flags = ii->flags;\n    context->lastinode.file = false;\n    context->lastinode.o = NULL;\n    context->lastinode.sd = NULL;\n\n    if (context->lastinode.path) {\n        ExFreePool(context->lastinode.path);\n        context->lastinode.path = NULL;\n    }\n\n    if (tp2) {\n        INODE_ITEM* ii2 = (INODE_ITEM*)tp2->item->data;\n        LIST_ENTRY* le;\n\n        if (tp2->item->size < sizeof(INODE_ITEM)) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset,\n                tp2->item->size, sizeof(INODE_ITEM));\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        context->lastinode.oldmode = ii2->st_mode;\n        context->lastinode.olduid = ii2->st_uid;\n        context->lastinode.oldgid = ii2->st_gid;\n\n        if ((ii2->st_mode & __S_IFREG) == __S_IFREG && (ii2->st_mode & __S_IFLNK) != __S_IFLNK && (ii2->st_mode & __S_IFSOCK) != __S_IFSOCK)\n            context->lastinode.file = true;\n\n        context->lastinode.new = false;\n\n        le = context->orphans.Flink;\n        while (le != &context->orphans) {\n            orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry);\n\n            if (o2->inode == tp->item->key.obj_id) {\n                context->lastinode.o = o2;\n                break;\n            } else if (o2->inode > tp->item->key.obj_id)\n                break;\n\n            le = le->Flink;\n        }\n    } else\n        context->lastinode.new = true;\n\n    if (tp->item->key.obj_id == SUBVOL_ROOT_INODE) {\n        send_dir* sd;\n\n        Status = find_send_dir(context, tp->item->key.obj_id, ii->generation, &sd, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_send_dir returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        sd->atime = ii->st_atime;\n        sd->mtime = ii->st_mtime;\n        sd->ctime = ii->st_ctime;\n        context->root_dir = sd;\n    } else if (!tp2) {\n        ULONG pos = context->datalen;\n        uint16_t cmd;\n        send_dir* sd;\n\n        char name[64];\n        orphan* o;\n\n        // skip creating orphan directory if we've already done so\n        if (ii->st_mode & __S_IFDIR) {\n            LIST_ENTRY* le;\n\n            le = context->orphans.Flink;\n            while (le != &context->orphans) {\n                orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry);\n\n                if (o2->inode == tp->item->key.obj_id) {\n                    context->lastinode.o = o2;\n                    o2->sd->atime = ii->st_atime;\n                    o2->sd->mtime = ii->st_mtime;\n                    o2->sd->ctime = ii->st_ctime;\n                    o2->sd->dummy = false;\n                    return STATUS_SUCCESS;\n                } else if (o2->inode > tp->item->key.obj_id)\n                    break;\n\n                le = le->Flink;\n            }\n        }\n\n        if ((ii->st_mode & __S_IFSOCK) == __S_IFSOCK)\n            cmd = BTRFS_SEND_CMD_MKSOCK;\n        else if ((ii->st_mode & __S_IFLNK) == __S_IFLNK)\n            cmd = BTRFS_SEND_CMD_SYMLINK;\n        else if ((ii->st_mode & __S_IFCHR) == __S_IFCHR || (ii->st_mode & __S_IFBLK) == __S_IFBLK)\n            cmd = BTRFS_SEND_CMD_MKNOD;\n        else if ((ii->st_mode & __S_IFDIR) == __S_IFDIR)\n            cmd = BTRFS_SEND_CMD_MKDIR;\n        else if ((ii->st_mode & __S_IFIFO) == __S_IFIFO)\n            cmd = BTRFS_SEND_CMD_MKFIFO;\n        else {\n            cmd = BTRFS_SEND_CMD_MKFILE;\n            context->lastinode.file = true;\n        }\n\n        send_command(context, cmd);\n\n        Status = get_orphan_name(context, tp->item->key.obj_id, ii->generation, name);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"get_orphan_name returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        send_add_tlv(context, BTRFS_SEND_TLV_PATH, name, (uint16_t)strlen(name));\n        send_add_tlv(context, BTRFS_SEND_TLV_INODE, &tp->item->key.obj_id, sizeof(uint64_t));\n\n        if (cmd == BTRFS_SEND_CMD_MKNOD || cmd == BTRFS_SEND_CMD_MKFIFO || cmd == BTRFS_SEND_CMD_MKSOCK) {\n            uint64_t rdev = makedev((ii->st_rdev & 0xFFFFFFFFFFF) >> 20, ii->st_rdev & 0xFFFFF), mode = ii->st_mode;\n\n            send_add_tlv(context, BTRFS_SEND_TLV_RDEV, &rdev, sizeof(uint64_t));\n            send_add_tlv(context, BTRFS_SEND_TLV_MODE, &mode, sizeof(uint64_t));\n        } else if (cmd == BTRFS_SEND_CMD_SYMLINK && ii->st_size > 0) {\n            char* link;\n            uint16_t linklen;\n\n            Status = send_read_symlink(context, tp->item->key.obj_id, &link, &linklen);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"send_read_symlink returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            send_add_tlv(context, BTRFS_SEND_TLV_PATH_LINK, link, linklen);\n        }\n\n        send_command_finish(context, pos);\n\n        if (ii->st_mode & __S_IFDIR) {\n            Status = find_send_dir(context, tp->item->key.obj_id, ii->generation, &sd, NULL);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"find_send_dir returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            sd->dummy = false;\n        } else\n            sd = NULL;\n\n        context->lastinode.sd = sd;\n\n        o = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG);\n        if (!o) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        o->inode = tp->item->key.obj_id;\n        o->dir = (ii->st_mode & __S_IFDIR && ii->st_size > 0) ? true : false;\n        strcpy(o->tmpname, name);\n        o->sd = sd;\n        add_orphan(context, o);\n\n        context->lastinode.path = ExAllocatePoolWithTag(PagedPool, strlen(o->tmpname) + 1, ALLOC_TAG);\n        if (!context->lastinode.path) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        strcpy(context->lastinode.path, o->tmpname);\n\n        context->lastinode.o = o;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS send_add_dir(send_context* context, uint64_t inode, send_dir* parent, char* name, uint16_t namelen, bool dummy, LIST_ENTRY* lastentry, send_dir** psd) {\n    LIST_ENTRY* le;\n    send_dir* sd = ExAllocatePoolWithTag(PagedPool, sizeof(send_dir), ALLOC_TAG);\n\n    if (!sd) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    sd->inode = inode;\n    sd->dummy = dummy;\n    sd->parent = parent;\n\n    if (!dummy) {\n        sd->atime = context->lastinode.atime;\n        sd->mtime = context->lastinode.mtime;\n        sd->ctime = context->lastinode.ctime;\n    }\n\n    if (namelen > 0) {\n        sd->name = ExAllocatePoolWithTag(PagedPool, namelen, ALLOC_TAG);\n        if (!sd->name) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(sd);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        memcpy(sd->name, name, namelen);\n    } else\n        sd->name = NULL;\n\n    sd->namelen = namelen;\n\n    InitializeListHead(&sd->deleted_children);\n\n    if (lastentry)\n        InsertHeadList(lastentry, &sd->list_entry);\n    else {\n        le = context->dirs.Flink;\n        while (le != &context->dirs) {\n            send_dir* sd2 = CONTAINING_RECORD(le, send_dir, list_entry);\n\n            if (sd2->inode > sd->inode) {\n                InsertHeadList(sd2->list_entry.Blink, &sd->list_entry);\n\n                if (psd)\n                    *psd = sd;\n\n                return STATUS_SUCCESS;\n            }\n\n            le = le->Flink;\n        }\n\n        InsertTailList(&context->dirs, &sd->list_entry);\n    }\n\n    if (psd)\n        *psd = sd;\n\n    return STATUS_SUCCESS;\n}\n\nstatic __inline uint16_t find_path_len(send_dir* parent, uint16_t namelen) {\n    uint16_t len = namelen;\n\n    while (parent && parent->namelen > 0) {\n        len += parent->namelen + 1;\n        parent = parent->parent;\n    }\n\n    return len;\n}\n\nstatic void find_path(char* path, send_dir* parent, char* name, ULONG namelen) {\n    ULONG len = namelen;\n\n    RtlCopyMemory(path, name, namelen);\n\n    while (parent && parent->namelen > 0) {\n        RtlMoveMemory(path + parent->namelen + 1, path, len);\n        RtlCopyMemory(path, parent->name, parent->namelen);\n        path[parent->namelen] = '/';\n        len += parent->namelen + 1;\n\n        parent = parent->parent;\n    }\n}\n\nstatic void send_add_tlv_path(send_context* context, uint16_t type, send_dir* parent, char* name, uint16_t namelen) {\n    uint16_t len = find_path_len(parent, namelen);\n\n    send_add_tlv(context, type, NULL, len);\n\n    if (len > 0)\n        find_path((char*)&context->data[context->datalen - len], parent, name, namelen);\n}\n\nstatic NTSTATUS found_path(send_context* context, send_dir* parent, char* name, uint16_t namelen) {\n    ULONG pos = context->datalen;\n\n    if (context->lastinode.o) {\n        send_command(context, BTRFS_SEND_CMD_RENAME);\n\n        send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, context->root_dir, context->lastinode.o->tmpname, (uint16_t)strlen(context->lastinode.o->tmpname));\n\n        send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, parent, name, namelen);\n\n        send_command_finish(context, pos);\n    } else {\n        send_command(context, BTRFS_SEND_CMD_LINK);\n\n        send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, parent, name, namelen);\n\n        send_add_tlv(context, BTRFS_SEND_TLV_PATH_LINK, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);\n\n        send_command_finish(context, pos);\n    }\n\n    if (context->lastinode.o) {\n        uint16_t pathlen;\n\n        if (context->lastinode.o->sd) {\n            if (context->lastinode.o->sd->name)\n                ExFreePool(context->lastinode.o->sd->name);\n\n            context->lastinode.o->sd->name = ExAllocatePoolWithTag(PagedPool, namelen, ALLOC_TAG);\n            if (!context->lastinode.o->sd->name) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            RtlCopyMemory(context->lastinode.o->sd->name, name, namelen);\n            context->lastinode.o->sd->namelen = namelen;\n            context->lastinode.o->sd->parent = parent;\n        }\n\n        if (context->lastinode.path)\n            ExFreePool(context->lastinode.path);\n\n        pathlen = find_path_len(parent, namelen);\n        context->lastinode.path = ExAllocatePoolWithTag(PagedPool, pathlen + 1, ALLOC_TAG);\n        if (!context->lastinode.path) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        find_path(context->lastinode.path, parent, name, namelen);\n        context->lastinode.path[pathlen] = 0;\n\n        RemoveEntryList(&context->lastinode.o->list_entry);\n        ExFreePool(context->lastinode.o);\n\n        context->lastinode.o = NULL;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic void send_utimes_command_dir(send_context* context, send_dir* sd, BTRFS_TIME* atime, BTRFS_TIME* mtime, BTRFS_TIME* ctime) {\n    ULONG pos = context->datalen;\n\n    send_command(context, BTRFS_SEND_CMD_UTIMES);\n\n    send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, sd->parent, sd->name, sd->namelen);\n\n    send_add_tlv(context, BTRFS_SEND_TLV_ATIME, atime, sizeof(BTRFS_TIME));\n    send_add_tlv(context, BTRFS_SEND_TLV_MTIME, mtime, sizeof(BTRFS_TIME));\n    send_add_tlv(context, BTRFS_SEND_TLV_CTIME, ctime, sizeof(BTRFS_TIME));\n\n    send_command_finish(context, pos);\n}\n\nstatic NTSTATUS find_send_dir(send_context* context, uint64_t dir, uint64_t generation, send_dir** psd, bool* added_dummy) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    char name[64];\n\n    le = context->dirs.Flink;\n    while (le != &context->dirs) {\n        send_dir* sd2 = CONTAINING_RECORD(le, send_dir, list_entry);\n\n        if (sd2->inode > dir)\n            break;\n        else if (sd2->inode == dir) {\n            *psd = sd2;\n\n            if (added_dummy)\n                *added_dummy = false;\n\n            return STATUS_SUCCESS;\n        }\n\n        le = le->Flink;\n    }\n\n    if (dir == SUBVOL_ROOT_INODE) {\n        Status = send_add_dir(context, dir, NULL, NULL, 0, false, le, psd);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"send_add_dir returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (added_dummy)\n            *added_dummy = false;\n\n        return STATUS_SUCCESS;\n    }\n\n    if (context->parent) {\n        KEY searchkey;\n        traverse_ptr tp;\n\n        searchkey.obj_id = dir;\n        searchkey.obj_type = TYPE_INODE_REF; // directories should never have an extiref\n        searchkey.offset = 0xffffffffffffffff;\n\n        Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n            INODE_REF* ir = (INODE_REF*)tp.item->data;\n            send_dir* parent;\n\n            if (tp.item->size < sizeof(INODE_REF) || tp.item->size < offsetof(INODE_REF, name[0]) + ir->n) {\n                ERR(\"(%I64x,%x,%I64x) was truncated\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            if (tp.item->key.offset == SUBVOL_ROOT_INODE)\n                parent = context->root_dir;\n            else {\n                Status = find_send_dir(context, tp.item->key.offset, generation, &parent, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"find_send_dir returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n\n            Status = send_add_dir(context, dir, parent, ir->name, ir->n, true, NULL, psd);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"send_add_dir returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (added_dummy)\n                *added_dummy = false;\n\n            return STATUS_SUCCESS;\n        }\n    }\n\n    Status = get_orphan_name(context, dir, generation, name);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"get_orphan_name returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    Status = send_add_dir(context, dir, NULL, name, (uint16_t)strlen(name), true, le, psd);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"send_add_dir returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (added_dummy)\n        *added_dummy = true;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS send_inode_ref(send_context* context, traverse_ptr* tp, bool tree2) {\n    NTSTATUS Status;\n    uint64_t inode = tp ? tp->item->key.obj_id : 0, dir = tp ? tp->item->key.offset : 0;\n    LIST_ENTRY* le;\n    INODE_REF* ir;\n    uint16_t len;\n    send_dir* sd = NULL;\n    orphan* o2 = NULL;\n\n    if (inode == dir) // root\n        return STATUS_SUCCESS;\n\n    if (tp->item->size < sizeof(INODE_REF)) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,\n            tp->item->size, sizeof(INODE_REF));\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (dir != SUBVOL_ROOT_INODE) {\n        bool added_dummy;\n\n        Status = find_send_dir(context, dir, context->root->root_item.ctransid, &sd, &added_dummy);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_send_dir returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        // directory has higher inode number than file, so might need to be created\n        if (added_dummy) {\n            bool found = false;\n\n            le = context->orphans.Flink;\n            while (le != &context->orphans) {\n                o2 = CONTAINING_RECORD(le, orphan, list_entry);\n\n                if (o2->inode == dir) {\n                    found = true;\n                    break;\n                } else if (o2->inode > dir)\n                    break;\n\n                le = le->Flink;\n            }\n\n            if (!found) {\n                ULONG pos = context->datalen;\n\n                send_command(context, BTRFS_SEND_CMD_MKDIR);\n\n                send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, NULL, sd->name, sd->namelen);\n\n                send_add_tlv(context, BTRFS_SEND_TLV_INODE, &dir, sizeof(uint64_t));\n\n                send_command_finish(context, pos);\n\n                o2 = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG);\n                if (!o2) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                o2->inode = dir;\n                o2->dir = true;\n                memcpy(o2->tmpname, sd->name, sd->namelen);\n                o2->tmpname[sd->namelen] = 0;\n                o2->sd = sd;\n                add_orphan(context, o2);\n            }\n        }\n    } else\n        sd = context->root_dir;\n\n    len = tp->item->size;\n    ir = (INODE_REF*)tp->item->data;\n\n    while (len > 0) {\n        ref* r;\n\n        if (len < sizeof(INODE_REF) || len < offsetof(INODE_REF, name[0]) + ir->n) {\n            ERR(\"(%I64x,%x,%I64x) was truncated\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        r = ExAllocatePoolWithTag(PagedPool, offsetof(ref, name[0]) + ir->n, ALLOC_TAG);\n        if (!r) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        r->sd = sd;\n        r->namelen = ir->n;\n        RtlCopyMemory(r->name, ir->name, ir->n);\n\n        InsertTailList(tree2 ? &context->lastinode.oldrefs : &context->lastinode.refs, &r->list_entry);\n\n        len -= (uint16_t)offsetof(INODE_REF, name[0]) + ir->n;\n        ir = (INODE_REF*)&ir->name[ir->n];\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS send_inode_extref(send_context* context, traverse_ptr* tp, bool tree2) {\n    INODE_EXTREF* ier;\n    uint16_t len;\n\n    if (tp->item->size < sizeof(INODE_EXTREF)) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,\n            tp->item->size, sizeof(INODE_EXTREF));\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    len = tp->item->size;\n    ier = (INODE_EXTREF*)tp->item->data;\n\n    while (len > 0) {\n        NTSTATUS Status;\n        send_dir* sd = NULL;\n        orphan* o2 = NULL;\n        ref* r;\n\n        if (len < sizeof(INODE_EXTREF) || len < offsetof(INODE_EXTREF, name[0]) + ier->n) {\n            ERR(\"(%I64x,%x,%I64x) was truncated\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (ier->dir != SUBVOL_ROOT_INODE) {\n            LIST_ENTRY* le;\n            bool added_dummy;\n\n            Status = find_send_dir(context, ier->dir, context->root->root_item.ctransid, &sd, &added_dummy);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"find_send_dir returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            // directory has higher inode number than file, so might need to be created\n            if (added_dummy) {\n                bool found = false;\n\n                le = context->orphans.Flink;\n                while (le != &context->orphans) {\n                    o2 = CONTAINING_RECORD(le, orphan, list_entry);\n\n                    if (o2->inode == ier->dir) {\n                        found = true;\n                        break;\n                    } else if (o2->inode > ier->dir)\n                        break;\n\n                    le = le->Flink;\n                }\n\n                if (!found) {\n                    ULONG pos = context->datalen;\n\n                    send_command(context, BTRFS_SEND_CMD_MKDIR);\n\n                    send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, NULL, sd->name, sd->namelen);\n                    send_add_tlv(context, BTRFS_SEND_TLV_INODE, &ier->dir, sizeof(uint64_t));\n\n                    send_command_finish(context, pos);\n\n                    o2 = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG);\n                    if (!o2) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    o2->inode = ier->dir;\n                    o2->dir = true;\n                    memcpy(o2->tmpname, sd->name, sd->namelen);\n                    o2->tmpname[sd->namelen] = 0;\n                    o2->sd = sd;\n                    add_orphan(context, o2);\n                }\n            }\n        } else\n            sd = context->root_dir;\n\n        r = ExAllocatePoolWithTag(PagedPool, offsetof(ref, name[0]) + ier->n, ALLOC_TAG);\n        if (!r) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        r->sd = sd;\n        r->namelen = ier->n;\n        RtlCopyMemory(r->name, ier->name, ier->n);\n\n        InsertTailList(tree2 ? &context->lastinode.oldrefs : &context->lastinode.refs, &r->list_entry);\n\n        len -= (uint16_t)offsetof(INODE_EXTREF, name[0]) + ier->n;\n        ier = (INODE_EXTREF*)&ier->name[ier->n];\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic void send_subvol_header(send_context* context, root* r, file_ref* fr) {\n    ULONG pos = context->datalen;\n\n    send_command(context, context->parent ? BTRFS_SEND_CMD_SNAPSHOT : BTRFS_SEND_CMD_SUBVOL);\n\n    send_add_tlv(context, BTRFS_SEND_TLV_PATH, fr->dc->utf8.Buffer, fr->dc->utf8.Length);\n\n    send_add_tlv(context, BTRFS_SEND_TLV_UUID, r->root_item.rtransid == 0 ? &r->root_item.uuid : &r->root_item.received_uuid, sizeof(BTRFS_UUID));\n    send_add_tlv(context, BTRFS_SEND_TLV_TRANSID, &r->root_item.ctransid, sizeof(uint64_t));\n\n    if (context->parent) {\n        send_add_tlv(context, BTRFS_SEND_TLV_CLONE_UUID,\n                     context->parent->root_item.rtransid == 0 ? &context->parent->root_item.uuid : &context->parent->root_item.received_uuid, sizeof(BTRFS_UUID));\n        send_add_tlv(context, BTRFS_SEND_TLV_CLONE_CTRANSID, &context->parent->root_item.ctransid, sizeof(uint64_t));\n    }\n\n    send_command_finish(context, pos);\n}\n\nstatic void send_chown_command(send_context* context, char* path, uint64_t uid, uint64_t gid) {\n    ULONG pos = context->datalen;\n\n    send_command(context, BTRFS_SEND_CMD_CHOWN);\n\n    send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0);\n    send_add_tlv(context, BTRFS_SEND_TLV_UID, &uid, sizeof(uint64_t));\n    send_add_tlv(context, BTRFS_SEND_TLV_GID, &gid, sizeof(uint64_t));\n\n    send_command_finish(context, pos);\n}\n\nstatic void send_chmod_command(send_context* context, char* path, uint64_t mode) {\n    ULONG pos = context->datalen;\n\n    send_command(context, BTRFS_SEND_CMD_CHMOD);\n\n    mode &= 07777;\n\n    send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0);\n    send_add_tlv(context, BTRFS_SEND_TLV_MODE, &mode, sizeof(uint64_t));\n\n    send_command_finish(context, pos);\n}\n\nstatic void send_utimes_command(send_context* context, char* path, BTRFS_TIME* atime, BTRFS_TIME* mtime, BTRFS_TIME* ctime) {\n    ULONG pos = context->datalen;\n\n    send_command(context, BTRFS_SEND_CMD_UTIMES);\n\n    send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0);\n    send_add_tlv(context, BTRFS_SEND_TLV_ATIME, atime, sizeof(BTRFS_TIME));\n    send_add_tlv(context, BTRFS_SEND_TLV_MTIME, mtime, sizeof(BTRFS_TIME));\n    send_add_tlv(context, BTRFS_SEND_TLV_CTIME, ctime, sizeof(BTRFS_TIME));\n\n    send_command_finish(context, pos);\n}\n\nstatic void send_truncate_command(send_context* context, char* path, uint64_t size) {\n    ULONG pos = context->datalen;\n\n    send_command(context, BTRFS_SEND_CMD_TRUNCATE);\n\n    send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, path ? (uint16_t)strlen(path) : 0);\n    send_add_tlv(context, BTRFS_SEND_TLV_SIZE, &size, sizeof(uint64_t));\n\n    send_command_finish(context, pos);\n}\n\nstatic NTSTATUS send_unlink_command(send_context* context, send_dir* parent, uint16_t namelen, char* name) {\n    ULONG pos = context->datalen;\n    uint16_t pathlen;\n\n    send_command(context, BTRFS_SEND_CMD_UNLINK);\n\n    pathlen = find_path_len(parent, namelen);\n    send_add_tlv(context, BTRFS_SEND_TLV_PATH, NULL, pathlen);\n\n    find_path((char*)&context->data[context->datalen - pathlen], parent, name, namelen);\n\n    send_command_finish(context, pos);\n\n    return STATUS_SUCCESS;\n}\n\nstatic void send_rmdir_command(send_context* context, uint16_t pathlen, char* path) {\n    ULONG pos = context->datalen;\n\n    send_command(context, BTRFS_SEND_CMD_RMDIR);\n    send_add_tlv(context, BTRFS_SEND_TLV_PATH, path, pathlen);\n    send_command_finish(context, pos);\n}\n\nstatic NTSTATUS get_dir_last_child(send_context* context, uint64_t* last_inode) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n\n    *last_inode = 0;\n\n    searchkey.obj_id = context->lastinode.inode;\n    searchkey.obj_type = TYPE_DIR_INDEX;\n    searchkey.offset = 2;\n\n    Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    do {\n        traverse_ptr next_tp;\n\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n            DIR_ITEM* di = (DIR_ITEM*)tp.item->data;\n\n            if (tp.item->size < sizeof(DIR_ITEM) || tp.item->size < offsetof(DIR_ITEM, name[0]) + di->m + di->n) {\n                ERR(\"(%I64x,%x,%I64x) was truncated\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            if (di->key.obj_type == TYPE_INODE_ITEM)\n                *last_inode = max(*last_inode, di->key.obj_id);\n        } else\n            break;\n\n        if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL))\n            tp = next_tp;\n        else\n            break;\n    } while (true);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS add_pending_rmdir(send_context* context, uint64_t last_inode) {\n    pending_rmdir* pr;\n    LIST_ENTRY* le;\n\n    pr = ExAllocatePoolWithTag(PagedPool, sizeof(pending_rmdir), ALLOC_TAG);\n    if (!pr) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    pr->sd = context->lastinode.sd;\n    pr->last_child_inode = last_inode;\n\n    le = context->pending_rmdirs.Flink;\n    while (le != &context->pending_rmdirs) {\n        pending_rmdir* pr2 = CONTAINING_RECORD(le, pending_rmdir, list_entry);\n\n        if (pr2->last_child_inode > pr->last_child_inode) {\n            InsertHeadList(pr2->list_entry.Blink, &pr->list_entry);\n            return STATUS_SUCCESS;\n        }\n\n        le = le->Flink;\n    }\n\n    InsertTailList(&context->pending_rmdirs, &pr->list_entry);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS look_for_collision(send_context* context, send_dir* sd, char* name, ULONG namelen, uint64_t* inode, bool* dir) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    DIR_ITEM* di;\n    uint16_t len;\n\n    searchkey.obj_id = sd->inode;\n    searchkey.obj_type = TYPE_DIR_ITEM;\n    searchkey.offset = calc_crc32c(0xfffffffe, (uint8_t*)name, namelen);\n\n    Status = find_item(context->Vcb, context->parent, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (keycmp(tp.item->key, searchkey))\n        return STATUS_SUCCESS;\n\n    di = (DIR_ITEM*)tp.item->data;\n    len = tp.item->size;\n\n    do {\n        if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) {\n            ERR(\"(%I64x,%x,%I64x) was truncated\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (di->n == namelen && RtlCompareMemory(di->name, name, namelen) == namelen) {\n            *inode = di->key.obj_type == TYPE_INODE_ITEM ? di->key.obj_id : 0;\n            *dir = di->type == BTRFS_TYPE_DIRECTORY ? true: false;\n            return STATUS_OBJECT_NAME_COLLISION;\n        }\n\n        di = (DIR_ITEM*)&di->name[di->m + di->n];\n        len -= (uint16_t)offsetof(DIR_ITEM, name[0]) + di->m + di->n;\n    } while (len > 0);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS make_file_orphan(send_context* context, uint64_t inode, bool dir, uint64_t generation, ref* r) {\n    NTSTATUS Status;\n    ULONG pos = context->datalen;\n    send_dir* sd = NULL;\n    orphan* o;\n    LIST_ENTRY* le;\n    char name[64];\n\n    if (!dir) {\n        deleted_child* dc;\n\n        dc = ExAllocatePoolWithTag(PagedPool, offsetof(deleted_child, name[0]) + r->namelen, ALLOC_TAG);\n        if (!dc) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        dc->namelen = r->namelen;\n        RtlCopyMemory(dc->name, r->name, r->namelen);\n        InsertTailList(&r->sd->deleted_children, &dc->list_entry);\n    }\n\n    le = context->orphans.Flink;\n    while (le != &context->orphans) {\n        orphan* o2 = CONTAINING_RECORD(le, orphan, list_entry);\n\n        if (o2->inode == inode) {\n            send_command(context, BTRFS_SEND_CMD_UNLINK);\n\n            send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, r->sd, r->name, r->namelen);\n\n            send_command_finish(context, pos);\n\n            return STATUS_SUCCESS;\n        } else if (o2->inode > inode)\n            break;\n\n        le = le->Flink;\n    }\n\n    Status = get_orphan_name(context, inode, generation, name);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"get_orphan_name returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    if (dir) {\n        Status = find_send_dir(context, inode, generation, &sd, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_send_dir returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        sd->dummy = true;\n\n        send_command(context, BTRFS_SEND_CMD_RENAME);\n\n        send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, r->sd, r->name, r->namelen);\n        send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, context->root_dir, name, (uint16_t)strlen(name));\n\n        send_command_finish(context, pos);\n\n        if (sd->name)\n            ExFreePool(sd->name);\n\n        sd->namelen = (uint16_t)strlen(name);\n        sd->name = ExAllocatePoolWithTag(PagedPool, sd->namelen, ALLOC_TAG);\n        if (!sd->name) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(sd->name, name, sd->namelen);\n        sd->parent = context->root_dir;\n    } else {\n        send_command(context, BTRFS_SEND_CMD_RENAME);\n\n        send_add_tlv_path(context, BTRFS_SEND_TLV_PATH, r->sd, r->name, r->namelen);\n\n        send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, context->root_dir, name, (uint16_t)strlen(name));\n\n        send_command_finish(context, pos);\n    }\n\n    o = ExAllocatePoolWithTag(PagedPool, sizeof(orphan), ALLOC_TAG);\n    if (!o) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    o->inode = inode;\n    o->dir = true;\n    strcpy(o->tmpname, name);\n    o->sd = sd;\n    add_orphan(context, o);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS flush_refs(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    ref *nameref = NULL, *nameref2 = NULL;\n\n    if (context->lastinode.mode & __S_IFDIR) { // directory\n        ref* r = IsListEmpty(&context->lastinode.refs) ? NULL : CONTAINING_RECORD(context->lastinode.refs.Flink, ref, list_entry);\n        ref* or = IsListEmpty(&context->lastinode.oldrefs) ? NULL : CONTAINING_RECORD(context->lastinode.oldrefs.Flink, ref, list_entry);\n\n        if (or && !context->lastinode.o) {\n            ULONG len = find_path_len(or->sd, or->namelen);\n\n            context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG);\n            if (!context->lastinode.path) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            find_path(context->lastinode.path, or->sd, or->name, or->namelen);\n            context->lastinode.path[len] = 0;\n\n            if (!context->lastinode.sd) {\n                Status = find_send_dir(context, context->lastinode.inode, context->lastinode.gen, &context->lastinode.sd, false);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"find_send_dir returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n        }\n\n        if (r && or) {\n            uint64_t inode;\n            bool dir;\n\n            Status = look_for_collision(context, r->sd, r->name, r->namelen, &inode, &dir);\n            if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_COLLISION) {\n                ERR(\"look_for_collision returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (Status == STATUS_OBJECT_NAME_COLLISION && inode > context->lastinode.inode) {\n                Status = make_file_orphan(context, inode, dir, context->parent->root_item.ctransid, r);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"make_file_orphan returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n\n            if (context->lastinode.o) {\n                Status = found_path(context, r->sd, r->name, r->namelen);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"found_path returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                if (!r->sd->dummy)\n                    send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime);\n            } else if (r->sd != or->sd || r->namelen != or->namelen || RtlCompareMemory(r->name, or->name, r->namelen) != r->namelen) { // moved or renamed\n                ULONG pos = context->datalen, len;\n\n                send_command(context, BTRFS_SEND_CMD_RENAME);\n\n                send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, (uint16_t)strlen(context->lastinode.path));\n\n                send_add_tlv_path(context, BTRFS_SEND_TLV_PATH_TO, r->sd, r->name, r->namelen);\n\n                send_command_finish(context, pos);\n\n                if (!r->sd->dummy)\n                    send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime);\n\n                if (context->lastinode.sd->name)\n                    ExFreePool(context->lastinode.sd->name);\n\n                context->lastinode.sd->name = ExAllocatePoolWithTag(PagedPool, r->namelen, ALLOC_TAG);\n                if (!context->lastinode.sd->name) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(context->lastinode.sd->name, r->name, r->namelen);\n                context->lastinode.sd->parent = r->sd;\n\n                if (context->lastinode.path)\n                    ExFreePool(context->lastinode.path);\n\n                len = find_path_len(r->sd, r->namelen);\n                context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG);\n                if (!context->lastinode.path) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                find_path(context->lastinode.path, r->sd, r->name, r->namelen);\n                context->lastinode.path[len] = 0;\n            }\n        } else if (r && !or) { // new\n            Status = found_path(context, r->sd, r->name, r->namelen);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"found_path returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (!r->sd->dummy)\n                send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime);\n        } else { // deleted\n            uint64_t last_inode;\n\n            Status = get_dir_last_child(context, &last_inode);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"get_dir_last_child returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (last_inode <= context->lastinode.inode) {\n                send_rmdir_command(context, (uint16_t)strlen(context->lastinode.path), context->lastinode.path);\n\n                if (!or->sd->dummy)\n                    send_utimes_command_dir(context, or->sd, &or->sd->atime, &or->sd->mtime, &or->sd->ctime);\n            } else {\n                char name[64];\n                ULONG pos = context->datalen;\n\n                Status = get_orphan_name(context, context->lastinode.inode, context->lastinode.gen, name);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"get_orphan_name returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                send_command(context, BTRFS_SEND_CMD_RENAME);\n                send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, (uint16_t)strlen(context->lastinode.path));\n                send_add_tlv(context, BTRFS_SEND_TLV_PATH_TO, name, (uint16_t)strlen(name));\n                send_command_finish(context, pos);\n\n                if (context->lastinode.sd->name)\n                    ExFreePool(context->lastinode.sd->name);\n\n                context->lastinode.sd->name = ExAllocatePoolWithTag(PagedPool, strlen(name), ALLOC_TAG);\n                if (!context->lastinode.sd->name) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(context->lastinode.sd->name, name, strlen(name));\n                context->lastinode.sd->namelen = (uint16_t)strlen(name);\n                context->lastinode.sd->dummy = true;\n                context->lastinode.sd->parent = NULL;\n\n                send_utimes_command(context, NULL, &context->root_dir->atime, &context->root_dir->mtime, &context->root_dir->ctime);\n\n                Status = add_pending_rmdir(context, last_inode);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"add_pending_rmdir returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n        }\n\n        while (!IsListEmpty(&context->lastinode.refs)) {\n            r = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.refs), ref, list_entry);\n            ExFreePool(r);\n        }\n\n        while (!IsListEmpty(&context->lastinode.oldrefs)) {\n            or = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldrefs), ref, list_entry);\n            ExFreePool(or);\n        }\n\n        return STATUS_SUCCESS;\n    } else {\n        if (!IsListEmpty(&context->lastinode.oldrefs)) {\n            ref* or = CONTAINING_RECORD(context->lastinode.oldrefs.Flink, ref, list_entry);\n            ULONG len = find_path_len(or->sd, or->namelen);\n\n            context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG);\n            if (!context->lastinode.path) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            find_path(context->lastinode.path, or->sd, or->name, or->namelen);\n            context->lastinode.path[len] = 0;\n            nameref = or;\n        }\n\n        // remove unchanged refs\n        le = context->lastinode.oldrefs.Flink;\n        while (le != &context->lastinode.oldrefs) {\n            ref* or = CONTAINING_RECORD(le, ref, list_entry);\n            LIST_ENTRY* le2;\n            bool matched = false;\n\n            le2 = context->lastinode.refs.Flink;\n            while (le2 != &context->lastinode.refs) {\n                ref* r = CONTAINING_RECORD(le2, ref, list_entry);\n\n                if (r->sd == or->sd && r->namelen == or->namelen && RtlCompareMemory(r->name, or->name, r->namelen) == r->namelen) {\n                    RemoveEntryList(&r->list_entry);\n                    ExFreePool(r);\n                    matched = true;\n                    break;\n                }\n\n                le2 = le2->Flink;\n            }\n\n            if (matched) {\n                le = le->Flink;\n                RemoveEntryList(&or->list_entry);\n                ExFreePool(or);\n                continue;\n            }\n\n            le = le->Flink;\n        }\n\n        while (!IsListEmpty(&context->lastinode.refs)) {\n            ref* r = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.refs), ref, list_entry);\n            uint64_t inode;\n            bool dir;\n\n            if (context->parent) {\n                Status = look_for_collision(context, r->sd, r->name, r->namelen, &inode, &dir);\n                if (!NT_SUCCESS(Status) && Status != STATUS_OBJECT_NAME_COLLISION) {\n                    ERR(\"look_for_collision returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                if (Status == STATUS_OBJECT_NAME_COLLISION && inode > context->lastinode.inode) {\n                    Status = make_file_orphan(context, inode, dir, context->lastinode.gen, r);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"make_file_orphan returned %08lx\\n\", Status);\n                        return Status;\n                    }\n                }\n            }\n\n            if (context->datalen > SEND_BUFFER_LENGTH) {\n                Status = wait_for_flush(context, tp1, tp2);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"wait_for_flush returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                if (context->send->cancelling)\n                    return STATUS_SUCCESS;\n            }\n\n            Status = found_path(context, r->sd, r->name, r->namelen);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"found_path returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (!r->sd->dummy)\n                send_utimes_command_dir(context, r->sd, &r->sd->atime, &r->sd->mtime, &r->sd->ctime);\n\n            if (nameref && !nameref2)\n                nameref2 = r;\n            else\n                ExFreePool(r);\n        }\n\n        while (!IsListEmpty(&context->lastinode.oldrefs)) {\n            ref* or = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldrefs), ref, list_entry);\n            bool deleted = false;\n\n            le = or->sd->deleted_children.Flink;\n            while (le != &or->sd->deleted_children) {\n                deleted_child* dc = CONTAINING_RECORD(le, deleted_child, list_entry);\n\n                if (dc->namelen == or->namelen && RtlCompareMemory(dc->name, or->name, or->namelen) == or->namelen) {\n                    RemoveEntryList(&dc->list_entry);\n                    ExFreePool(dc);\n                    deleted = true;\n                    break;\n                }\n\n                le = le->Flink;\n            }\n\n            if (!deleted) {\n                if (context->datalen > SEND_BUFFER_LENGTH) {\n                    Status = wait_for_flush(context, tp1, tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"wait_for_flush returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    if (context->send->cancelling)\n                        return STATUS_SUCCESS;\n                }\n\n                Status = send_unlink_command(context, or->sd, or->namelen, or->name);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"send_unlink_command returned %08lx\\n\", Status);\n                    return Status;\n                }\n\n                if (!or->sd->dummy)\n                    send_utimes_command_dir(context, or->sd, &or->sd->atime, &or->sd->mtime, &or->sd->ctime);\n            }\n\n            if (or == nameref && nameref2) {\n                uint16_t len = find_path_len(nameref2->sd, nameref2->namelen);\n\n                if (context->lastinode.path)\n                    ExFreePool(context->lastinode.path);\n\n                context->lastinode.path = ExAllocatePoolWithTag(PagedPool, len + 1, ALLOC_TAG);\n                if (!context->lastinode.path) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                find_path(context->lastinode.path, nameref2->sd, nameref2->name, nameref2->namelen);\n                context->lastinode.path[len] = 0;\n\n                ExFreePool(nameref2);\n            }\n\n            ExFreePool(or);\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS wait_for_flush(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) {\n    NTSTATUS Status;\n    KEY key1, key2;\n\n    if (tp1)\n        key1 = tp1->item->key;\n\n    if (tp2)\n        key2 = tp2->item->key;\n\n    ExReleaseResourceLite(&context->Vcb->tree_lock);\n\n    KeClearEvent(&context->send->cleared_event);\n    KeSetEvent(&context->buffer_event, 0, true);\n    KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL);\n\n    ExAcquireResourceSharedLite(&context->Vcb->tree_lock, true);\n\n    if (context->send->cancelling)\n        return STATUS_SUCCESS;\n\n    if (tp1) {\n        Status = find_item(context->Vcb, context->root, tp1, &key1, false, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (keycmp(tp1->item->key, key1)) {\n            ERR(\"readonly subvolume changed\\n\");\n            return STATUS_INTERNAL_ERROR;\n        }\n    }\n\n    if (tp2) {\n        Status = find_item(context->Vcb, context->parent, tp2, &key2, false, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (keycmp(tp2->item->key, key2)) {\n            ERR(\"readonly subvolume changed\\n\");\n            return STATUS_INTERNAL_ERROR;\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS add_ext_holes(device_extension* Vcb, LIST_ENTRY* exts, uint64_t size) {\n    uint64_t lastoff = 0;\n    LIST_ENTRY* le;\n\n    le = exts->Flink;\n    while (le != exts) {\n        send_ext* ext = CONTAINING_RECORD(le, send_ext, list_entry);\n\n        if (ext->offset > lastoff) {\n            send_ext* ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data.data) + sizeof(EXTENT_DATA2), ALLOC_TAG);\n            EXTENT_DATA2* ed2;\n\n            if (!ext2) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            ed2 = (EXTENT_DATA2*)ext2->data.data;\n\n            ext2->offset = lastoff;\n            ext2->datalen = offsetof(EXTENT_DATA, data) + sizeof(EXTENT_DATA2);\n            ext2->data.decoded_size = ed2->num_bytes = ext->offset - lastoff;\n            ext2->data.type = EXTENT_TYPE_REGULAR;\n            ed2->address = ed2->size = ed2->offset = 0;\n\n            InsertHeadList(le->Blink, &ext2->list_entry);\n        }\n\n        if (ext->data.type == EXTENT_TYPE_INLINE)\n            lastoff = ext->offset + ext->data.decoded_size;\n        else {\n            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ext->data.data;\n            lastoff = ext->offset + ed2->num_bytes;\n        }\n\n        le = le->Flink;\n    }\n\n    if (size > lastoff) {\n        send_ext* ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data.data) + sizeof(EXTENT_DATA2), ALLOC_TAG);\n        EXTENT_DATA2* ed2;\n\n        if (!ext2) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        ed2 = (EXTENT_DATA2*)ext2->data.data;\n\n        ext2->offset = lastoff;\n        ext2->datalen = offsetof(EXTENT_DATA, data) + sizeof(EXTENT_DATA2);\n        ext2->data.decoded_size = ed2->num_bytes = sector_align(size - lastoff, Vcb->superblock.sector_size);\n        ext2->data.type = EXTENT_TYPE_REGULAR;\n        ed2->address = ed2->size = ed2->offset = 0;\n\n        InsertTailList(exts, &ext2->list_entry);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS divide_ext(send_ext* ext, uint64_t len, bool trunc) {\n    send_ext* ext2;\n    EXTENT_DATA2 *ed2a, *ed2b;\n\n    if (ext->data.type == EXTENT_TYPE_INLINE) {\n        if (!trunc) {\n            ext2 = ExAllocatePoolWithTag(PagedPool, (ULONG)(offsetof(send_ext, data.data) + ext->data.decoded_size - len), ALLOC_TAG);\n\n            if (!ext2) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            ext2->offset = ext->offset + len;\n            ext2->datalen = (ULONG)(ext->data.decoded_size - len);\n            ext2->data.decoded_size = ext->data.decoded_size - len;\n            ext2->data.compression = ext->data.compression;\n            ext2->data.encryption = ext->data.encryption;\n            ext2->data.encoding = ext->data.encoding;\n            ext2->data.type = ext->data.type;\n            RtlCopyMemory(ext2->data.data, ext->data.data + len, (ULONG)(ext->data.decoded_size - len));\n\n            InsertHeadList(&ext->list_entry, &ext2->list_entry);\n        }\n\n        ext->data.decoded_size = len;\n\n        return STATUS_SUCCESS;\n    }\n\n    ed2a = (EXTENT_DATA2*)ext->data.data;\n\n    if (!trunc) {\n        ext2 = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data.data) + sizeof(EXTENT_DATA2), ALLOC_TAG);\n\n        if (!ext2) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        ed2b = (EXTENT_DATA2*)ext2->data.data;\n\n        ext2->offset = ext->offset + len;\n        ext2->datalen = offsetof(EXTENT_DATA, data) + sizeof(EXTENT_DATA2);\n\n        ext2->data.compression = ext->data.compression;\n        ext2->data.encryption = ext->data.encryption;\n        ext2->data.encoding = ext->data.encoding;\n        ext2->data.type = ext->data.type;\n        ed2b->num_bytes = ed2a->num_bytes - len;\n\n        if (ed2a->size == 0) {\n            ext2->data.decoded_size = ed2b->num_bytes;\n            ext->data.decoded_size = len;\n\n            ed2b->address = ed2b->size = ed2b->offset = 0;\n        } else {\n            ext2->data.decoded_size = ext->data.decoded_size;\n\n            ed2b->address = ed2a->address;\n            ed2b->size = ed2a->size;\n            ed2b->offset = ed2a->offset + len;\n        }\n\n        InsertHeadList(&ext->list_entry, &ext2->list_entry);\n    }\n\n    ed2a->num_bytes = len;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS sync_ext_cutoff_points(send_context* context) {\n    NTSTATUS Status;\n    send_ext *ext1, *ext2;\n\n    ext1 = CONTAINING_RECORD(context->lastinode.exts.Flink, send_ext, list_entry);\n    ext2 = CONTAINING_RECORD(context->lastinode.oldexts.Flink, send_ext, list_entry);\n\n    do {\n        uint64_t len1, len2;\n        EXTENT_DATA2 *ed2a, *ed2b;\n\n        ed2a = ext1->data.type == EXTENT_TYPE_INLINE ? NULL : (EXTENT_DATA2*)ext1->data.data;\n        ed2b = ext2->data.type == EXTENT_TYPE_INLINE ? NULL : (EXTENT_DATA2*)ext2->data.data;\n\n        len1 = ed2a ? ed2a->num_bytes : ext1->data.decoded_size;\n        len2 = ed2b ? ed2b->num_bytes : ext2->data.decoded_size;\n\n        if (len1 < len2) {\n            Status = divide_ext(ext2, len1, false);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"divide_ext returned %08lx\\n\", Status);\n                return Status;\n            }\n        } else if (len2 < len1) {\n            Status = divide_ext(ext1, len2, false);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"divide_ext returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        if (ext1->list_entry.Flink == &context->lastinode.exts || ext2->list_entry.Flink == &context->lastinode.oldexts)\n            break;\n\n        ext1 = CONTAINING_RECORD(ext1->list_entry.Flink, send_ext, list_entry);\n        ext2 = CONTAINING_RECORD(ext2->list_entry.Flink, send_ext, list_entry);\n    } while (true);\n\n    ext1 = CONTAINING_RECORD(context->lastinode.exts.Blink, send_ext, list_entry);\n    ext2 = CONTAINING_RECORD(context->lastinode.oldexts.Blink, send_ext, list_entry);\n\n    Status = divide_ext(ext1, context->lastinode.size - ext1->offset, true);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"divide_ext returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    Status = divide_ext(ext2, context->lastinode.size - ext2->offset, true);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"divide_ext returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic bool send_add_tlv_clone_path(send_context* context, root* r, uint64_t inode) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    uint16_t len = 0;\n    uint64_t num;\n    uint8_t* ptr;\n\n    num = inode;\n\n    while (num != SUBVOL_ROOT_INODE) {\n        searchkey.obj_id = num;\n        searchkey.obj_type = TYPE_INODE_EXTREF;\n        searchkey.offset = 0xffffffffffffffff;\n\n        Status = find_item(context->Vcb, r, &tp, &searchkey, false, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            return false;\n        }\n\n        if (tp.item->key.obj_id != searchkey.obj_id || (tp.item->key.obj_type != TYPE_INODE_REF && tp.item->key.obj_type != TYPE_INODE_EXTREF)) {\n            ERR(\"could not find INODE_REF for inode %I64x\\n\", searchkey.obj_id);\n            return false;\n        }\n\n        if (len > 0)\n            len++;\n\n        if (tp.item->key.obj_type == TYPE_INODE_REF) {\n            INODE_REF* ir = (INODE_REF*)tp.item->data;\n\n            if (tp.item->size < sizeof(INODE_REF) || tp.item->size < offsetof(INODE_REF, name[0]) + ir->n) {\n                ERR(\"(%I64x,%x,%I64x) was truncated\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n                return false;\n            }\n\n            len += ir->n;\n            num = tp.item->key.offset;\n        } else {\n            INODE_EXTREF* ier = (INODE_EXTREF*)tp.item->data;\n\n            if (tp.item->size < sizeof(INODE_EXTREF) || tp.item->size < offsetof(INODE_EXTREF, name[0]) + ier->n) {\n                ERR(\"(%I64x,%x,%I64x) was truncated\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n                return false;\n            }\n\n            len += ier->n;\n            num = ier->dir;\n        }\n    }\n\n    send_add_tlv(context, BTRFS_SEND_TLV_CLONE_PATH, NULL, len);\n    ptr = &context->data[context->datalen];\n\n    num = inode;\n\n    while (num != SUBVOL_ROOT_INODE) {\n        searchkey.obj_id = num;\n        searchkey.obj_type = TYPE_INODE_EXTREF;\n        searchkey.offset = 0xffffffffffffffff;\n\n        Status = find_item(context->Vcb, r, &tp, &searchkey, false, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            return false;\n        }\n\n        if (tp.item->key.obj_id != searchkey.obj_id || (tp.item->key.obj_type != TYPE_INODE_REF && tp.item->key.obj_type != TYPE_INODE_EXTREF)) {\n            ERR(\"could not find INODE_REF for inode %I64x\\n\", searchkey.obj_id);\n            return false;\n        }\n\n        if (num != inode) {\n            ptr--;\n            *ptr = '/';\n        }\n\n        if (tp.item->key.obj_type == TYPE_INODE_REF) {\n            INODE_REF* ir = (INODE_REF*)tp.item->data;\n\n            RtlCopyMemory(ptr - ir->n, ir->name, ir->n);\n            ptr -= ir->n;\n            num = tp.item->key.offset;\n        } else {\n            INODE_EXTREF* ier = (INODE_EXTREF*)tp.item->data;\n\n            RtlCopyMemory(ptr - ier->n, ier->name, ier->n);\n            ptr -= ier->n;\n            num = ier->dir;\n        }\n    }\n\n    return true;\n}\n\nstatic bool try_clone_edr(send_context* context, send_ext* se, EXTENT_DATA_REF* edr) {\n    NTSTATUS Status;\n    root* r = NULL;\n    KEY searchkey;\n    traverse_ptr tp;\n    EXTENT_DATA2* seed2 = (EXTENT_DATA2*)se->data.data;\n\n    if (context->parent && edr->root == context->parent->id)\n        r = context->parent;\n\n    if (!r && context->num_clones > 0) {\n        ULONG i;\n\n        for (i = 0; i < context->num_clones; i++) {\n            if (context->clones[i]->id == edr->root && context->clones[i] != context->root) {\n                r = context->clones[i];\n                break;\n            }\n        }\n    }\n\n    if (!r)\n        return false;\n\n    searchkey.obj_id = edr->objid;\n    searchkey.obj_type = TYPE_EXTENT_DATA;\n    searchkey.offset = 0;\n\n    Status = find_item(context->Vcb, r, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return false;\n    }\n\n    while (true) {\n        traverse_ptr next_tp;\n\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n            if (tp.item->size < sizeof(EXTENT_DATA))\n                ERR(\"(%I64x,%x,%I64x) has size %u, not at least %Iu as expected\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA));\n            else {\n                EXTENT_DATA* ed = (EXTENT_DATA*)tp.item->data;\n\n                if (ed->type == EXTENT_TYPE_REGULAR) {\n                    if (tp.item->size < offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2))\n                        ERR(\"(%I64x,%x,%I64x) has size %u, not %Iu as expected\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset,\n                            tp.item->size, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2));\n                    else {\n                        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n\n                        if (ed2->address == seed2->address && ed2->size == seed2->size && seed2->offset <= ed2->offset && seed2->offset + seed2->num_bytes >= ed2->offset + ed2->num_bytes) {\n                            uint64_t clone_offset = tp.item->key.offset + ed2->offset - seed2->offset;\n                            uint64_t clone_len = min(context->lastinode.size - se->offset, ed2->num_bytes);\n\n                            if ((clone_offset & (context->Vcb->superblock.sector_size - 1)) == 0 && (clone_len & (context->Vcb->superblock.sector_size - 1)) == 0) {\n                                ULONG pos = context->datalen;\n\n                                send_command(context, BTRFS_SEND_CMD_CLONE);\n\n                                send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &se->offset, sizeof(uint64_t));\n                                send_add_tlv(context, BTRFS_SEND_TLV_CLONE_LENGTH, &clone_len, sizeof(uint64_t));\n                                send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);\n                                send_add_tlv(context, BTRFS_SEND_TLV_CLONE_UUID, r->root_item.rtransid == 0 ? &r->root_item.uuid : &r->root_item.received_uuid, sizeof(BTRFS_UUID));\n                                send_add_tlv(context, BTRFS_SEND_TLV_CLONE_CTRANSID, &r->root_item.ctransid, sizeof(uint64_t));\n\n                                if (!send_add_tlv_clone_path(context, r, tp.item->key.obj_id))\n                                    context->datalen = pos;\n                                else {\n                                    send_add_tlv(context, BTRFS_SEND_TLV_CLONE_OFFSET, &clone_offset, sizeof(uint64_t));\n\n                                    send_command_finish(context, pos);\n\n                                    return true;\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        } else if (tp.item->key.obj_id > searchkey.obj_id || (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type > searchkey.obj_type))\n            break;\n\n        if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL))\n            tp = next_tp;\n        else\n            break;\n    }\n\n    return false;\n}\n\nstatic bool try_clone(send_context* context, send_ext* se) {\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp;\n    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)se->data.data;\n    EXTENT_ITEM* ei;\n    uint64_t rc = 0;\n\n    searchkey.obj_id = ed2->address;\n    searchkey.obj_type = TYPE_EXTENT_ITEM;\n    searchkey.offset = ed2->size;\n\n    Status = find_item(context->Vcb, context->Vcb->extent_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return false;\n    }\n\n    if (keycmp(tp.item->key, searchkey)) {\n        ERR(\"(%I64x,%x,%I64x) not found\\n\", searchkey.obj_id, searchkey.obj_type, searchkey.offset);\n        return false;\n    }\n\n    if (tp.item->size < sizeof(EXTENT_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_ITEM));\n        return false;\n    }\n\n    ei = (EXTENT_ITEM*)tp.item->data;\n\n    if (tp.item->size > sizeof(EXTENT_ITEM)) {\n        uint32_t len = tp.item->size - sizeof(EXTENT_ITEM);\n        uint8_t* ptr = (uint8_t*)&ei[1];\n\n        while (len > 0) {\n            uint8_t secttype = *ptr;\n            ULONG sectlen = get_extent_data_len(secttype);\n            uint64_t sectcount = get_extent_data_refcount(secttype, ptr + sizeof(uint8_t));\n\n            len--;\n\n            if (sectlen > len) {\n                ERR(\"(%I64x,%x,%I64x): %x bytes left, expecting at least %lx\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, len, sectlen);\n                return false;\n            }\n\n            if (sectlen == 0) {\n                ERR(\"(%I64x,%x,%I64x): unrecognized extent type %x\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, secttype);\n                return false;\n            }\n\n            rc += sectcount;\n\n            if (secttype == TYPE_EXTENT_DATA_REF) {\n                EXTENT_DATA_REF* sectedr = (EXTENT_DATA_REF*)(ptr + sizeof(uint8_t));\n\n                if (try_clone_edr(context, se, sectedr))\n                    return true;\n            }\n\n            len -= sectlen;\n            ptr += sizeof(uint8_t) + sectlen;\n        }\n    }\n\n    if (rc >= ei->refcount)\n        return false;\n\n    searchkey.obj_type = TYPE_EXTENT_DATA_REF;\n    searchkey.offset = 0;\n\n    Status = find_item(context->Vcb, context->Vcb->extent_root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return false;\n    }\n\n    while (true) {\n        traverse_ptr next_tp;\n\n        if (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type == searchkey.obj_type) {\n            if (tp.item->size < sizeof(EXTENT_DATA_REF))\n                ERR(\"(%I64x,%x,%I64x) has size %u, not %Iu as expected\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset, tp.item->size, sizeof(EXTENT_DATA_REF));\n            else {\n                if (try_clone_edr(context, se, (EXTENT_DATA_REF*)tp.item->data))\n                    return true;\n            }\n        } else if (tp.item->key.obj_id > searchkey.obj_id || (tp.item->key.obj_id == searchkey.obj_id && tp.item->key.obj_type > searchkey.obj_type))\n            break;\n\n        if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL))\n            tp = next_tp;\n        else\n            break;\n    }\n\n    return false;\n}\n\nstatic NTSTATUS flush_extents(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) {\n    NTSTATUS Status;\n\n    if ((IsListEmpty(&context->lastinode.exts) && IsListEmpty(&context->lastinode.oldexts)) || context->lastinode.size == 0)\n        return STATUS_SUCCESS;\n\n    if (context->parent) {\n        Status = add_ext_holes(context->Vcb, &context->lastinode.exts, context->lastinode.size);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_ext_holes returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        Status = add_ext_holes(context->Vcb, &context->lastinode.oldexts, context->lastinode.size);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_ext_holes returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        Status = sync_ext_cutoff_points(context);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"sync_ext_cutoff_points returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    while (!IsListEmpty(&context->lastinode.exts)) {\n        send_ext* se = CONTAINING_RECORD(RemoveHeadList(&context->lastinode.exts), send_ext, list_entry);\n        send_ext* se2 = context->parent ? CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldexts), send_ext, list_entry) : NULL;\n        ULONG pos;\n        EXTENT_DATA2* ed2;\n\n        if (se2) {\n            if (se->data.type == EXTENT_TYPE_INLINE && se2->data.type == EXTENT_TYPE_INLINE &&\n                RtlCompareMemory(se->data.data, se2->data.data, (ULONG)se->data.decoded_size) == (ULONG)se->data.decoded_size) {\n                ExFreePool(se);\n                ExFreePool(se2);\n                continue;\n            }\n\n            if (se->data.type == EXTENT_TYPE_REGULAR && se2->data.type == EXTENT_TYPE_REGULAR) {\n                EXTENT_DATA2 *ed2a, *ed2b;\n\n                ed2a = (EXTENT_DATA2*)se->data.data;\n                ed2b = (EXTENT_DATA2*)se2->data.data;\n\n                if (RtlCompareMemory(ed2a, ed2b, sizeof(EXTENT_DATA2)) == sizeof(EXTENT_DATA2)) {\n                    ExFreePool(se);\n                    ExFreePool(se2);\n                    continue;\n                }\n            }\n        }\n\n        if (se->data.type == EXTENT_TYPE_INLINE) {\n            pos = context->datalen;\n\n            send_command(context, BTRFS_SEND_CMD_WRITE);\n\n            send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);\n            send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &se->offset, sizeof(uint64_t));\n\n            if (se->data.compression == BTRFS_COMPRESSION_NONE)\n                send_add_tlv(context, BTRFS_SEND_TLV_DATA, se->data.data, (uint16_t)se->data.decoded_size);\n            else if (se->data.compression == BTRFS_COMPRESSION_ZLIB || se->data.compression == BTRFS_COMPRESSION_LZO || se->data.compression == BTRFS_COMPRESSION_ZSTD) {\n                ULONG inlen = se->datalen - (ULONG)offsetof(EXTENT_DATA, data[0]);\n\n                send_add_tlv(context, BTRFS_SEND_TLV_DATA, NULL, (uint16_t)se->data.decoded_size);\n                RtlZeroMemory(&context->data[context->datalen - se->data.decoded_size], (ULONG)se->data.decoded_size);\n\n                if (se->data.compression == BTRFS_COMPRESSION_ZLIB) {\n                    Status = zlib_decompress(se->data.data, inlen, &context->data[context->datalen - se->data.decoded_size], (uint32_t)se->data.decoded_size);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"zlib_decompress returned %08lx\\n\", Status);\n                        ExFreePool(se);\n                        if (se2) ExFreePool(se2);\n                        return Status;\n                    }\n                } else if (se->data.compression == BTRFS_COMPRESSION_LZO) {\n                    if (inlen < sizeof(uint32_t)) {\n                        ERR(\"extent data was truncated\\n\");\n                        ExFreePool(se);\n                        if (se2) ExFreePool(se2);\n                        return STATUS_INTERNAL_ERROR;\n                    } else\n                        inlen -= sizeof(uint32_t);\n\n                    Status = lzo_decompress(se->data.data + sizeof(uint32_t), inlen, &context->data[context->datalen - se->data.decoded_size], (uint32_t)se->data.decoded_size, sizeof(uint32_t));\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"lzo_decompress returned %08lx\\n\", Status);\n                        ExFreePool(se);\n                        if (se2) ExFreePool(se2);\n                        return Status;\n                    }\n                } else if (se->data.compression == BTRFS_COMPRESSION_ZSTD) {\n                    Status = zstd_decompress(se->data.data, inlen, &context->data[context->datalen - se->data.decoded_size], (uint32_t)se->data.decoded_size);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"zlib_decompress returned %08lx\\n\", Status);\n                        ExFreePool(se);\n                        if (se2) ExFreePool(se2);\n                        return Status;\n                    }\n                }\n            } else {\n                ERR(\"unhandled compression type %x\\n\", se->data.compression);\n                ExFreePool(se);\n                if (se2) ExFreePool(se2);\n                return STATUS_NOT_IMPLEMENTED;\n            }\n\n            send_command_finish(context, pos);\n\n            ExFreePool(se);\n            if (se2) ExFreePool(se2);\n            continue;\n        }\n\n        ed2 = (EXTENT_DATA2*)se->data.data;\n\n        if (ed2->size != 0 && (context->parent || context->num_clones > 0)) {\n            if (try_clone(context, se)) {\n                ExFreePool(se);\n                if (se2) ExFreePool(se2);\n                continue;\n            }\n        }\n\n        if (ed2->size == 0) { // write sparse\n            uint64_t off, offset;\n\n            for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) {\n                uint16_t length = (uint16_t)min(min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE), context->lastinode.size - se->offset - off);\n\n                if (context->datalen > SEND_BUFFER_LENGTH) {\n                    Status = wait_for_flush(context, tp1, tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"wait_for_flush returned %08lx\\n\", Status);\n                        ExFreePool(se);\n                        if (se2) ExFreePool(se2);\n                        return Status;\n                    }\n\n                    if (context->send->cancelling) {\n                        ExFreePool(se);\n                        if (se2) ExFreePool(se2);\n                        return STATUS_SUCCESS;\n                    }\n                }\n\n                pos = context->datalen;\n\n                send_command(context, BTRFS_SEND_CMD_WRITE);\n\n                send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);\n\n                offset = se->offset + off;\n                send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(uint64_t));\n\n                send_add_tlv(context, BTRFS_SEND_TLV_DATA, NULL, length);\n                RtlZeroMemory(&context->data[context->datalen - length], length);\n\n                send_command_finish(context, pos);\n            }\n        } else if (se->data.compression == BTRFS_COMPRESSION_NONE) {\n            uint64_t off, offset;\n            uint8_t* buf;\n\n            buf = ExAllocatePoolWithTag(NonPagedPool, MAX_SEND_WRITE + (2 * context->Vcb->superblock.sector_size), ALLOC_TAG);\n            if (!buf) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(se);\n                if (se2) ExFreePool(se2);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) {\n                uint16_t length = (uint16_t)min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE);\n                ULONG skip_start;\n                uint64_t addr = ed2->address + off;\n                void* csum;\n\n                if (context->datalen > SEND_BUFFER_LENGTH) {\n                    Status = wait_for_flush(context, tp1, tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"wait_for_flush returned %08lx\\n\", Status);\n                        ExFreePool(buf);\n                        ExFreePool(se);\n                        if (se2) ExFreePool(se2);\n                        return Status;\n                    }\n\n                    if (context->send->cancelling) {\n                        ExFreePool(buf);\n                        ExFreePool(se);\n                        if (se2) ExFreePool(se2);\n                        return STATUS_SUCCESS;\n                    }\n                }\n\n                skip_start = addr & (context->Vcb->superblock.sector_size - 1);\n                addr -= skip_start;\n\n                if (context->lastinode.flags & BTRFS_INODE_NODATASUM)\n                    csum = NULL;\n                else {\n                    uint32_t len;\n\n                    len = (uint32_t)sector_align(length + skip_start, context->Vcb->superblock.sector_size) >> context->Vcb->sector_shift;\n\n                    csum = ExAllocatePoolWithTag(PagedPool, len * context->Vcb->csum_size, ALLOC_TAG);\n                    if (!csum) {\n                        ERR(\"out of memory\\n\");\n                        ExFreePool(buf);\n                        ExFreePool(se);\n                        if (se2) ExFreePool(se2);\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    Status = load_csum(context->Vcb, csum, addr, len, NULL);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"load_csum returned %08lx\\n\", Status);\n                        ExFreePool(csum);\n                        ExFreePool(buf);\n                        ExFreePool(se);\n                        if (se2) ExFreePool(se2);\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n                }\n\n                Status = read_data(context->Vcb, addr, (uint32_t)sector_align(length + skip_start, context->Vcb->superblock.sector_size),\n                                   csum, false, buf, NULL, NULL, NULL, 0, false, NormalPagePriority);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"read_data returned %08lx\\n\", Status);\n                    ExFreePool(buf);\n                    ExFreePool(se);\n                    if (se2) ExFreePool(se2);\n                    if (csum) ExFreePool(csum);\n                    return Status;\n                }\n\n                if (csum)\n                    ExFreePool(csum);\n\n                pos = context->datalen;\n\n                send_command(context, BTRFS_SEND_CMD_WRITE);\n\n                send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);\n\n                offset = se->offset + off;\n                send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(uint64_t));\n\n                length = (uint16_t)min(context->lastinode.size - se->offset - off, length);\n                send_add_tlv(context, BTRFS_SEND_TLV_DATA, buf + skip_start, length);\n\n                send_command_finish(context, pos);\n            }\n\n            ExFreePool(buf);\n        } else {\n            uint8_t *buf, *compbuf;\n            uint64_t off;\n            void* csum;\n\n            buf = ExAllocatePoolWithTag(PagedPool, (ULONG)se->data.decoded_size, ALLOC_TAG);\n            if (!buf) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(se);\n                if (se2) ExFreePool(se2);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            compbuf = ExAllocatePoolWithTag(PagedPool, (ULONG)ed2->size, ALLOC_TAG);\n            if (!compbuf) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(buf);\n                ExFreePool(se);\n                if (se2) ExFreePool(se2);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            if (context->lastinode.flags & BTRFS_INODE_NODATASUM)\n                csum = NULL;\n            else {\n                uint32_t len;\n\n                len = (uint32_t)(ed2->size >> context->Vcb->sector_shift);\n\n                csum = ExAllocatePoolWithTag(PagedPool, len * context->Vcb->csum_size, ALLOC_TAG);\n                if (!csum) {\n                    ERR(\"out of memory\\n\");\n                    ExFreePool(compbuf);\n                    ExFreePool(buf);\n                    ExFreePool(se);\n                    if (se2) ExFreePool(se2);\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                Status = load_csum(context->Vcb, csum, ed2->address, len, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"load_csum returned %08lx\\n\", Status);\n                    ExFreePool(csum);\n                    ExFreePool(compbuf);\n                    ExFreePool(buf);\n                    ExFreePool(se);\n                    if (se2) ExFreePool(se2);\n                    return Status;\n                }\n            }\n\n            Status = read_data(context->Vcb, ed2->address, (uint32_t)ed2->size, csum, false, compbuf, NULL, NULL, NULL, 0, false, NormalPagePriority);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"read_data returned %08lx\\n\", Status);\n                ExFreePool(compbuf);\n                ExFreePool(buf);\n                ExFreePool(se);\n                if (se2) ExFreePool(se2);\n                if (csum) ExFreePool(csum);\n                return Status;\n            }\n\n            if (csum)\n                ExFreePool(csum);\n\n            if (se->data.compression == BTRFS_COMPRESSION_ZLIB) {\n                Status = zlib_decompress(compbuf, (uint32_t)ed2->size, buf, (uint32_t)se->data.decoded_size);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"zlib_decompress returned %08lx\\n\", Status);\n                    ExFreePool(compbuf);\n                    ExFreePool(buf);\n                    ExFreePool(se);\n                    if (se2) ExFreePool(se2);\n                    return Status;\n                }\n            } else if (se->data.compression == BTRFS_COMPRESSION_LZO) {\n                Status = lzo_decompress(&compbuf[sizeof(uint32_t)], (uint32_t)ed2->size, buf, (uint32_t)se->data.decoded_size, sizeof(uint32_t));\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"lzo_decompress returned %08lx\\n\", Status);\n                    ExFreePool(compbuf);\n                    ExFreePool(buf);\n                    ExFreePool(se);\n                    if (se2) ExFreePool(se2);\n                    return Status;\n                }\n            } else if (se->data.compression == BTRFS_COMPRESSION_ZSTD) {\n                Status = zstd_decompress(compbuf, (uint32_t)ed2->size, buf, (uint32_t)se->data.decoded_size);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"zstd_decompress returned %08lx\\n\", Status);\n                    ExFreePool(compbuf);\n                    ExFreePool(buf);\n                    ExFreePool(se);\n                    if (se2) ExFreePool(se2);\n                    return Status;\n                }\n            }\n\n            ExFreePool(compbuf);\n\n            for (off = ed2->offset; off < ed2->offset + ed2->num_bytes; off += MAX_SEND_WRITE) {\n                uint16_t length = (uint16_t)min(ed2->offset + ed2->num_bytes - off, MAX_SEND_WRITE);\n                uint64_t offset;\n\n                if (context->datalen > SEND_BUFFER_LENGTH) {\n                    Status = wait_for_flush(context, tp1, tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"wait_for_flush returned %08lx\\n\", Status);\n                        ExFreePool(buf);\n                        ExFreePool(se);\n                        if (se2) ExFreePool(se2);\n                        return Status;\n                    }\n\n                    if (context->send->cancelling) {\n                        ExFreePool(buf);\n                        ExFreePool(se);\n                        if (se2) ExFreePool(se2);\n                        return STATUS_SUCCESS;\n                    }\n                }\n\n                pos = context->datalen;\n\n                send_command(context, BTRFS_SEND_CMD_WRITE);\n\n                send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);\n\n                offset = se->offset + off;\n                send_add_tlv(context, BTRFS_SEND_TLV_OFFSET, &offset, sizeof(uint64_t));\n\n                length = (uint16_t)min(context->lastinode.size - se->offset - off, length);\n                send_add_tlv(context, BTRFS_SEND_TLV_DATA, &buf[off], length);\n\n                send_command_finish(context, pos);\n            }\n\n            ExFreePool(buf);\n        }\n\n        ExFreePool(se);\n        if (se2) ExFreePool(se2);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS finish_inode(send_context* context, traverse_ptr* tp1, traverse_ptr* tp2) {\n    LIST_ENTRY* le;\n\n    if (!IsListEmpty(&context->lastinode.refs) || !IsListEmpty(&context->lastinode.oldrefs)) {\n        NTSTATUS Status = flush_refs(context, tp1, tp2);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"flush_refs returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (context->send->cancelling)\n            return STATUS_SUCCESS;\n    }\n\n    if (!context->lastinode.deleting) {\n        if (context->lastinode.file) {\n            NTSTATUS Status = flush_extents(context, tp1, tp2);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"flush_extents returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (context->send->cancelling)\n                return STATUS_SUCCESS;\n\n            send_truncate_command(context, context->lastinode.path, context->lastinode.size);\n        }\n\n        if (context->lastinode.new || context->lastinode.uid != context->lastinode.olduid || context->lastinode.gid != context->lastinode.oldgid)\n            send_chown_command(context, context->lastinode.path, context->lastinode.uid, context->lastinode.gid);\n\n        if (((context->lastinode.mode & __S_IFLNK) != __S_IFLNK || ((context->lastinode.mode & 07777) != 0777)) &&\n            (context->lastinode.new || context->lastinode.mode != context->lastinode.oldmode))\n            send_chmod_command(context, context->lastinode.path, context->lastinode.mode);\n\n        send_utimes_command(context, context->lastinode.path, &context->lastinode.atime, &context->lastinode.mtime, &context->lastinode.ctime);\n    }\n\n    while (!IsListEmpty(&context->lastinode.exts)) {\n        ExFreePool(CONTAINING_RECORD(RemoveHeadList(&context->lastinode.exts), send_ext, list_entry));\n    }\n\n    while (!IsListEmpty(&context->lastinode.oldexts)) {\n        ExFreePool(CONTAINING_RECORD(RemoveHeadList(&context->lastinode.oldexts), send_ext, list_entry));\n    }\n\n    if (context->parent) {\n        le = context->pending_rmdirs.Flink;\n\n        while (le != &context->pending_rmdirs) {\n            pending_rmdir* pr = CONTAINING_RECORD(le, pending_rmdir, list_entry);\n\n            if (pr->last_child_inode <= context->lastinode.inode) {\n                le = le->Flink;\n\n                send_rmdir_command(context, pr->sd->namelen, pr->sd->name);\n\n                RemoveEntryList(&pr->sd->list_entry);\n\n                if (pr->sd->name)\n                    ExFreePool(pr->sd->name);\n\n                while (!IsListEmpty(&pr->sd->deleted_children)) {\n                    deleted_child* dc = CONTAINING_RECORD(RemoveHeadList(&pr->sd->deleted_children), deleted_child, list_entry);\n                    ExFreePool(dc);\n                }\n\n                ExFreePool(pr->sd);\n\n                RemoveEntryList(&pr->list_entry);\n                ExFreePool(pr);\n            } else\n                break;\n        }\n    }\n\n    context->lastinode.inode = 0;\n    context->lastinode.o = NULL;\n\n    if (context->lastinode.path) {\n        ExFreePool(context->lastinode.path);\n        context->lastinode.path = NULL;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS send_extent_data(send_context* context, traverse_ptr* tp, traverse_ptr* tp2) {\n    NTSTATUS Status;\n\n    if (tp && tp2 && tp->item->size == tp2->item->size && RtlCompareMemory(tp->item->data, tp2->item->data, tp->item->size) == tp->item->size)\n        return STATUS_SUCCESS;\n\n    if (!IsListEmpty(&context->lastinode.refs) || !IsListEmpty(&context->lastinode.oldrefs)) {\n        Status = flush_refs(context, tp, tp2);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"flush_refs returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (context->send->cancelling)\n            return STATUS_SUCCESS;\n    }\n\n    if ((context->lastinode.mode & __S_IFLNK) == __S_IFLNK)\n        return STATUS_SUCCESS;\n\n    if (tp) {\n        EXTENT_DATA* ed;\n        EXTENT_DATA2* ed2 = NULL;\n\n        if (tp->item->size < sizeof(EXTENT_DATA)) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,\n                tp->item->size, sizeof(EXTENT_DATA));\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        ed = (EXTENT_DATA*)tp->item->data;\n\n        if (ed->encryption != BTRFS_ENCRYPTION_NONE) {\n            ERR(\"unknown encryption type %u\\n\", ed->encryption);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (ed->encoding != BTRFS_ENCODING_NONE) {\n            ERR(\"unknown encoding type %u\\n\", ed->encoding);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (ed->compression != BTRFS_COMPRESSION_NONE && ed->compression != BTRFS_COMPRESSION_ZLIB &&\n            ed->compression != BTRFS_COMPRESSION_LZO && ed->compression != BTRFS_COMPRESSION_ZSTD) {\n            ERR(\"unknown compression type %u\\n\", ed->compression);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (ed->type == EXTENT_TYPE_REGULAR) {\n            if (tp->item->size < offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,\n                    tp->item->size, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2));\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            ed2 = (EXTENT_DATA2*)ed->data;\n        } else if (ed->type == EXTENT_TYPE_INLINE) {\n            if (tp->item->size < offsetof(EXTENT_DATA, data[0]) + ed->decoded_size && ed->compression == BTRFS_COMPRESSION_NONE) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %I64u\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,\n                    tp->item->size, offsetof(EXTENT_DATA, data[0]) + ed->decoded_size);\n                return STATUS_INTERNAL_ERROR;\n            }\n        }\n\n        if ((ed->type == EXTENT_TYPE_INLINE || (ed->type == EXTENT_TYPE_REGULAR && ed2->size != 0)) && ed->decoded_size != 0) {\n            send_ext* se = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data) + tp->item->size, ALLOC_TAG);\n\n            if (!se) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            se->offset = tp->item->key.offset;\n            se->datalen = tp->item->size;\n            RtlCopyMemory(&se->data, tp->item->data, tp->item->size);\n            InsertTailList(&context->lastinode.exts, &se->list_entry);\n        }\n    }\n\n    if (tp2) {\n        EXTENT_DATA* ed;\n        EXTENT_DATA2* ed2 = NULL;\n\n        if (tp2->item->size < sizeof(EXTENT_DATA)) {\n            ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset,\n                tp2->item->size, sizeof(EXTENT_DATA));\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        ed = (EXTENT_DATA*)tp2->item->data;\n\n        if (ed->encryption != BTRFS_ENCRYPTION_NONE) {\n            ERR(\"unknown encryption type %u\\n\", ed->encryption);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (ed->encoding != BTRFS_ENCODING_NONE) {\n            ERR(\"unknown encoding type %u\\n\", ed->encoding);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (ed->compression != BTRFS_COMPRESSION_NONE && ed->compression != BTRFS_COMPRESSION_ZLIB &&\n            ed->compression != BTRFS_COMPRESSION_LZO && ed->compression != BTRFS_COMPRESSION_ZSTD) {\n            ERR(\"unknown compression type %u\\n\", ed->compression);\n            return STATUS_INTERNAL_ERROR;\n        }\n\n        if (ed->type == EXTENT_TYPE_REGULAR) {\n            if (tp2->item->size < offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2)) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %Iu\\n\", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset,\n                    tp2->item->size, offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2));\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            ed2 = (EXTENT_DATA2*)ed->data;\n        } else if (ed->type == EXTENT_TYPE_INLINE) {\n            if (tp2->item->size < offsetof(EXTENT_DATA, data[0]) + ed->decoded_size) {\n                ERR(\"(%I64x,%x,%I64x) was %u bytes, expected %I64u\\n\", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset,\n                    tp2->item->size, offsetof(EXTENT_DATA, data[0]) + ed->decoded_size);\n                return STATUS_INTERNAL_ERROR;\n            }\n        }\n\n        if ((ed->type == EXTENT_TYPE_INLINE || (ed->type == EXTENT_TYPE_REGULAR && ed2->size != 0)) && ed->decoded_size != 0) {\n            send_ext* se = ExAllocatePoolWithTag(PagedPool, offsetof(send_ext, data) + tp2->item->size, ALLOC_TAG);\n\n            if (!se) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            se->offset = tp2->item->key.offset;\n            se->datalen = tp2->item->size;\n            RtlCopyMemory(&se->data, tp2->item->data, tp2->item->size);\n            InsertTailList(&context->lastinode.oldexts, &se->list_entry);\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\ntypedef struct {\n    uint16_t namelen;\n    char* name;\n    uint16_t value1len;\n    char* value1;\n    uint16_t value2len;\n    char* value2;\n    LIST_ENTRY list_entry;\n} xattr_cmp;\n\nstatic NTSTATUS send_xattr(send_context* context, traverse_ptr* tp, traverse_ptr* tp2) {\n    if (tp && tp2 && tp->item->size == tp2->item->size && RtlCompareMemory(tp->item->data, tp2->item->data, tp->item->size) == tp->item->size)\n        return STATUS_SUCCESS;\n\n    if (!IsListEmpty(&context->lastinode.refs) || !IsListEmpty(&context->lastinode.oldrefs)) {\n        NTSTATUS Status = flush_refs(context, tp, tp2);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"flush_refs returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (context->send->cancelling)\n            return STATUS_SUCCESS;\n    }\n\n    if (tp && tp->item->size < sizeof(DIR_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset,\n            tp->item->size, sizeof(DIR_ITEM));\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (tp2 && tp2->item->size < sizeof(DIR_ITEM)) {\n        ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset,\n            tp2->item->size, sizeof(DIR_ITEM));\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (tp && !tp2) {\n        ULONG len;\n        DIR_ITEM* di;\n\n        len = tp->item->size;\n        di = (DIR_ITEM*)tp->item->data;\n\n        do {\n            ULONG pos;\n\n            if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) {\n                ERR(\"(%I64x,%x,%I64x) was truncated\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            pos = context->datalen;\n            send_command(context, BTRFS_SEND_CMD_SET_XATTR);\n            send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);\n            send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, di->name, di->n);\n            send_add_tlv(context, BTRFS_SEND_TLV_XATTR_DATA, &di->name[di->n], di->m);\n            send_command_finish(context, pos);\n\n            len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n;\n            di = (DIR_ITEM*)&di->name[di->m + di->n];\n        } while (len > 0);\n    } else if (!tp && tp2) {\n        ULONG len;\n        DIR_ITEM* di;\n\n        len = tp2->item->size;\n        di = (DIR_ITEM*)tp2->item->data;\n\n        do {\n            ULONG pos;\n\n            if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) {\n                ERR(\"(%I64x,%x,%I64x) was truncated\\n\", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            pos = context->datalen;\n            send_command(context, BTRFS_SEND_CMD_REMOVE_XATTR);\n            send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);\n            send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, di->name, di->n);\n            send_command_finish(context, pos);\n\n            len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n;\n            di = (DIR_ITEM*)&di->name[di->m + di->n];\n        } while (len > 0);\n    } else {\n        ULONG len;\n        DIR_ITEM* di;\n        LIST_ENTRY xattrs;\n\n        InitializeListHead(&xattrs);\n\n        len = tp->item->size;\n        di = (DIR_ITEM*)tp->item->data;\n\n        do {\n            xattr_cmp* xa;\n\n            if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) {\n                ERR(\"(%I64x,%x,%I64x) was truncated\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            xa = ExAllocatePoolWithTag(PagedPool, sizeof(xattr_cmp), ALLOC_TAG);\n            if (!xa) {\n                ERR(\"out of memory\\n\");\n\n                while (!IsListEmpty(&xattrs)) {\n                    ExFreePool(CONTAINING_RECORD(RemoveHeadList(&xattrs), xattr_cmp, list_entry));\n                }\n\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            xa->namelen = di->n;\n            xa->name = di->name;\n            xa->value1len = di->m;\n            xa->value1 = di->name + di->n;\n            xa->value2len = 0;\n            xa->value2 = NULL;\n\n            InsertTailList(&xattrs, &xa->list_entry);\n\n            len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n;\n            di = (DIR_ITEM*)&di->name[di->m + di->n];\n        } while (len > 0);\n\n        len = tp2->item->size;\n        di = (DIR_ITEM*)tp2->item->data;\n\n        do {\n            xattr_cmp* xa;\n            LIST_ENTRY* le;\n            bool found = false;\n\n            if (len < sizeof(DIR_ITEM) || len < offsetof(DIR_ITEM, name[0]) + di->m + di->n) {\n                ERR(\"(%I64x,%x,%I64x) was truncated\\n\", tp2->item->key.obj_id, tp2->item->key.obj_type, tp2->item->key.offset);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            le = xattrs.Flink;\n            while (le != &xattrs) {\n                xa = CONTAINING_RECORD(le, xattr_cmp, list_entry);\n\n                if (xa->namelen == di->n && RtlCompareMemory(xa->name, di->name, di->n) == di->n) {\n                    xa->value2len = di->m;\n                    xa->value2 = di->name + di->n;\n                    found = true;\n                    break;\n                }\n\n                le = le->Flink;\n            }\n\n            if (!found) {\n                xa = ExAllocatePoolWithTag(PagedPool, sizeof(xattr_cmp), ALLOC_TAG);\n                if (!xa) {\n                    ERR(\"out of memory\\n\");\n\n                    while (!IsListEmpty(&xattrs)) {\n                        ExFreePool(CONTAINING_RECORD(RemoveHeadList(&xattrs), xattr_cmp, list_entry));\n                    }\n\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                xa->namelen = di->n;\n                xa->name = di->name;\n                xa->value1len = 0;\n                xa->value1 = NULL;\n                xa->value2len = di->m;\n                xa->value2 = di->name + di->n;\n\n                InsertTailList(&xattrs, &xa->list_entry);\n            }\n\n            len -= (ULONG)offsetof(DIR_ITEM, name[0]) + di->m + di->n;\n            di = (DIR_ITEM*)&di->name[di->m + di->n];\n        } while (len > 0);\n\n        while (!IsListEmpty(&xattrs)) {\n            xattr_cmp* xa = CONTAINING_RECORD(RemoveHeadList(&xattrs), xattr_cmp, list_entry);\n\n            if (xa->value1len != xa->value2len || !xa->value1 || !xa->value2 || RtlCompareMemory(xa->value1, xa->value2, xa->value1len) != xa->value1len) {\n                ULONG pos;\n\n                if (!xa->value1) {\n                    pos = context->datalen;\n                    send_command(context, BTRFS_SEND_CMD_REMOVE_XATTR);\n                    send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);\n                    send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, xa->name, xa->namelen);\n                    send_command_finish(context, pos);\n                } else {\n                    pos = context->datalen;\n                    send_command(context, BTRFS_SEND_CMD_SET_XATTR);\n                    send_add_tlv(context, BTRFS_SEND_TLV_PATH, context->lastinode.path, context->lastinode.path ? (uint16_t)strlen(context->lastinode.path) : 0);\n                    send_add_tlv(context, BTRFS_SEND_TLV_XATTR_NAME, xa->name, xa->namelen);\n                    send_add_tlv(context, BTRFS_SEND_TLV_XATTR_DATA, xa->value1, xa->value1len);\n                    send_command_finish(context, pos);\n                }\n            }\n\n            ExFreePool(xa);\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\n_Function_class_(KSTART_ROUTINE)\nstatic void __stdcall send_thread(void* ctx) {\n    send_context* context = (send_context*)ctx;\n    NTSTATUS Status;\n    KEY searchkey;\n    traverse_ptr tp, tp2;\n\n    InterlockedIncrement(&context->root->send_ops);\n\n    if (context->parent)\n        InterlockedIncrement(&context->parent->send_ops);\n\n    if (context->clones) {\n        ULONG i;\n\n        for (i = 0; i < context->num_clones; i++) {\n            InterlockedIncrement(&context->clones[i]->send_ops);\n        }\n    }\n\n    ExAcquireResourceExclusiveLite(&context->Vcb->tree_lock, true);\n\n    flush_subvol_fcbs(context->root);\n\n    if (context->parent)\n        flush_subvol_fcbs(context->parent);\n\n    if (context->Vcb->need_write)\n        Status = do_write(context->Vcb, NULL);\n    else\n        Status = STATUS_SUCCESS;\n\n    free_trees(context->Vcb);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"do_write returned %08lx\\n\", Status);\n        ExReleaseResourceLite(&context->Vcb->tree_lock);\n        goto end;\n    }\n\n    ExConvertExclusiveToSharedLite(&context->Vcb->tree_lock);\n\n    searchkey.obj_id = searchkey.offset = 0;\n    searchkey.obj_type = 0;\n\n    Status = find_item(context->Vcb, context->root, &tp, &searchkey, false, NULL);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        ExReleaseResourceLite(&context->Vcb->tree_lock);\n        goto end;\n    }\n\n    if (context->parent) {\n        bool ended1 = false, ended2 = false;\n        Status = find_item(context->Vcb, context->parent, &tp2, &searchkey, false, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_item returned %08lx\\n\", Status);\n            ExReleaseResourceLite(&context->Vcb->tree_lock);\n            goto end;\n        }\n\n        do {\n            traverse_ptr next_tp;\n\n            if (context->datalen > SEND_BUFFER_LENGTH) {\n                KEY key1 = tp.item->key, key2 = tp2.item->key;\n\n                ExReleaseResourceLite(&context->Vcb->tree_lock);\n\n                KeClearEvent(&context->send->cleared_event);\n                KeSetEvent(&context->buffer_event, 0, true);\n                KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL);\n\n                if (context->send->cancelling)\n                    goto end;\n\n                ExAcquireResourceSharedLite(&context->Vcb->tree_lock, true);\n\n                if (!ended1) {\n                    Status = find_item(context->Vcb, context->root, &tp, &key1, false, NULL);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"find_item returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    if (keycmp(tp.item->key, key1)) {\n                        ERR(\"readonly subvolume changed\\n\");\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        Status = STATUS_INTERNAL_ERROR;\n                        goto end;\n                    }\n                }\n\n                if (!ended2) {\n                    Status = find_item(context->Vcb, context->parent, &tp2, &key2, false, NULL);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"find_item returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    if (keycmp(tp2.item->key, key2)) {\n                        ERR(\"readonly subvolume changed\\n\");\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        Status = STATUS_INTERNAL_ERROR;\n                        goto end;\n                    }\n                }\n            }\n\n            while (!ended1 && !ended2 && tp.tree->header.address == tp2.tree->header.address) {\n                Status = skip_to_difference(context->Vcb, &tp, &tp2, &ended1, &ended2);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"skip_to_difference returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                    goto end;\n                }\n            }\n\n            if (!ended1 && !ended2 && !keycmp(tp.item->key, tp2.item->key)) {\n                bool no_next = false, no_next2 = false;\n\n                TRACE(\"~ %I64x,%x,%I64x\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n\n                if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) {\n                    Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"finish_inode returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    if (context->send->cancelling) {\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                }\n\n                if (tp.item->key.obj_type == TYPE_INODE_ITEM) {\n                    if (tp.item->size == tp2.item->size && tp.item->size > 0 && RtlCompareMemory(tp.item->data, tp2.item->data, tp.item->size) == tp.item->size) {\n                        uint64_t inode = tp.item->key.obj_id;\n\n                        while (true) {\n                            if (!find_next_item(context->Vcb, &tp, &next_tp, false, NULL)) {\n                                ended1 = true;\n                                break;\n                            }\n\n                            tp = next_tp;\n\n                            if (tp.item->key.obj_id != inode)\n                                break;\n                        }\n\n                        while (true) {\n                            if (!find_next_item(context->Vcb, &tp2, &next_tp, false, NULL)) {\n                                ended2 = true;\n                                break;\n                            }\n\n                            tp2 = next_tp;\n\n                            if (tp2.item->key.obj_id != inode)\n                                break;\n                        }\n\n                        no_next = true;\n                    } else if (tp.item->size > sizeof(uint64_t) && tp2.item->size > sizeof(uint64_t) && *(uint64_t*)tp.item->data != *(uint64_t*)tp2.item->data) {\n                        uint64_t inode = tp.item->key.obj_id;\n\n                        Status = send_inode(context, NULL, &tp2);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"send_inode returned %08lx\\n\", Status);\n                            ExReleaseResourceLite(&context->Vcb->tree_lock);\n                            goto end;\n                        }\n\n                        while (true) {\n                            if (!find_next_item(context->Vcb, &tp2, &next_tp, false, NULL)) {\n                                ended2 = true;\n                                break;\n                            }\n\n                            tp2 = next_tp;\n\n                            if (tp2.item->key.obj_id != inode)\n                                break;\n\n                            if (tp2.item->key.obj_type == TYPE_INODE_REF) {\n                                Status = send_inode_ref(context, &tp2, true);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"send_inode_ref returned %08lx\\n\", Status);\n                                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                                    goto end;\n                                }\n                            } else if (tp2.item->key.obj_type == TYPE_INODE_EXTREF) {\n                                Status = send_inode_extref(context, &tp2, true);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"send_inode_extref returned %08lx\\n\", Status);\n                                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                                    goto end;\n                                }\n                            }\n                        }\n\n                        Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"finish_inode returned %08lx\\n\", Status);\n                            ExReleaseResourceLite(&context->Vcb->tree_lock);\n                            goto end;\n                        }\n\n                        if (context->send->cancelling) {\n                            ExReleaseResourceLite(&context->Vcb->tree_lock);\n                            goto end;\n                        }\n\n                        no_next2 = true;\n\n                        Status = send_inode(context, &tp, NULL);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"send_inode returned %08lx\\n\", Status);\n                            ExReleaseResourceLite(&context->Vcb->tree_lock);\n                            goto end;\n                        }\n                    } else {\n                        Status = send_inode(context, &tp, &tp2);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"send_inode returned %08lx\\n\", Status);\n                            ExReleaseResourceLite(&context->Vcb->tree_lock);\n                            goto end;\n                        }\n                    }\n                } else if (tp.item->key.obj_type == TYPE_INODE_REF) {\n                    Status = send_inode_ref(context, &tp, false);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_inode_ref returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    Status = send_inode_ref(context, &tp2, true);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_inode_ref returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) {\n                    Status = send_inode_extref(context, &tp, false);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_inode_extref returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    Status = send_inode_extref(context, &tp2, true);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_inode_extref returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) {\n                    Status = send_extent_data(context, &tp, &tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_extent_data returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    if (context->send->cancelling) {\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) {\n                    Status = send_xattr(context, &tp, &tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_xattr returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    if (context->send->cancelling) {\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                }\n\n                if (!no_next) {\n                    if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL))\n                        tp = next_tp;\n                    else\n                        ended1 = true;\n\n                    if (!no_next2) {\n                        if (find_next_item(context->Vcb, &tp2, &next_tp, false, NULL))\n                            tp2 = next_tp;\n                        else\n                            ended2 = true;\n                    }\n                }\n            } else if (ended2 || (!ended1 && !ended2 && keycmp(tp.item->key, tp2.item->key) == -1)) {\n                TRACE(\"A %I64x,%x,%I64x\\n\", tp.item->key.obj_id, tp.item->key.obj_type, tp.item->key.offset);\n\n                if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) {\n                    Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"finish_inode returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    if (context->send->cancelling) {\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                }\n\n                if (tp.item->key.obj_type == TYPE_INODE_ITEM) {\n                    Status = send_inode(context, &tp, NULL);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_inode returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                } else if (tp.item->key.obj_type == TYPE_INODE_REF) {\n                    Status = send_inode_ref(context, &tp, false);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_inode_ref returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) {\n                    Status = send_inode_extref(context, &tp, false);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_inode_extref returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) {\n                    Status = send_extent_data(context, &tp, NULL);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_extent_data returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    if (context->send->cancelling) {\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) {\n                    Status = send_xattr(context, &tp, NULL);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_xattr returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    if (context->send->cancelling) {\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                }\n\n                if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL))\n                    tp = next_tp;\n                else\n                    ended1 = true;\n            } else if (ended1 || (!ended1 && !ended2 && keycmp(tp.item->key, tp2.item->key) == 1)) {\n                TRACE(\"B %I64x,%x,%I64x\\n\", tp2.item->key.obj_id, tp2.item->key.obj_type, tp2.item->key.offset);\n\n                if (context->lastinode.inode != 0 && tp2.item->key.obj_id > context->lastinode.inode) {\n                    Status = finish_inode(context, ended1 ? NULL : &tp, ended2 ? NULL : &tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"finish_inode returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    if (context->send->cancelling) {\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                }\n\n                if (tp2.item->key.obj_type == TYPE_INODE_ITEM) {\n                    Status = send_inode(context, NULL, &tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_inode returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                } else if (tp2.item->key.obj_type == TYPE_INODE_REF) {\n                    Status = send_inode_ref(context, &tp2, true);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_inode_ref returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                } else if (tp2.item->key.obj_type == TYPE_INODE_EXTREF) {\n                    Status = send_inode_extref(context, &tp2, true);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_inode_extref returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                } else if (tp2.item->key.obj_type == TYPE_EXTENT_DATA && !context->lastinode.deleting) {\n                    Status = send_extent_data(context, NULL, &tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_extent_data returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    if (context->send->cancelling) {\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                } else if (tp2.item->key.obj_type == TYPE_XATTR_ITEM && !context->lastinode.deleting) {\n                    Status = send_xattr(context, NULL, &tp2);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"send_xattr returned %08lx\\n\", Status);\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n\n                    if (context->send->cancelling) {\n                        ExReleaseResourceLite(&context->Vcb->tree_lock);\n                        goto end;\n                    }\n                }\n\n                if (find_next_item(context->Vcb, &tp2, &next_tp, false, NULL))\n                    tp2 = next_tp;\n                else\n                    ended2 = true;\n            }\n        } while (!ended1 || !ended2);\n    } else {\n        do {\n            traverse_ptr next_tp;\n\n            if (context->datalen > SEND_BUFFER_LENGTH) {\n                KEY key = tp.item->key;\n\n                ExReleaseResourceLite(&context->Vcb->tree_lock);\n\n                KeClearEvent(&context->send->cleared_event);\n                KeSetEvent(&context->buffer_event, 0, true);\n                KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL);\n\n                if (context->send->cancelling)\n                    goto end;\n\n                ExAcquireResourceSharedLite(&context->Vcb->tree_lock, true);\n\n                Status = find_item(context->Vcb, context->root, &tp, &key, false, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"find_item returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                    goto end;\n                }\n\n                if (keycmp(tp.item->key, key)) {\n                    ERR(\"readonly subvolume changed\\n\");\n                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                    Status = STATUS_INTERNAL_ERROR;\n                    goto end;\n                }\n            }\n\n            if (context->lastinode.inode != 0 && tp.item->key.obj_id > context->lastinode.inode) {\n                Status = finish_inode(context, &tp, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"finish_inode returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                    goto end;\n                }\n\n                if (context->send->cancelling) {\n                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                    goto end;\n                }\n            }\n\n            if (tp.item->key.obj_type == TYPE_INODE_ITEM) {\n                Status = send_inode(context, &tp, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"send_inode returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                    goto end;\n                }\n            } else if (tp.item->key.obj_type == TYPE_INODE_REF) {\n                Status = send_inode_ref(context, &tp, false);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"send_inode_ref returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                    goto end;\n                }\n            } else if (tp.item->key.obj_type == TYPE_INODE_EXTREF) {\n                Status = send_inode_extref(context, &tp, false);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"send_inode_extref returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                    goto end;\n                }\n            } else if (tp.item->key.obj_type == TYPE_EXTENT_DATA) {\n                Status = send_extent_data(context, &tp, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"send_extent_data returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                    goto end;\n                }\n\n                if (context->send->cancelling) {\n                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                    goto end;\n                }\n            } else if (tp.item->key.obj_type == TYPE_XATTR_ITEM) {\n                Status = send_xattr(context, &tp, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"send_xattr returned %08lx\\n\", Status);\n                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                    goto end;\n                }\n\n                if (context->send->cancelling) {\n                    ExReleaseResourceLite(&context->Vcb->tree_lock);\n                    goto end;\n                }\n            }\n\n            if (find_next_item(context->Vcb, &tp, &next_tp, false, NULL))\n                tp = next_tp;\n            else\n                break;\n        } while (true);\n    }\n\n    if (context->lastinode.inode != 0) {\n        Status = finish_inode(context, NULL, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"finish_inode returned %08lx\\n\", Status);\n            ExReleaseResourceLite(&context->Vcb->tree_lock);\n            goto end;\n        }\n\n        ExReleaseResourceLite(&context->Vcb->tree_lock);\n\n        if (context->send->cancelling)\n            goto end;\n    } else\n        ExReleaseResourceLite(&context->Vcb->tree_lock);\n\n    KeClearEvent(&context->send->cleared_event);\n    KeSetEvent(&context->buffer_event, 0, true);\n    KeWaitForSingleObject(&context->send->cleared_event, Executive, KernelMode, false, NULL);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    if (!NT_SUCCESS(Status)) {\n        KeSetEvent(&context->buffer_event, 0, false);\n\n        if (context->send->ccb)\n            context->send->ccb->send_status = Status;\n    }\n\n    ExAcquireResourceExclusiveLite(&context->Vcb->send_load_lock, true);\n\n    while (!IsListEmpty(&context->orphans)) {\n        orphan* o = CONTAINING_RECORD(RemoveHeadList(&context->orphans), orphan, list_entry);\n        ExFreePool(o);\n    }\n\n    while (!IsListEmpty(&context->dirs)) {\n        send_dir* sd = CONTAINING_RECORD(RemoveHeadList(&context->dirs), send_dir, list_entry);\n\n        if (sd->name)\n            ExFreePool(sd->name);\n\n        while (!IsListEmpty(&sd->deleted_children)) {\n            deleted_child* dc = CONTAINING_RECORD(RemoveHeadList(&sd->deleted_children), deleted_child, list_entry);\n            ExFreePool(dc);\n        }\n\n        ExFreePool(sd);\n    }\n\n    ZwClose(context->send->thread);\n    context->send->thread = NULL;\n\n    if (context->send->ccb)\n        context->send->ccb->send = NULL;\n\n    RemoveEntryList(&context->send->list_entry);\n    ExFreePool(context->send);\n    ExFreePool(context->data);\n\n    InterlockedDecrement(&context->Vcb->running_sends);\n    InterlockedDecrement(&context->root->send_ops);\n\n    if (context->parent)\n        InterlockedDecrement(&context->parent->send_ops);\n\n    ExReleaseResourceLite(&context->Vcb->send_load_lock);\n\n    if (context->clones) {\n        ULONG i;\n\n        for (i = 0; i < context->num_clones; i++) {\n            InterlockedDecrement(&context->clones[i]->send_ops);\n        }\n\n        ExFreePool(context->clones);\n    }\n\n    ExFreePool(context);\n\n    PsTerminateSystemThread(STATUS_SUCCESS);\n}\n\nNTSTATUS send_subvol(device_extension* Vcb, void* data, ULONG datalen, PFILE_OBJECT FileObject, PIRP Irp) {\n    NTSTATUS Status;\n    fcb* fcb;\n    ccb* ccb;\n    root* parsubvol = NULL;\n    send_context* context;\n    send_info* send;\n    ULONG num_clones = 0;\n    root** clones = NULL;\n    OBJECT_ATTRIBUTES oa;\n\n    if (!FileObject || !FileObject->FsContext || !FileObject->FsContext2 || FileObject->FsContext == Vcb->volume_fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), Irp->RequestorMode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    fcb = FileObject->FsContext;\n    ccb = FileObject->FsContext2;\n\n    if (fcb->inode != SUBVOL_ROOT_INODE || fcb == Vcb->root_fileref->fcb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!Vcb->readonly && !(fcb->subvol->root_item.flags & BTRFS_SUBVOL_READONLY))\n        return STATUS_INVALID_PARAMETER;\n\n    if (data) {\n        btrfs_send_subvol* bss = (btrfs_send_subvol*)data;\n        HANDLE parent;\n\n#if defined(_WIN64)\n        if (IoIs32bitProcess(Irp)) {\n            btrfs_send_subvol32* bss32 = (btrfs_send_subvol32*)data;\n\n            if (datalen < offsetof(btrfs_send_subvol32, num_clones))\n                return STATUS_INVALID_PARAMETER;\n\n            parent = Handle32ToHandle(bss32->parent);\n\n            if (datalen >= offsetof(btrfs_send_subvol32, clones[0]))\n                num_clones = bss32->num_clones;\n\n            if (datalen < offsetof(btrfs_send_subvol32, clones[0]) + (num_clones * sizeof(uint32_t)))\n                return STATUS_INVALID_PARAMETER;\n        } else {\n#endif\n            if (datalen < offsetof(btrfs_send_subvol, num_clones))\n                return STATUS_INVALID_PARAMETER;\n\n            parent = bss->parent;\n\n            if (datalen >= offsetof(btrfs_send_subvol, clones[0]))\n                num_clones = bss->num_clones;\n\n            if (datalen < offsetof(btrfs_send_subvol, clones[0]) + (num_clones * sizeof(HANDLE)))\n                return STATUS_INVALID_PARAMETER;\n#if defined(_WIN64)\n        }\n#endif\n\n        if (parent) {\n            PFILE_OBJECT fileobj;\n            struct _fcb* parfcb;\n\n            Status = ObReferenceObjectByHandle(parent, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"ObReferenceObjectByHandle returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            if (fileobj->DeviceObject != FileObject->DeviceObject) {\n                ObDereferenceObject(fileobj);\n                return STATUS_INVALID_PARAMETER;\n            }\n\n            parfcb = fileobj->FsContext;\n\n            if (!parfcb || parfcb == Vcb->root_fileref->fcb || parfcb == Vcb->volume_fcb || parfcb->inode != SUBVOL_ROOT_INODE) {\n                ObDereferenceObject(fileobj);\n                return STATUS_INVALID_PARAMETER;\n            }\n\n            parsubvol = parfcb->subvol;\n            ObDereferenceObject(fileobj);\n\n            if (!Vcb->readonly && !(parsubvol->root_item.flags & BTRFS_SUBVOL_READONLY))\n                return STATUS_INVALID_PARAMETER;\n\n            if (parsubvol == fcb->subvol)\n                return STATUS_INVALID_PARAMETER;\n        }\n\n        if (num_clones > 0) {\n            ULONG i;\n\n            clones = ExAllocatePoolWithTag(PagedPool, sizeof(root*) * num_clones, ALLOC_TAG);\n            if (!clones) {\n                ERR(\"out of memory\\n\");\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            for (i = 0; i < num_clones; i++) {\n                HANDLE h;\n                PFILE_OBJECT fileobj;\n                struct _fcb* clonefcb;\n\n#if defined(_WIN64)\n                if (IoIs32bitProcess(Irp)) {\n                    btrfs_send_subvol32* bss32 = (btrfs_send_subvol32*)data;\n\n                    h = Handle32ToHandle(bss32->clones[i]);\n                } else\n#endif\n                    h = bss->clones[i];\n\n                Status = ObReferenceObjectByHandle(h, 0, *IoFileObjectType, Irp->RequestorMode, (void**)&fileobj, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"ObReferenceObjectByHandle returned %08lx\\n\", Status);\n                    ExFreePool(clones);\n                    return Status;\n                }\n\n                if (fileobj->DeviceObject != FileObject->DeviceObject) {\n                    ObDereferenceObject(fileobj);\n                    ExFreePool(clones);\n                    return STATUS_INVALID_PARAMETER;\n                }\n\n                clonefcb = fileobj->FsContext;\n\n                if (!clonefcb || clonefcb == Vcb->root_fileref->fcb || clonefcb == Vcb->volume_fcb || clonefcb->inode != SUBVOL_ROOT_INODE) {\n                    ObDereferenceObject(fileobj);\n                    ExFreePool(clones);\n                    return STATUS_INVALID_PARAMETER;\n                }\n\n                clones[i] = clonefcb->subvol;\n                ObDereferenceObject(fileobj);\n\n                if (!Vcb->readonly && !(clones[i]->root_item.flags & BTRFS_SUBVOL_READONLY)) {\n                    ExFreePool(clones);\n                    return STATUS_INVALID_PARAMETER;\n                }\n            }\n        }\n    }\n\n    ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, true);\n\n    if (ccb->send) {\n        WARN(\"send operation already running\\n\");\n        ExReleaseResourceLite(&Vcb->send_load_lock);\n        return STATUS_DEVICE_NOT_READY;\n    }\n\n    context = ExAllocatePoolWithTag(NonPagedPool, sizeof(send_context), ALLOC_TAG);\n    if (!context) {\n        ERR(\"out of memory\\n\");\n\n        if (clones)\n            ExFreePool(clones);\n\n        ExReleaseResourceLite(&Vcb->send_load_lock);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    context->Vcb = Vcb;\n    context->root = fcb->subvol;\n    context->parent = parsubvol;\n    InitializeListHead(&context->orphans);\n    InitializeListHead(&context->dirs);\n    InitializeListHead(&context->pending_rmdirs);\n    context->lastinode.inode = 0;\n    context->lastinode.path = NULL;\n    context->lastinode.sd = NULL;\n    context->root_dir = NULL;\n    context->num_clones = num_clones;\n    context->clones = clones;\n    InitializeListHead(&context->lastinode.refs);\n    InitializeListHead(&context->lastinode.oldrefs);\n    InitializeListHead(&context->lastinode.exts);\n    InitializeListHead(&context->lastinode.oldexts);\n\n    context->data = ExAllocatePoolWithTag(PagedPool, SEND_BUFFER_LENGTH + (2 * MAX_SEND_WRITE), ALLOC_TAG); // give ourselves some wiggle room\n    if (!context->data) {\n        ExFreePool(context);\n        ExReleaseResourceLite(&Vcb->send_load_lock);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    context->datalen = 0;\n\n    send_subvol_header(context, fcb->subvol, ccb->fileref); // FIXME - fileref needs some sort of lock here\n\n    KeInitializeEvent(&context->buffer_event, NotificationEvent, false);\n\n    send = ExAllocatePoolWithTag(NonPagedPool, sizeof(send_info), ALLOC_TAG);\n    if (!send) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(context->data);\n        ExFreePool(context);\n\n        if (clones)\n            ExFreePool(clones);\n\n        ExReleaseResourceLite(&Vcb->send_load_lock);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    KeInitializeEvent(&send->cleared_event, NotificationEvent, false);\n\n    send->context = context;\n    context->send = send;\n\n    ccb->send = send;\n    send->ccb = ccb;\n    ccb->send_status = STATUS_SUCCESS;\n\n    send->cancelling = false;\n\n    InterlockedIncrement(&Vcb->running_sends);\n\n    InitializeObjectAttributes(&oa, NULL, OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    Status = PsCreateSystemThread(&send->thread, 0, &oa, NULL, NULL, send_thread, context);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"PsCreateSystemThread returned %08lx\\n\", Status);\n        ccb->send = NULL;\n        InterlockedDecrement(&Vcb->running_sends);\n        ExFreePool(send);\n        ExFreePool(context->data);\n        ExFreePool(context);\n\n        if (clones)\n            ExFreePool(clones);\n\n        ExReleaseResourceLite(&Vcb->send_load_lock);\n        return Status;\n    }\n\n    InsertTailList(&Vcb->send_ops, &send->list_entry);\n    ExReleaseResourceLite(&Vcb->send_load_lock);\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS read_send_buffer(device_extension* Vcb, PFILE_OBJECT FileObject, void* data, ULONG datalen, ULONG_PTR* retlen, KPROCESSOR_MODE processor_mode) {\n    ccb* ccb;\n    send_context* context;\n\n    ccb = FileObject ? FileObject->FsContext2 : NULL;\n    if (!ccb)\n        return STATUS_INVALID_PARAMETER;\n\n    if (!SeSinglePrivilegeCheck(RtlConvertLongToLuid(SE_MANAGE_VOLUME_PRIVILEGE), processor_mode))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    ExAcquireResourceExclusiveLite(&Vcb->send_load_lock, true);\n\n    if (!ccb->send) {\n        ExReleaseResourceLite(&Vcb->send_load_lock);\n        return !NT_SUCCESS(ccb->send_status) ? ccb->send_status : STATUS_END_OF_FILE;\n    }\n\n    context = (send_context*)ccb->send->context;\n\n    KeWaitForSingleObject(&context->buffer_event, Executive, KernelMode, false, NULL);\n\n    if (datalen == 0) {\n        ExReleaseResourceLite(&Vcb->send_load_lock);\n        return STATUS_SUCCESS;\n    }\n\n    RtlCopyMemory(data, context->data, min(datalen, context->datalen));\n\n    if (datalen < context->datalen) { // not empty yet\n        *retlen = datalen;\n        RtlMoveMemory(context->data, &context->data[datalen], context->datalen - datalen);\n        context->datalen -= datalen;\n        ExReleaseResourceLite(&Vcb->send_load_lock);\n    } else {\n        *retlen = context->datalen;\n        context->datalen = 0;\n        ExReleaseResourceLite(&Vcb->send_load_lock);\n\n        KeClearEvent(&context->buffer_event);\n        KeSetEvent(&ccb->send->cleared_event, 0, false);\n    }\n\n    return STATUS_SUCCESS;\n}\n"
  },
  {
    "path": "src/sha256.c",
    "content": "#include <stdint.h>\n#include <string.h>\n\n// Public domain code from https://github.com/amosnier/sha-2\n\n// FIXME - x86 SHA extensions\n\n#define CHUNK_SIZE 64\n#define TOTAL_LEN_LEN 8\n\n/*\n * ABOUT bool: this file does not use bool in order to be as pre-C99 compatible as possible.\n */\n\n/*\n * Comments from pseudo-code at https://en.wikipedia.org/wiki/SHA-2 are reproduced here.\n * When useful for clarification, portions of the pseudo-code are reproduced here too.\n */\n\n/*\n * Initialize array of round constants:\n * (first 32 bits of the fractional parts of the cube roots of the first 64 primes 2..311):\n */\nstatic const uint32_t k[] = {\n\t0x428a2f98, 0x71374491, 0xb5c0fbcf, 0xe9b5dba5, 0x3956c25b, 0x59f111f1, 0x923f82a4, 0xab1c5ed5,\n\t0xd807aa98, 0x12835b01, 0x243185be, 0x550c7dc3, 0x72be5d74, 0x80deb1fe, 0x9bdc06a7, 0xc19bf174,\n\t0xe49b69c1, 0xefbe4786, 0x0fc19dc6, 0x240ca1cc, 0x2de92c6f, 0x4a7484aa, 0x5cb0a9dc, 0x76f988da,\n\t0x983e5152, 0xa831c66d, 0xb00327c8, 0xbf597fc7, 0xc6e00bf3, 0xd5a79147, 0x06ca6351, 0x14292967,\n\t0x27b70a85, 0x2e1b2138, 0x4d2c6dfc, 0x53380d13, 0x650a7354, 0x766a0abb, 0x81c2c92e, 0x92722c85,\n\t0xa2bfe8a1, 0xa81a664b, 0xc24b8b70, 0xc76c51a3, 0xd192e819, 0xd6990624, 0xf40e3585, 0x106aa070,\n\t0x19a4c116, 0x1e376c08, 0x2748774c, 0x34b0bcb5, 0x391c0cb3, 0x4ed8aa4a, 0x5b9cca4f, 0x682e6ff3,\n\t0x748f82ee, 0x78a5636f, 0x84c87814, 0x8cc70208, 0x90befffa, 0xa4506ceb, 0xbef9a3f7, 0xc67178f2\n};\n\nstruct buffer_state {\n\tconst uint8_t * p;\n\tsize_t len;\n\tsize_t total_len;\n\tint single_one_delivered; /* bool */\n\tint total_len_delivered; /* bool */\n};\n\nstatic inline uint32_t right_rot(uint32_t value, unsigned int count)\n{\n\t/*\n\t * Defined behaviour in standard C for all count where 0 < count < 32,\n\t * which is what we need here.\n\t */\n\treturn value >> count | value << (32 - count);\n}\n\nstatic void init_buf_state(struct buffer_state * state, const void * input, size_t len)\n{\n\tstate->p = input;\n\tstate->len = len;\n\tstate->total_len = len;\n\tstate->single_one_delivered = 0;\n\tstate->total_len_delivered = 0;\n}\n\n/* Return value: bool */\nstatic int calc_chunk(uint8_t chunk[CHUNK_SIZE], struct buffer_state * state)\n{\n\tsize_t space_in_chunk;\n\n\tif (state->total_len_delivered) {\n\t\treturn 0;\n\t}\n\n\tif (state->len >= CHUNK_SIZE) {\n\t\tmemcpy(chunk, state->p, CHUNK_SIZE);\n\t\tstate->p += CHUNK_SIZE;\n\t\tstate->len -= CHUNK_SIZE;\n\t\treturn 1;\n\t}\n\n\tmemcpy(chunk, state->p, state->len);\n\tchunk += state->len;\n\tspace_in_chunk = CHUNK_SIZE - state->len;\n\tstate->p += state->len;\n\tstate->len = 0;\n\n\t/* If we are here, space_in_chunk is one at minimum. */\n\tif (!state->single_one_delivered) {\n\t\t*chunk++ = 0x80;\n\t\tspace_in_chunk -= 1;\n\t\tstate->single_one_delivered = 1;\n\t}\n\n\t/*\n\t * Now:\n\t * - either there is enough space left for the total length, and we can conclude,\n\t * - or there is too little space left, and we have to pad the rest of this chunk with zeroes.\n\t * In the latter case, we will conclude at the next invokation of this function.\n\t */\n\tif (space_in_chunk >= TOTAL_LEN_LEN) {\n\t\tconst size_t left = space_in_chunk - TOTAL_LEN_LEN;\n\t\tsize_t len = state->total_len;\n\t\tint i;\n\t\tmemset(chunk, 0x00, left);\n\t\tchunk += left;\n\n\t\t/* Storing of len * 8 as a big endian 64-bit without overflow. */\n\t\tchunk[7] = (uint8_t) (len << 3);\n\t\tlen >>= 5;\n\t\tfor (i = 6; i >= 0; i--) {\n\t\t\tchunk[i] = (uint8_t) len;\n\t\t\tlen >>= 8;\n\t\t}\n\t\tstate->total_len_delivered = 1;\n\t} else {\n\t\tmemset(chunk, 0x00, space_in_chunk);\n\t}\n\n\treturn 1;\n}\n\n/*\n * Limitations:\n * - Since input is a pointer in RAM, the data to hash should be in RAM, which could be a problem\n *   for large data sizes.\n * - SHA algorithms theoretically operate on bit strings. However, this implementation has no support\n *   for bit string lengths that are not multiples of eight, and it really operates on arrays of bytes.\n *   In particular, the len parameter is a number of bytes.\n */\nvoid calc_sha256(uint8_t* hash, const void* input, size_t len)\n{\n\t/*\n\t * Note 1: All integers (expect indexes) are 32-bit unsigned integers and addition is calculated modulo 2^32.\n\t * Note 2: For each round, there is one round constant k[i] and one entry in the message schedule array w[i], 0 = i = 63\n\t * Note 3: The compression function uses 8 working variables, a through h\n\t * Note 4: Big-endian convention is used when expressing the constants in this pseudocode,\n\t *     and when parsing message block data from bytes to words, for example,\n\t *     the first word of the input message \"abc\" after padding is 0x61626380\n\t */\n\n\t/*\n\t * Initialize hash values:\n\t * (first 32 bits of the fractional parts of the square roots of the first 8 primes 2..19):\n\t */\n\tuint32_t h[] = { 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, 0x5be0cd19 };\n\tunsigned i, j;\n\n\t/* 512-bit chunks is what we will operate on. */\n\tuint8_t chunk[64];\n\n\tstruct buffer_state state;\n\n\tinit_buf_state(&state, input, len);\n\n\twhile (calc_chunk(chunk, &state)) {\n\t\tuint32_t ah[8];\n\n\t\tconst uint8_t *p = chunk;\n\n\t\t/* Initialize working variables to current hash value: */\n\t\tfor (i = 0; i < 8; i++)\n\t\t\tah[i] = h[i];\n\n\t\t/* Compression function main loop: */\n\t\tfor (i = 0; i < 4; i++) {\n\t\t\t/*\n\t\t\t * The w-array is really w[64], but since we only need\n\t\t\t * 16 of them at a time, we save stack by calculating\n\t\t\t * 16 at a time.\n\t\t\t *\n\t\t\t * This optimization was not there initially and the\n\t\t\t * rest of the comments about w[64] are kept in their\n\t\t\t * initial state.\n\t\t\t */\n\n\t\t\t/*\n\t\t\t * create a 64-entry message schedule array w[0..63] of 32-bit words\n\t\t\t * (The initial values in w[0..63] don't matter, so many implementations zero them here)\n\t\t\t * copy chunk into first 16 words w[0..15] of the message schedule array\n\t\t\t */\n\t\t\tuint32_t w[16];\n\n\t\t\tfor (j = 0; j < 16; j++) {\n\t\t\t\tif (i == 0) {\n\t\t\t\t\tw[j] = (uint32_t) p[0] << 24 | (uint32_t) p[1] << 16 |\n\t\t\t\t\t\t(uint32_t) p[2] << 8 | (uint32_t) p[3];\n\t\t\t\t\tp += 4;\n\t\t\t\t} else {\n\t\t\t\t\t/* Extend the first 16 words into the remaining 48 words w[16..63] of the message schedule array: */\n\t\t\t\t\tconst uint32_t s0 = right_rot(w[(j + 1) & 0xf], 7) ^ right_rot(w[(j + 1) & 0xf], 18) ^ (w[(j + 1) & 0xf] >> 3);\n\t\t\t\t\tconst uint32_t s1 = right_rot(w[(j + 14) & 0xf], 17) ^ right_rot(w[(j + 14) & 0xf], 19) ^ (w[(j + 14) & 0xf] >> 10);\n\t\t\t\t\tw[j] = w[j] + s0 + w[(j + 9) & 0xf] + s1;\n\t\t\t\t}\n\t\t\t\tconst uint32_t s1 = right_rot(ah[4], 6) ^ right_rot(ah[4], 11) ^ right_rot(ah[4], 25);\n\t\t\t\tconst uint32_t ch = (ah[4] & ah[5]) ^ (~ah[4] & ah[6]);\n\t\t\t\tconst uint32_t temp1 = ah[7] + s1 + ch + k[i << 4 | j] + w[j];\n\t\t\t\tconst uint32_t s0 = right_rot(ah[0], 2) ^ right_rot(ah[0], 13) ^ right_rot(ah[0], 22);\n\t\t\t\tconst uint32_t maj = (ah[0] & ah[1]) ^ (ah[0] & ah[2]) ^ (ah[1] & ah[2]);\n\t\t\t\tconst uint32_t temp2 = s0 + maj;\n\n\t\t\t\tah[7] = ah[6];\n\t\t\t\tah[6] = ah[5];\n\t\t\t\tah[5] = ah[4];\n\t\t\t\tah[4] = ah[3] + temp1;\n\t\t\t\tah[3] = ah[2];\n\t\t\t\tah[2] = ah[1];\n\t\t\t\tah[1] = ah[0];\n\t\t\t\tah[0] = temp1 + temp2;\n\t\t\t}\n\t\t}\n\n\t\t/* Add the compressed chunk to the current hash value: */\n\t\tfor (i = 0; i < 8; i++)\n\t\t\th[i] += ah[i];\n\t}\n\n\t/* Produce the final hash value (big-endian): */\n\tfor (i = 0, j = 0; i < 8; i++)\n\t{\n\t\thash[j++] = (uint8_t) (h[i] >> 24);\n\t\thash[j++] = (uint8_t) (h[i] >> 16);\n\t\thash[j++] = (uint8_t) (h[i] >> 8);\n\t\thash[j++] = (uint8_t) h[i];\n\t}\n}\n"
  },
  {
    "path": "src/shellext/balance.cpp",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"shellext.h\"\n#include \"balance.h\"\n#include \"resource.h\"\n#include \"../btrfsioctl.h\"\n#include <shlobj.h>\n#include <uxtheme.h>\n#include <stdio.h>\n#include <strsafe.h>\n#include <winternl.h>\n\n#define NO_SHLWAPI_STRFCNS\n#include <shlwapi.h>\n#include <uxtheme.h>\n\nstatic uint64_t convtypes2[] = { BLOCK_FLAG_SINGLE, BLOCK_FLAG_DUPLICATE, BLOCK_FLAG_RAID0, BLOCK_FLAG_RAID1,\n                                 BLOCK_FLAG_RAID5, BLOCK_FLAG_RAID1C3, BLOCK_FLAG_RAID6, BLOCK_FLAG_RAID10,\n                                 BLOCK_FLAG_RAID1C4 };\n\nstatic WCHAR hex_digit(uint8_t u) {\n    if (u >= 0xa && u <= 0xf)\n        return (uint8_t)(u - 0xa + 'a');\n    else\n        return (uint8_t)(u + '0');\n}\n\nstatic void serialize(void* data, ULONG len, WCHAR* s) {\n    uint8_t* d;\n\n    d = (uint8_t*)data;\n\n    while (true) {\n        *s = hex_digit((uint8_t)(*d >> 4)); s++;\n        *s = hex_digit(*d & 0xf); s++;\n\n        d++;\n        len--;\n\n        if (len == 0) {\n            *s = 0;\n            return;\n        }\n    }\n}\n\nvoid BtrfsBalance::StartBalance(HWND hwndDlg) {\n    wstring t;\n    WCHAR modfn[MAX_PATH], u[600];\n    SHELLEXECUTEINFOW sei;\n    btrfs_start_balance bsb;\n\n    GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR));\n\n    t = L\"\\\"\"s + modfn + L\"\\\",StartBalance \"s + fn + L\" \"s;\n\n    RtlCopyMemory(&bsb.opts[0], &data_opts, sizeof(btrfs_balance_opts));\n    RtlCopyMemory(&bsb.opts[1], &metadata_opts, sizeof(btrfs_balance_opts));\n    RtlCopyMemory(&bsb.opts[2], &system_opts, sizeof(btrfs_balance_opts));\n\n    if (IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED)\n        bsb.opts[0].flags |= BTRFS_BALANCE_OPTS_ENABLED;\n    else\n        bsb.opts[0].flags &= ~BTRFS_BALANCE_OPTS_ENABLED;\n\n    if (IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED)\n        bsb.opts[1].flags |= BTRFS_BALANCE_OPTS_ENABLED;\n    else\n        bsb.opts[1].flags &= ~BTRFS_BALANCE_OPTS_ENABLED;\n\n    if (IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED)\n        bsb.opts[2].flags |= BTRFS_BALANCE_OPTS_ENABLED;\n    else\n        bsb.opts[2].flags &= ~BTRFS_BALANCE_OPTS_ENABLED;\n\n    serialize(&bsb, sizeof(btrfs_start_balance), u);\n\n    t += u;\n\n    RtlZeroMemory(&sei, sizeof(sei));\n\n    sei.cbSize = sizeof(sei);\n    sei.hwnd = hwndDlg;\n    sei.lpVerb = L\"runas\";\n    sei.lpFile = L\"rundll32.exe\";\n    sei.lpParameters = t.c_str();\n    sei.nShow = SW_SHOW;\n    sei.fMask = SEE_MASK_NOCLOSEPROCESS;\n\n    if (!ShellExecuteExW(&sei))\n        throw last_error(GetLastError());\n\n    cancelling = false;\n    removing = false;\n    shrinking = false;\n    balance_status = BTRFS_BALANCE_RUNNING;\n\n    EnableWindow(GetDlgItem(hwndDlg, IDC_PAUSE_BALANCE), true);\n    EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BALANCE), true);\n    EnableWindow(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), true);\n    EnableWindow(GetDlgItem(hwndDlg, IDC_DATA), false);\n    EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA), false);\n    EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM), false);\n    EnableWindow(GetDlgItem(hwndDlg, IDC_DATA_OPTIONS), data_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? true : false);\n    EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA_OPTIONS), metadata_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? true : false);\n    EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM_OPTIONS), system_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? true : false);\n\n    EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), false);\n\n    WaitForSingleObject(sei.hProcess, INFINITE);\n    CloseHandle(sei.hProcess);\n}\n\nvoid BtrfsBalance::PauseBalance(HWND hwndDlg) {\n    WCHAR modfn[MAX_PATH];\n    wstring t;\n    SHELLEXECUTEINFOW sei;\n\n    GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR));\n\n    t = L\"\\\"\"s + modfn + L\"\\\",PauseBalance \" + fn;\n\n    RtlZeroMemory(&sei, sizeof(sei));\n\n    sei.cbSize = sizeof(sei);\n    sei.hwnd = hwndDlg;\n    sei.lpVerb = L\"runas\";\n    sei.lpFile = L\"rundll32.exe\";\n    sei.lpParameters = t.c_str();\n    sei.nShow = SW_SHOW;\n    sei.fMask = SEE_MASK_NOCLOSEPROCESS;\n\n    if (!ShellExecuteExW(&sei))\n        throw last_error(GetLastError());\n\n    WaitForSingleObject(sei.hProcess, INFINITE);\n    CloseHandle(sei.hProcess);\n}\n\nvoid BtrfsBalance::StopBalance(HWND hwndDlg) {\n    WCHAR modfn[MAX_PATH];\n    wstring t;\n    SHELLEXECUTEINFOW sei;\n\n    GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR));\n\n    t = L\"\\\"\"s + modfn + L\"\\\",StopBalance \" + fn;\n\n    RtlZeroMemory(&sei, sizeof(sei));\n\n    sei.cbSize = sizeof(sei);\n    sei.hwnd = hwndDlg;\n    sei.lpVerb = L\"runas\";\n    sei.lpFile = L\"rundll32.exe\";\n    sei.lpParameters = t.c_str();\n    sei.nShow = SW_SHOW;\n    sei.fMask = SEE_MASK_NOCLOSEPROCESS;\n\n    if (!ShellExecuteExW(&sei))\n        throw last_error(GetLastError());\n\n    cancelling = true;\n\n    WaitForSingleObject(sei.hProcess, INFINITE);\n    CloseHandle(sei.hProcess);\n}\n\nvoid BtrfsBalance::RefreshBalanceDlg(HWND hwndDlg, bool first) {\n    bool balancing = false;\n    wstring s, t;\n\n    {\n        win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                   OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n        if (h != INVALID_HANDLE_VALUE) {\n            NTSTATUS Status;\n            IO_STATUS_BLOCK iosb;\n\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_BALANCE, nullptr, 0, &bqb, sizeof(btrfs_query_balance));\n\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n        } else\n            throw last_error(GetLastError());\n    }\n\n    if (cancelling)\n        bqb.status = BTRFS_BALANCE_STOPPED;\n\n    balancing = bqb.status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED);\n\n    if (!balancing) {\n        if (first || balance_status != BTRFS_BALANCE_STOPPED) {\n            int resid;\n\n            EnableWindow(GetDlgItem(hwndDlg, IDC_PAUSE_BALANCE), false);\n            EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BALANCE), false);\n            SendMessageW(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), PBM_SETSTATE, PBST_NORMAL, 0);\n            EnableWindow(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), false);\n            EnableWindow(GetDlgItem(hwndDlg, IDC_DATA), true);\n            EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA), true);\n            EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM), true);\n\n            if (balance_status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED)) {\n                CheckDlgButton(hwndDlg, IDC_DATA, BST_UNCHECKED);\n                CheckDlgButton(hwndDlg, IDC_METADATA, BST_UNCHECKED);\n                CheckDlgButton(hwndDlg, IDC_SYSTEM, BST_UNCHECKED);\n\n                SendMessageW(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), PBM_SETPOS, 0, 0);\n            }\n\n            EnableWindow(GetDlgItem(hwndDlg, IDC_DATA_OPTIONS), IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED ? true : false);\n            EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA_OPTIONS), IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED ? true : false);\n            EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM_OPTIONS), IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED ? true : false);\n\n            if (bqb.status & BTRFS_BALANCE_ERROR) {\n                if (removing)\n                    resid = IDS_BALANCE_FAILED_REMOVAL;\n                else if (shrinking)\n                    resid = IDS_BALANCE_FAILED_SHRINK;\n                else\n                    resid = IDS_BALANCE_FAILED;\n\n                if (!load_string(module, resid, s))\n                    throw last_error(GetLastError());\n\n                wstring_sprintf(t, s, bqb.error, format_ntstatus(bqb.error).c_str());\n\n                SetDlgItemTextW(hwndDlg, IDC_BALANCE_STATUS, t.c_str());\n            } else {\n                if (cancelling)\n                    resid = removing ? IDS_BALANCE_CANCELLED_REMOVAL : (shrinking ? IDS_BALANCE_CANCELLED_SHRINK : IDS_BALANCE_CANCELLED);\n                else if (balance_status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED))\n                    resid = removing ? IDS_BALANCE_COMPLETE_REMOVAL : (shrinking ? IDS_BALANCE_COMPLETE_SHRINK : IDS_BALANCE_COMPLETE);\n                else\n                    resid = IDS_NO_BALANCE;\n\n                if (!load_string(module, resid, s))\n                    throw last_error(GetLastError());\n\n                SetDlgItemTextW(hwndDlg, IDC_BALANCE_STATUS, s.c_str());\n            }\n\n            EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), !readonly && (IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED ||\n                         IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED || IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED) ? true: false);\n\n            balance_status = bqb.status;\n            cancelling = false;\n        }\n\n        return;\n    }\n\n    if (first || !(balance_status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED))) {\n        EnableWindow(GetDlgItem(hwndDlg, IDC_PAUSE_BALANCE), true);\n        EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BALANCE), true);\n        EnableWindow(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), true);\n        EnableWindow(GetDlgItem(hwndDlg, IDC_DATA), false);\n        EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA), false);\n        EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM), false);\n\n        CheckDlgButton(hwndDlg, IDC_DATA, bqb.data_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? BST_CHECKED : BST_UNCHECKED);\n        CheckDlgButton(hwndDlg, IDC_METADATA, bqb.metadata_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? BST_CHECKED : BST_UNCHECKED);\n        CheckDlgButton(hwndDlg, IDC_SYSTEM, bqb.system_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? BST_CHECKED : BST_UNCHECKED);\n\n        EnableWindow(GetDlgItem(hwndDlg, IDC_DATA_OPTIONS), bqb.data_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? true : false);\n        EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA_OPTIONS), bqb.metadata_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? true : false);\n        EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM_OPTIONS), bqb.system_opts.flags & BTRFS_BALANCE_OPTS_ENABLED ? true : false);\n\n        EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), false);\n    }\n\n    SendMessageW(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), PBM_SETRANGE32, 0, (LPARAM)bqb.total_chunks);\n    SendMessageW(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), PBM_SETPOS, (WPARAM)(bqb.total_chunks - bqb.chunks_left), 0);\n\n    if (bqb.status & BTRFS_BALANCE_PAUSED && balance_status != bqb.status)\n        SendMessageW(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), PBM_SETSTATE, PBST_PAUSED, 0);\n    else if (!(bqb.status & BTRFS_BALANCE_PAUSED) && balance_status & BTRFS_BALANCE_PAUSED)\n        SendMessageW(GetDlgItem(hwndDlg, IDC_BALANCE_PROGRESS), PBM_SETSTATE, PBST_NORMAL, 0);\n\n    balance_status = bqb.status;\n\n    if (bqb.status & BTRFS_BALANCE_REMOVAL) {\n        if (!load_string(module, balance_status & BTRFS_BALANCE_PAUSED ? IDS_BALANCE_PAUSED_REMOVAL : IDS_BALANCE_RUNNING_REMOVAL, s))\n            throw last_error(GetLastError());\n\n        wstring_sprintf(t, s, bqb.data_opts.devid, bqb.total_chunks - bqb.chunks_left, bqb.total_chunks,\n                        (float)(bqb.total_chunks - bqb.chunks_left) * 100.0f / (float)bqb.total_chunks);\n\n        removing = true;\n        shrinking = false;\n    } else if (bqb.status & BTRFS_BALANCE_SHRINKING) {\n        if (!load_string(module, balance_status & BTRFS_BALANCE_PAUSED ? IDS_BALANCE_PAUSED_SHRINK : IDS_BALANCE_RUNNING_SHRINK, s))\n            throw last_error(GetLastError());\n\n        wstring_sprintf(t, s, bqb.data_opts.devid, bqb.total_chunks - bqb.chunks_left, bqb.total_chunks,\n                        (float)(bqb.total_chunks - bqb.chunks_left) * 100.0f / (float)bqb.total_chunks);\n\n        removing = false;\n        shrinking = true;\n    } else {\n        if (!load_string(module, balance_status & BTRFS_BALANCE_PAUSED ? IDS_BALANCE_PAUSED : IDS_BALANCE_RUNNING, s))\n            throw last_error(GetLastError());\n\n        wstring_sprintf(t, s, bqb.total_chunks - bqb.chunks_left, bqb.total_chunks,\n                        (float)(bqb.total_chunks - bqb.chunks_left) * 100.0f / (float)bqb.total_chunks);\n\n        removing = false;\n        shrinking = false;\n    }\n\n    SetDlgItemTextW(hwndDlg, IDC_BALANCE_STATUS, t.c_str());\n}\n\nvoid BtrfsBalance::SaveBalanceOpts(HWND hwndDlg) {\n    btrfs_balance_opts* opts;\n\n    switch (opts_type) {\n        case 1:\n            opts = &data_opts;\n        break;\n\n        case 2:\n            opts = &metadata_opts;\n        break;\n\n        case 3:\n            opts = &system_opts;\n        break;\n\n        default:\n            return;\n    }\n\n    RtlZeroMemory(opts, sizeof(btrfs_balance_opts));\n\n    if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES) == BST_CHECKED) {\n        opts->flags |= BTRFS_BALANCE_OPTS_PROFILES;\n\n        if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_SINGLE) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_SINGLE;\n        if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_DUP) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_DUPLICATE;\n        if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID0) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID0;\n        if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID1) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID1;\n        if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID10) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID10;\n        if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID5) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID5;\n        if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID6) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID6;\n        if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID1C3) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID1C3;\n        if (IsDlgButtonChecked(hwndDlg, IDC_PROFILES_RAID1C4) == BST_CHECKED) opts->profiles |= BLOCK_FLAG_RAID1C4;\n    }\n\n    if (IsDlgButtonChecked(hwndDlg, IDC_DEVID) == BST_CHECKED) {\n        opts->flags |= BTRFS_BALANCE_OPTS_DEVID;\n\n        auto sel = SendMessageW(GetDlgItem(hwndDlg, IDC_DEVID_COMBO), CB_GETCURSEL, 0, 0);\n\n        if (sel == CB_ERR)\n            opts->flags &= ~BTRFS_BALANCE_OPTS_DEVID;\n        else {\n            btrfs_device* bd = devices;\n            int i = 0;\n\n            while (true) {\n                if (i == sel) {\n                    opts->devid = bd->dev_id;\n                    break;\n                }\n\n                i++;\n\n                if (bd->next_entry > 0)\n                    bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry);\n                else\n                    break;\n            }\n\n            if (opts->devid == 0)\n                opts->flags &= ~BTRFS_BALANCE_OPTS_DEVID;\n        }\n    }\n\n    if (IsDlgButtonChecked(hwndDlg, IDC_DRANGE) == BST_CHECKED) {\n        WCHAR s[255];\n\n        opts->flags |= BTRFS_BALANCE_OPTS_DRANGE;\n\n        GetWindowTextW(GetDlgItem(hwndDlg, IDC_DRANGE_START), s, sizeof(s) / sizeof(WCHAR));\n        opts->drange_start = _wtoi64(s);\n\n        GetWindowTextW(GetDlgItem(hwndDlg, IDC_DRANGE_END), s, sizeof(s) / sizeof(WCHAR));\n        opts->drange_end = _wtoi64(s);\n\n        if (opts->drange_end < opts->drange_start)\n            throw string_error(IDS_DRANGE_END_BEFORE_START);\n    }\n\n    if (IsDlgButtonChecked(hwndDlg, IDC_VRANGE) == BST_CHECKED) {\n        WCHAR s[255];\n\n        opts->flags |= BTRFS_BALANCE_OPTS_VRANGE;\n\n        GetWindowTextW(GetDlgItem(hwndDlg, IDC_VRANGE_START), s, sizeof(s) / sizeof(WCHAR));\n        opts->vrange_start = _wtoi64(s);\n\n        GetWindowTextW(GetDlgItem(hwndDlg, IDC_VRANGE_END), s, sizeof(s) / sizeof(WCHAR));\n        opts->vrange_end = _wtoi64(s);\n\n        if (opts->vrange_end < opts->vrange_start)\n            throw string_error(IDS_VRANGE_END_BEFORE_START);\n    }\n\n    if (IsDlgButtonChecked(hwndDlg, IDC_LIMIT) == BST_CHECKED) {\n        WCHAR s[255];\n\n        opts->flags |= BTRFS_BALANCE_OPTS_LIMIT;\n\n        GetWindowTextW(GetDlgItem(hwndDlg, IDC_LIMIT_START), s, sizeof(s) / sizeof(WCHAR));\n        opts->limit_start = _wtoi64(s);\n\n        GetWindowTextW(GetDlgItem(hwndDlg, IDC_LIMIT_END), s, sizeof(s) / sizeof(WCHAR));\n        opts->limit_end = _wtoi64(s);\n\n        if (opts->limit_end < opts->limit_start)\n            throw string_error(IDS_LIMIT_END_BEFORE_START);\n    }\n\n    if (IsDlgButtonChecked(hwndDlg, IDC_STRIPES) == BST_CHECKED) {\n        WCHAR s[255];\n\n        opts->flags |= BTRFS_BALANCE_OPTS_STRIPES;\n\n        GetWindowTextW(GetDlgItem(hwndDlg, IDC_STRIPES_START), s, sizeof(s) / sizeof(WCHAR));\n        opts->stripes_start = (uint8_t)_wtoi(s);\n\n        GetWindowTextW(GetDlgItem(hwndDlg, IDC_STRIPES_END), s, sizeof(s) / sizeof(WCHAR));\n        opts->stripes_end = (uint8_t)_wtoi(s);\n\n        if (opts->stripes_end < opts->stripes_start)\n            throw string_error(IDS_STRIPES_END_BEFORE_START);\n    }\n\n    if (IsDlgButtonChecked(hwndDlg, IDC_USAGE) == BST_CHECKED) {\n        WCHAR s[255];\n\n        opts->flags |= BTRFS_BALANCE_OPTS_USAGE;\n\n        GetWindowTextW(GetDlgItem(hwndDlg, IDC_USAGE_START), s, sizeof(s) / sizeof(WCHAR));\n        opts->usage_start = (uint8_t)_wtoi(s);\n\n        GetWindowTextW(GetDlgItem(hwndDlg, IDC_USAGE_END), s, sizeof(s) / sizeof(WCHAR));\n        opts->usage_end = (uint8_t)_wtoi(s);\n\n        if (opts->usage_end < opts->usage_start)\n            throw string_error(IDS_USAGE_END_BEFORE_START);\n    }\n\n    if (IsDlgButtonChecked(hwndDlg, IDC_CONVERT) == BST_CHECKED) {\n        opts->flags |= BTRFS_BALANCE_OPTS_CONVERT;\n\n        auto sel = SendMessageW(GetDlgItem(hwndDlg, IDC_CONVERT_COMBO), CB_GETCURSEL, 0, 0);\n\n        if (sel == CB_ERR || (unsigned int)sel >= sizeof(convtypes2) / sizeof(convtypes2[0]))\n            opts->flags &= ~BTRFS_BALANCE_OPTS_CONVERT;\n        else {\n            opts->convert = convtypes2[sel];\n\n            if (IsDlgButtonChecked(hwndDlg, IDC_SOFT) == BST_CHECKED) opts->flags |= BTRFS_BALANCE_OPTS_SOFT;\n        }\n    }\n\n    EndDialog(hwndDlg, 0);\n}\n\nINT_PTR CALLBACK BtrfsBalance::BalanceOptsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n            {\n                HWND devcb, convcb;\n                btrfs_device* bd;\n                btrfs_balance_opts* opts;\n                static int convtypes[] = { IDS_SINGLE2, IDS_DUP, IDS_RAID0, IDS_RAID1, IDS_RAID5, IDS_RAID1C3, IDS_RAID6, IDS_RAID10, IDS_RAID1C4, 0 };\n                int i, num_devices = 0, num_writeable_devices = 0;\n                wstring s, u;\n                bool balance_started = balance_status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED);\n\n                switch (opts_type) {\n                    case 1:\n                        opts = balance_started ? &bqb.data_opts : &data_opts;\n                    break;\n\n                    case 2:\n                        opts = balance_started ? &bqb.metadata_opts : &metadata_opts;\n                    break;\n\n                    case 3:\n                        opts = balance_started ? &bqb.system_opts : &system_opts;\n                    break;\n\n                    default:\n                        return true;\n                }\n\n                EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB);\n\n                devcb = GetDlgItem(hwndDlg, IDC_DEVID_COMBO);\n\n                if (!load_string(module, IDS_DEVID_LIST, u))\n                    throw last_error(GetLastError());\n\n                bd = devices;\n                while (true) {\n                    wstring t, v;\n\n                    if (bd->device_number == 0xffffffff)\n                        s = wstring(bd->name, bd->namelen);\n                    else if (bd->partition_number == 0) {\n                        if (!load_string(module, IDS_DISK_NUM, v))\n                            throw last_error(GetLastError());\n\n                        wstring_sprintf(s, v, bd->device_number);\n                    } else {\n                        if (!load_string(module, IDS_DISK_PART_NUM, v))\n                            throw last_error(GetLastError());\n\n                        wstring_sprintf(s, v, bd->device_number, bd->partition_number);\n                    }\n\n                    wstring_sprintf(t, u, bd->dev_id, s.c_str());\n\n                    SendMessageW(devcb, CB_ADDSTRING, 0, (LPARAM)t.c_str());\n\n                    if (opts->devid == bd->dev_id)\n                        SendMessageW(devcb, CB_SETCURSEL, num_devices, 0);\n\n                    num_devices++;\n\n                    if (!bd->readonly)\n                        num_writeable_devices++;\n\n                    if (bd->next_entry > 0)\n                        bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry);\n                    else\n                        break;\n                }\n\n                convcb = GetDlgItem(hwndDlg, IDC_CONVERT_COMBO);\n\n                if (num_writeable_devices == 0)\n                    num_writeable_devices = num_devices;\n\n                i = 0;\n                while (convtypes[i] != 0) {\n                    if (!load_string(module, convtypes[i], s))\n                        throw last_error(GetLastError());\n\n                    SendMessageW(convcb, CB_ADDSTRING, 0, (LPARAM)s.c_str());\n\n                    if (opts->convert == convtypes2[i])\n                        SendMessageW(convcb, CB_SETCURSEL, i, 0);\n\n                    i++;\n\n                    if (num_writeable_devices < 2 && i == 2)\n                        break;\n                    else if (num_writeable_devices < 3 && i == 4)\n                        break;\n                    else if (num_writeable_devices < 4 && i == 6)\n                        break;\n                }\n\n                // profiles\n\n                CheckDlgButton(hwndDlg, IDC_PROFILES, opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? BST_CHECKED : BST_UNCHECKED);\n                CheckDlgButton(hwndDlg, IDC_PROFILES_SINGLE, opts->profiles & BLOCK_FLAG_SINGLE ? BST_CHECKED : BST_UNCHECKED);\n                CheckDlgButton(hwndDlg, IDC_PROFILES_DUP, opts->profiles & BLOCK_FLAG_DUPLICATE ? BST_CHECKED : BST_UNCHECKED);\n                CheckDlgButton(hwndDlg, IDC_PROFILES_RAID0, opts->profiles & BLOCK_FLAG_RAID0 ? BST_CHECKED : BST_UNCHECKED);\n                CheckDlgButton(hwndDlg, IDC_PROFILES_RAID1, opts->profiles & BLOCK_FLAG_RAID1 ? BST_CHECKED : BST_UNCHECKED);\n                CheckDlgButton(hwndDlg, IDC_PROFILES_RAID10, opts->profiles & BLOCK_FLAG_RAID10 ? BST_CHECKED : BST_UNCHECKED);\n                CheckDlgButton(hwndDlg, IDC_PROFILES_RAID5, opts->profiles & BLOCK_FLAG_RAID5 ? BST_CHECKED : BST_UNCHECKED);\n                CheckDlgButton(hwndDlg, IDC_PROFILES_RAID6, opts->profiles & BLOCK_FLAG_RAID6 ? BST_CHECKED : BST_UNCHECKED);\n                CheckDlgButton(hwndDlg, IDC_PROFILES_RAID1C3, opts->profiles & BLOCK_FLAG_RAID1C3 ? BST_CHECKED : BST_UNCHECKED);\n                CheckDlgButton(hwndDlg, IDC_PROFILES_RAID1C4, opts->profiles & BLOCK_FLAG_RAID1C4 ? BST_CHECKED : BST_UNCHECKED);\n\n                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_SINGLE), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_DUP), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID0), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID1), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID10), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID5), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID6), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID1C3), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID1C4), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_PROFILES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES), balance_started ? false : true);\n\n                // usage\n\n                CheckDlgButton(hwndDlg, IDC_USAGE, opts->flags & BTRFS_BALANCE_OPTS_USAGE ? BST_CHECKED : BST_UNCHECKED);\n\n                s = to_wstring(opts->usage_start);\n                SetDlgItemTextW(hwndDlg, IDC_USAGE_START, s.c_str());\n                SendMessageW(GetDlgItem(hwndDlg, IDC_USAGE_START_SPINNER), UDM_SETRANGE32, 0, 100);\n\n                s = to_wstring(opts->usage_end);\n                SetDlgItemTextW(hwndDlg, IDC_USAGE_END, s.c_str());\n                SendMessageW(GetDlgItem(hwndDlg, IDC_USAGE_END_SPINNER), UDM_SETRANGE32, 0, 100);\n\n                EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_START), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_USAGE ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_START_SPINNER), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_USAGE ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_END), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_USAGE ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_END_SPINNER), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_USAGE ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE), balance_started ? false : true);\n\n                // devid\n\n                if (num_devices < 2 || balance_started)\n                    EnableWindow(GetDlgItem(hwndDlg, IDC_DEVID), false);\n\n                CheckDlgButton(hwndDlg, IDC_DEVID, opts->flags & BTRFS_BALANCE_OPTS_DEVID ? BST_CHECKED : BST_UNCHECKED);\n                EnableWindow(devcb, (opts->flags & BTRFS_BALANCE_OPTS_DEVID && num_devices >= 2 && !balance_started) ? true : false);\n\n                // drange\n\n                CheckDlgButton(hwndDlg, IDC_DRANGE, opts->flags & BTRFS_BALANCE_OPTS_DRANGE ? BST_CHECKED : BST_UNCHECKED);\n\n                s = to_wstring(opts->drange_start);\n                SetDlgItemTextW(hwndDlg, IDC_DRANGE_START, s.c_str());\n\n                s = to_wstring(opts->drange_end);\n                SetDlgItemTextW(hwndDlg, IDC_DRANGE_END, s.c_str());\n\n                EnableWindow(GetDlgItem(hwndDlg, IDC_DRANGE_START), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_DRANGE ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_DRANGE_END), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_DRANGE ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_DRANGE), balance_started ? false : true);\n\n                // vrange\n\n                CheckDlgButton(hwndDlg, IDC_VRANGE, opts->flags & BTRFS_BALANCE_OPTS_VRANGE ? BST_CHECKED : BST_UNCHECKED);\n\n                s = to_wstring(opts->vrange_start);\n                SetDlgItemTextW(hwndDlg, IDC_VRANGE_START, s.c_str());\n\n                s = to_wstring(opts->vrange_end);\n                SetDlgItemTextW(hwndDlg, IDC_VRANGE_END, s.c_str());\n\n                EnableWindow(GetDlgItem(hwndDlg, IDC_VRANGE_START), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_VRANGE ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_VRANGE_END), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_VRANGE ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_VRANGE), balance_started ? false : true);\n\n                // limit\n\n                CheckDlgButton(hwndDlg, IDC_LIMIT, opts->flags & BTRFS_BALANCE_OPTS_LIMIT ? BST_CHECKED : BST_UNCHECKED);\n\n                s = to_wstring(opts->limit_start);\n                SetDlgItemTextW(hwndDlg, IDC_LIMIT_START, s.c_str());\n                SendMessageW(GetDlgItem(hwndDlg, IDC_LIMIT_START_SPINNER), UDM_SETRANGE32, 0, 0x7fffffff);\n\n                s = to_wstring(opts->limit_end);\n                SetDlgItemTextW(hwndDlg, IDC_LIMIT_END, s.c_str());\n                SendMessageW(GetDlgItem(hwndDlg, IDC_LIMIT_END_SPINNER), UDM_SETRANGE32, 0, 0x7fffffff);\n\n                EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_START), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_LIMIT ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_START_SPINNER), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_LIMIT ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_END), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_LIMIT ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_END_SPINNER), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_LIMIT ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT), balance_started ? false : true);\n\n                // stripes\n\n                CheckDlgButton(hwndDlg, IDC_STRIPES, opts->flags & BTRFS_BALANCE_OPTS_STRIPES ? BST_CHECKED : BST_UNCHECKED);\n\n                s = to_wstring(opts->stripes_start);\n                SetDlgItemTextW(hwndDlg, IDC_STRIPES_START, s.c_str());\n                SendMessageW(GetDlgItem(hwndDlg, IDC_STRIPES_START_SPINNER), UDM_SETRANGE32, 0, 0xffff);\n\n                s = to_wstring(opts->stripes_end);\n                SetDlgItemTextW(hwndDlg, IDC_STRIPES_END, s.c_str());\n                SendMessageW(GetDlgItem(hwndDlg, IDC_STRIPES_END_SPINNER), UDM_SETRANGE32, 0, 0xffff);\n\n                EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_START), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_STRIPES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_START_SPINNER), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_STRIPES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_END), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_STRIPES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_END_SPINNER), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_STRIPES ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES), balance_started ? false : true);\n\n                // convert\n\n                CheckDlgButton(hwndDlg, IDC_CONVERT, opts->flags & BTRFS_BALANCE_OPTS_CONVERT ? BST_CHECKED : BST_UNCHECKED);\n                CheckDlgButton(hwndDlg, IDC_SOFT, opts->flags & BTRFS_BALANCE_OPTS_SOFT ? BST_CHECKED : BST_UNCHECKED);\n\n                EnableWindow(GetDlgItem(hwndDlg, IDC_SOFT), !balance_started && opts->flags & BTRFS_BALANCE_OPTS_CONVERT ? true : false);\n                EnableWindow(convcb, !balance_started && opts->flags & BTRFS_BALANCE_OPTS_CONVERT ? true : false);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_CONVERT), balance_started ? false : true);\n\n                break;\n            }\n\n            case WM_COMMAND:\n                switch (HIWORD(wParam)) {\n                    case BN_CLICKED:\n                        switch (LOWORD(wParam)) {\n                            case IDOK:\n                                if (balance_status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED))\n                                    EndDialog(hwndDlg, 0);\n                                else\n                                    SaveBalanceOpts(hwndDlg);\n                            return true;\n\n                            case IDCANCEL:\n                                EndDialog(hwndDlg, 0);\n                            return true;\n\n                            case IDC_PROFILES: {\n                                bool enabled = IsDlgButtonChecked(hwndDlg, IDC_PROFILES) == BST_CHECKED ? true : false;\n\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_SINGLE), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_DUP), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID0), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID1), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID10), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID5), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID6), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID1C3), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_PROFILES_RAID1C4), enabled);\n                                break;\n                            }\n\n                            case IDC_USAGE: {\n                                bool enabled = IsDlgButtonChecked(hwndDlg, IDC_USAGE) == BST_CHECKED ? true : false;\n\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_START), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_START_SPINNER), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_END), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_USAGE_END_SPINNER), enabled);\n                                break;\n                            }\n\n                            case IDC_DEVID: {\n                                bool enabled = IsDlgButtonChecked(hwndDlg, IDC_DEVID) == BST_CHECKED ? true : false;\n\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_DEVID_COMBO), enabled);\n                                break;\n                            }\n\n                            case IDC_DRANGE: {\n                                bool enabled = IsDlgButtonChecked(hwndDlg, IDC_DRANGE) == BST_CHECKED ? true : false;\n\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_DRANGE_START), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_DRANGE_END), enabled);\n                                break;\n                            }\n\n                            case IDC_VRANGE: {\n                                bool enabled = IsDlgButtonChecked(hwndDlg, IDC_VRANGE) == BST_CHECKED ? true : false;\n\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_VRANGE_START), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_VRANGE_END), enabled);\n                                break;\n                            }\n\n                            case IDC_LIMIT: {\n                                bool enabled = IsDlgButtonChecked(hwndDlg, IDC_LIMIT) == BST_CHECKED ? true : false;\n\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_START), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_START_SPINNER), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_END), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_LIMIT_END_SPINNER), enabled);\n                                break;\n                            }\n\n                            case IDC_STRIPES: {\n                                bool enabled = IsDlgButtonChecked(hwndDlg, IDC_STRIPES) == BST_CHECKED ? true : false;\n\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_START), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_START_SPINNER), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_END), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_STRIPES_END_SPINNER), enabled);\n                                break;\n                            }\n\n                            case IDC_CONVERT: {\n                                bool enabled = IsDlgButtonChecked(hwndDlg, IDC_CONVERT) == BST_CHECKED ? true : false;\n\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_CONVERT_COMBO), enabled);\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_SOFT), enabled);\n                                break;\n                            }\n                        }\n                    break;\n                }\n            break;\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nstatic INT_PTR CALLBACK stub_BalanceOptsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    BtrfsBalance* bb;\n\n    if (uMsg == WM_INITDIALOG) {\n        SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);\n        bb = (BtrfsBalance*)lParam;\n    } else {\n        bb = (BtrfsBalance*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n    }\n\n    if (bb)\n        return bb->BalanceOptsDlgProc(hwndDlg, uMsg, wParam, lParam);\n    else\n        return false;\n}\n\nvoid BtrfsBalance::ShowBalanceOptions(HWND hwndDlg, uint8_t type) {\n    opts_type = type;\n    DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_BALANCE_OPTIONS), hwndDlg, stub_BalanceOptsDlgProc, (LPARAM)this);\n}\n\nINT_PTR CALLBACK BtrfsBalance::BalanceDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n            {\n                EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB);\n\n                RtlZeroMemory(&data_opts, sizeof(btrfs_balance_opts));\n                RtlZeroMemory(&metadata_opts, sizeof(btrfs_balance_opts));\n                RtlZeroMemory(&system_opts, sizeof(btrfs_balance_opts));\n\n                removing = called_from_RemoveDevice;\n                shrinking = called_from_ShrinkDevice;\n                balance_status = (removing || shrinking) ? BTRFS_BALANCE_RUNNING : BTRFS_BALANCE_STOPPED;\n                cancelling = false;\n                RefreshBalanceDlg(hwndDlg, true);\n\n                if (readonly) {\n                    EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), false);\n                    EnableWindow(GetDlgItem(hwndDlg, IDC_PAUSE_BALANCE), false);\n                    EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_BALANCE), false);\n                }\n\n                SendMessageW(GetDlgItem(hwndDlg, IDC_START_BALANCE), BCM_SETSHIELD, 0, true);\n                SendMessageW(GetDlgItem(hwndDlg, IDC_PAUSE_BALANCE), BCM_SETSHIELD, 0, true);\n                SendMessageW(GetDlgItem(hwndDlg, IDC_CANCEL_BALANCE), BCM_SETSHIELD, 0, true);\n\n                SetTimer(hwndDlg, 1, 1000, nullptr);\n\n                break;\n            }\n\n            case WM_COMMAND:\n                switch (HIWORD(wParam)) {\n                    case BN_CLICKED:\n                        switch (LOWORD(wParam)) {\n                            case IDOK:\n                            case IDCANCEL:\n                                KillTimer(hwndDlg, 1);\n                                EndDialog(hwndDlg, 0);\n                            return true;\n\n                            case IDC_DATA:\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_DATA_OPTIONS), IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED ? true : false);\n\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), !readonly && (IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED ||\n                                IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED || IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED) ? true: false);\n                            return true;\n\n                            case IDC_METADATA:\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_METADATA_OPTIONS), IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED ? true : false);\n\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), !readonly && (IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED ||\n                                IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED || IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED) ? true: false);\n                            return true;\n\n                            case IDC_SYSTEM:\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_SYSTEM_OPTIONS), IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED ? true : false);\n\n                                EnableWindow(GetDlgItem(hwndDlg, IDC_START_BALANCE), !readonly && (IsDlgButtonChecked(hwndDlg, IDC_DATA) == BST_CHECKED ||\n                                IsDlgButtonChecked(hwndDlg, IDC_METADATA) == BST_CHECKED || IsDlgButtonChecked(hwndDlg, IDC_SYSTEM) == BST_CHECKED) ? true: false);\n                            return true;\n\n                            case IDC_DATA_OPTIONS:\n                                ShowBalanceOptions(hwndDlg, 1);\n                            return true;\n\n                            case IDC_METADATA_OPTIONS:\n                                ShowBalanceOptions(hwndDlg, 2);\n                            return true;\n\n                            case IDC_SYSTEM_OPTIONS:\n                                ShowBalanceOptions(hwndDlg, 3);\n                            return true;\n\n                            case IDC_START_BALANCE:\n                                StartBalance(hwndDlg);\n                            return true;\n\n                            case IDC_PAUSE_BALANCE:\n                                PauseBalance(hwndDlg);\n                                RefreshBalanceDlg(hwndDlg, false);\n                            return true;\n\n                            case IDC_CANCEL_BALANCE:\n                                StopBalance(hwndDlg);\n                                RefreshBalanceDlg(hwndDlg, false);\n                            return true;\n                        }\n                    break;\n                }\n            break;\n\n            case WM_TIMER:\n                RefreshBalanceDlg(hwndDlg, false);\n                break;\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nstatic INT_PTR CALLBACK stub_BalanceDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    BtrfsBalance* bb;\n\n    if (uMsg == WM_INITDIALOG) {\n        SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);\n        bb = (BtrfsBalance*)lParam;\n    } else {\n        bb = (BtrfsBalance*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n    }\n\n    if (bb)\n        return bb->BalanceDlgProc(hwndDlg, uMsg, wParam, lParam);\n    else\n        return false;\n}\n\nvoid BtrfsBalance::ShowBalance(HWND hwndDlg) {\n    btrfs_device* bd;\n\n    if (devices) {\n        free(devices);\n        devices = nullptr;\n    }\n\n    {\n        win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                   OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n        if (h != INVALID_HANDLE_VALUE) {\n            NTSTATUS Status;\n            IO_STATUS_BLOCK iosb;\n            ULONG devsize, i;\n\n            i = 0;\n            devsize = 1024;\n\n            devices = (btrfs_device*)malloc(devsize);\n\n            while (true) {\n                Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_DEVICES, nullptr, 0, devices, devsize);\n                if (Status == STATUS_BUFFER_OVERFLOW) {\n                    if (i < 8) {\n                        devsize += 1024;\n\n                        free(devices);\n                        devices = (btrfs_device*)malloc(devsize);\n\n                        i++;\n                    } else\n                        return;\n                } else\n                    break;\n            }\n\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n        } else\n            throw last_error(GetLastError());\n    }\n\n    readonly = true;\n    bd = devices;\n\n    while (true) {\n        if (!bd->readonly) {\n            readonly = false;\n            break;\n        }\n\n        if (bd->next_entry > 0)\n            bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry);\n        else\n            break;\n    }\n\n    DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_BALANCE), hwndDlg, stub_BalanceDlgProc, (LPARAM)this);\n}\n\nstatic uint8_t from_hex_digit(WCHAR c) {\n    if (c >= 'a' && c <= 'f')\n        return (uint8_t)(c - 'a' + 0xa);\n    else if (c >= 'A' && c <= 'F')\n        return (uint8_t)(c - 'A' + 0xa);\n    else\n        return (uint8_t)(c - '0');\n}\n\nstatic void unserialize(void* data, ULONG len, WCHAR* s) {\n    uint8_t* d;\n\n    d = (uint8_t*)data;\n\n    while (s[0] != 0 && s[1] != 0 && len > 0) {\n        *d = (uint8_t)(from_hex_digit(s[0]) << 4) | from_hex_digit(s[1]);\n\n        s += 2;\n        d++;\n        len--;\n    }\n}\n\nextern \"C\" void CALLBACK StartBalanceW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    try {\n        WCHAR *s, *vol, *block;\n        win_handle h, token;\n        btrfs_start_balance bsb;\n        TOKEN_PRIVILEGES tp;\n        LUID luid;\n\n        s = wcsstr(lpszCmdLine, L\" \");\n        if (!s)\n            return;\n\n        s[0] = 0;\n\n        vol = lpszCmdLine;\n        block = &s[1];\n\n        RtlZeroMemory(&bsb, sizeof(btrfs_start_balance));\n        unserialize(&bsb, sizeof(btrfs_start_balance), block);\n\n        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n            throw last_error(GetLastError());\n\n        if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid))\n            throw last_error(GetLastError());\n\n        tp.PrivilegeCount = 1;\n        tp.Privileges[0].Luid = luid;\n        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n        if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))\n            throw last_error(GetLastError());\n\n        h = CreateFileW(vol, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                            OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n        if (h != INVALID_HANDLE_VALUE) {\n            NTSTATUS Status;\n            IO_STATUS_BLOCK iosb;\n\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_START_BALANCE, &bsb, sizeof(btrfs_start_balance), nullptr, 0);\n\n            if (Status == STATUS_DEVICE_NOT_READY) {\n                btrfs_query_scrub bqs;\n                NTSTATUS Status2;\n\n                Status2 = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_SCRUB, nullptr, 0, &bqs, sizeof(btrfs_query_scrub));\n\n                if ((NT_SUCCESS(Status2) || Status2 == STATUS_BUFFER_OVERFLOW) && bqs.status != BTRFS_SCRUB_STOPPED)\n                    throw string_error(IDS_BALANCE_SCRUB_RUNNING);\n            }\n\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n        } else\n            throw last_error(GetLastError());\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n}\n\nextern \"C\" void CALLBACK PauseBalanceW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    try {\n        win_handle h, token;\n        TOKEN_PRIVILEGES tp;\n        LUID luid;\n\n        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n            throw last_error(GetLastError());\n\n        if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid))\n            throw last_error(GetLastError());\n\n        tp.PrivilegeCount = 1;\n        tp.Privileges[0].Luid = luid;\n        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n        if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))\n            throw last_error(GetLastError());\n\n        h = CreateFileW(lpszCmdLine, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                        OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n        if (h != INVALID_HANDLE_VALUE) {\n            NTSTATUS Status;\n            IO_STATUS_BLOCK iosb;\n            btrfs_query_balance bqb2;\n\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_BALANCE, nullptr, 0, &bqb2, sizeof(btrfs_query_balance));\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n\n            if (bqb2.status & BTRFS_BALANCE_PAUSED)\n                Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RESUME_BALANCE, nullptr, 0, nullptr, 0);\n            else if (bqb2.status & BTRFS_BALANCE_RUNNING)\n                Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_PAUSE_BALANCE, nullptr, 0, nullptr, 0);\n            else\n                return;\n\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n        } else\n            throw last_error(GetLastError());\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n}\n\nextern \"C\" void CALLBACK StopBalanceW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    try {\n        win_handle h, token;\n        TOKEN_PRIVILEGES tp;\n        LUID luid;\n\n        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n            throw last_error(GetLastError());\n\n        if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid))\n            throw last_error(GetLastError());\n\n        tp.PrivilegeCount = 1;\n        tp.Privileges[0].Luid = luid;\n        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n        if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))\n            throw last_error(GetLastError());\n\n        h = CreateFileW(lpszCmdLine, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                        OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n        if (h != INVALID_HANDLE_VALUE) {\n            NTSTATUS Status;\n            IO_STATUS_BLOCK iosb;\n            btrfs_query_balance bqb2;\n\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_BALANCE, nullptr, 0, &bqb2, sizeof(btrfs_query_balance));\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n\n            if (bqb2.status & BTRFS_BALANCE_PAUSED || bqb2.status & BTRFS_BALANCE_RUNNING)\n                Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_STOP_BALANCE, nullptr, 0, nullptr, 0);\n            else\n                return;\n\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n        } else\n            throw last_error(GetLastError());\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n}\n"
  },
  {
    "path": "src/shellext/balance.h",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\n#include <windows.h>\n#include \"../btrfsioctl.h\"\n\nclass BtrfsBalance {\npublic:\n    BtrfsBalance(const wstring& drive, bool RemoveDevice = false, bool ShrinkDevice = false) {\n        removing = false;\n        devices = nullptr;\n        called_from_RemoveDevice = RemoveDevice;\n        called_from_ShrinkDevice = ShrinkDevice;\n        fn = drive;\n    }\n\n    void ShowBalance(HWND hwndDlg);\n    INT_PTR CALLBACK BalanceDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);\n    INT_PTR CALLBACK BalanceOptsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);\n\nprivate:\n    void ShowBalanceOptions(HWND hwndDlg, uint8_t type);\n    void SaveBalanceOpts(HWND hwndDlg);\n    void StartBalance(HWND hwndDlg);\n    void RefreshBalanceDlg(HWND hwndDlg, bool first);\n    void PauseBalance(HWND hwndDlg);\n    void StopBalance(HWND hwndDlg);\n\n    uint32_t balance_status;\n    btrfs_balance_opts data_opts, metadata_opts, system_opts;\n    uint8_t opts_type;\n    btrfs_query_balance bqb;\n    bool cancelling;\n    bool removing;\n    bool shrinking;\n    wstring fn;\n    btrfs_device* devices;\n    bool readonly;\n    bool called_from_RemoveDevice, called_from_ShrinkDevice;\n};\n"
  },
  {
    "path": "src/shellext/contextmenu.cpp",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"shellext.h\"\n#include <windows.h>\n#include <strsafe.h>\n#include <stddef.h>\n#include <winternl.h>\n#include <wincodec.h>\n#include <sstream>\n#include <iostream>\n\n#define NO_SHLWAPI_STRFCNS\n#include <shlwapi.h>\n\n#include \"contextmenu.h\"\n#include \"resource.h\"\n#include \"../btrfsioctl.h\"\n\n#define NEW_SUBVOL_VERBA \"newsubvol\"\n#define NEW_SUBVOL_VERBW L\"newsubvol\"\n#define SNAPSHOT_VERBA \"snapshot\"\n#define SNAPSHOT_VERBW L\"snapshot\"\n#define REFLINK_VERBA \"reflink\"\n#define REFLINK_VERBW L\"reflink\"\n#define RECV_VERBA \"recvsubvol\"\n#define RECV_VERBW L\"recvsubvol\"\n#define SEND_VERBA \"sendsubvol\"\n#define SEND_VERBW L\"sendsubvol\"\n\ntypedef struct {\n    ULONG  ReparseTag;\n    USHORT ReparseDataLength;\n    USHORT Reserved;\n} reparse_header;\n\nstatic void path_remove_file(wstring& path);\n\n// FIXME - don't assume subvol's top inode is 0x100\n\nHRESULT __stdcall BtrfsContextMenu::QueryInterface(REFIID riid, void **ppObj) {\n    if (riid == IID_IUnknown || riid == IID_IContextMenu) {\n        *ppObj = static_cast<IContextMenu*>(this);\n        AddRef();\n        return S_OK;\n    } else if (riid == IID_IShellExtInit) {\n        *ppObj = static_cast<IShellExtInit*>(this);\n        AddRef();\n        return S_OK;\n    }\n\n    *ppObj = nullptr;\n    return E_NOINTERFACE;\n}\n\nHRESULT __stdcall BtrfsContextMenu::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY) {\n    IO_STATUS_BLOCK iosb;\n    btrfs_get_file_ids bgfi;\n    NTSTATUS Status;\n\n    if (!pidlFolder) {\n        FORMATETC format = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };\n        UINT num_files, i;\n        WCHAR fn[MAX_PATH];\n        HDROP hdrop;\n\n        if (!pdtobj)\n            return E_FAIL;\n\n        stgm.tymed = TYMED_HGLOBAL;\n\n        if (FAILED(pdtobj->GetData(&format, &stgm)))\n            return E_INVALIDARG;\n\n        stgm_set = true;\n\n        global_lock gl(stgm.hGlobal);\n\n        hdrop = (HDROP)gl.ptr;\n\n        if (!hdrop) {\n            ReleaseStgMedium(&stgm);\n            stgm_set = false;\n            return E_INVALIDARG;\n        }\n\n        num_files = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);\n\n        for (i = 0; i < num_files; i++) {\n            if (DragQueryFileW(hdrop, i, fn, sizeof(fn) / sizeof(WCHAR))) {\n                win_handle h = CreateFileW(fn, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n\n                if (h != INVALID_HANDLE_VALUE) {\n                    Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids));\n\n                    if (NT_SUCCESS(Status) && bgfi.inode == 0x100 && !bgfi.top) {\n                        wstring parpath;\n\n                        {\n                            win_handle h2;\n\n                            parpath = fn;\n                            path_remove_file(parpath);\n\n                            h2 = CreateFileW(parpath.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                            OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n\n                            if (h2 != INVALID_HANDLE_VALUE)\n                                allow_snapshot = true;\n                        }\n\n                        ignore = false;\n                        bg = false;\n\n                        return S_OK;\n                    }\n                }\n            }\n        }\n\n        return S_OK;\n    }\n\n    {\n        WCHAR pathbuf[MAX_PATH];\n\n        if (!SHGetPathFromIDListW(pidlFolder, pathbuf))\n            return E_FAIL;\n\n        path = pathbuf;\n    }\n\n    {\n        // check we have permissions to create new subdirectory\n\n        win_handle h = CreateFileW(path.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n\n        if (h == INVALID_HANDLE_VALUE)\n            return E_FAIL;\n\n        // check is Btrfs volume\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids));\n\n        if (!NT_SUCCESS(Status))\n            return E_FAIL;\n    }\n\n    ignore = false;\n    bg = true;\n\n    return S_OK;\n}\n\nstatic bool get_volume_path_parent(const WCHAR* fn, WCHAR* volpath, ULONG volpathlen) {\n    WCHAR *f, *p;\n    bool b;\n\n    f = PathFindFileNameW(fn);\n\n    if (f == fn)\n        return GetVolumePathNameW(fn, volpath, volpathlen);\n\n    p = (WCHAR*)malloc((f - fn + 1) * sizeof(WCHAR));\n    memcpy(p, fn, (f - fn) * sizeof(WCHAR));\n    p[f - fn] = 0;\n\n    b = GetVolumePathNameW(p, volpath, volpathlen);\n\n    free(p);\n\n    return b;\n}\n\nstatic bool show_reflink_paste(const wstring& path) {\n    HDROP hdrop;\n    HANDLE lh;\n    ULONG num_files;\n    WCHAR fn[MAX_PATH], volpath1[255], volpath2[255];\n\n    if (!IsClipboardFormatAvailable(CF_HDROP))\n        return false;\n\n    if (!GetVolumePathNameW(path.c_str(), volpath1, sizeof(volpath1) / sizeof(WCHAR)))\n        return false;\n\n    if (!OpenClipboard(nullptr))\n        return false;\n\n    hdrop = (HDROP)GetClipboardData(CF_HDROP);\n\n    if (!hdrop) {\n        CloseClipboard();\n        return false;\n    }\n\n    global_lock gl(hdrop);\n\n    lh = gl.ptr;\n\n    if (!lh) {\n        CloseClipboard();\n        return false;\n    }\n\n    num_files = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);\n\n    if (num_files == 0) {\n        CloseClipboard();\n        return false;\n    }\n\n    if (!DragQueryFileW(hdrop, 0, fn, sizeof(fn) / sizeof(WCHAR))) {\n        CloseClipboard();\n        return false;\n    }\n\n    if (!get_volume_path_parent(fn, volpath2, sizeof(volpath2) / sizeof(WCHAR))) {\n        CloseClipboard();\n        return false;\n    }\n\n    CloseClipboard();\n\n    return !wcscmp(volpath1, volpath2);\n}\n\n// The code for putting an icon against a menu item comes from:\n// http://web.archive.org/web/20070208005514/http://shellrevealed.com/blogs/shellblog/archive/2007/02/06/Vista-Style-Menus_2C00_-Part-1-_2D00_-Adding-icons-to-standard-menus.aspx\n\nstatic void InitBitmapInfo(BITMAPINFO* pbmi, ULONG cbInfo, LONG cx, LONG cy, WORD bpp) {\n    ZeroMemory(pbmi, cbInfo);\n    pbmi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);\n    pbmi->bmiHeader.biPlanes = 1;\n    pbmi->bmiHeader.biCompression = BI_RGB;\n\n    pbmi->bmiHeader.biWidth = cx;\n    pbmi->bmiHeader.biHeight = cy;\n    pbmi->bmiHeader.biBitCount = bpp;\n}\n\nstatic HRESULT Create32BitHBITMAP(HDC hdc, const SIZE *psize, void **ppvBits, HBITMAP* phBmp) {\n    BITMAPINFO bmi;\n    HDC hdcUsed;\n\n    *phBmp = nullptr;\n\n    InitBitmapInfo(&bmi, sizeof(bmi), psize->cx, psize->cy, 32);\n\n    hdcUsed = hdc ? hdc : GetDC(nullptr);\n\n    if (hdcUsed) {\n        *phBmp = CreateDIBSection(hdcUsed, &bmi, DIB_RGB_COLORS, ppvBits, nullptr, 0);\n        if (hdc != hdcUsed)\n            ReleaseDC(nullptr, hdcUsed);\n    }\n\n    return !*phBmp ? E_OUTOFMEMORY : S_OK;\n}\n\nvoid BtrfsContextMenu::get_uac_icon() {\n    IWICImagingFactory* factory = nullptr;\n    IWICBitmap* bitmap;\n    HRESULT hr;\n\n    hr = CoCreateInstance(CLSID_WICImagingFactory, nullptr, CLSCTX_INPROC_SERVER, IID_PPV_ARGS(&factory));\n\n    if (SUCCEEDED(hr)) {\n        HANDLE icon;\n\n        // We can't use IDI_SHIELD, as that will only give us the full-size icon\n        icon = LoadImageW(GetModuleHandleW(L\"user32.dll\"), MAKEINTRESOURCEW(106)/* UAC shield */, IMAGE_ICON,\n                          GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), LR_DEFAULTCOLOR);\n\n        hr = factory->CreateBitmapFromHICON((HICON)icon, &bitmap);\n        if (SUCCEEDED(hr)) {\n            UINT cx, cy;\n\n            hr = bitmap->GetSize(&cx, &cy);\n            if (SUCCEEDED(hr)) {\n                SIZE sz;\n                BYTE* buf;\n\n                sz.cx = (int)cx;\n                sz.cy = -(int)cy;\n\n                hr = Create32BitHBITMAP(nullptr, &sz, (void**)&buf, &uacicon);\n                if (SUCCEEDED(hr)) {\n                    UINT stride = (UINT)(cx * sizeof(DWORD));\n                    UINT buflen = cy * stride;\n                    bitmap->CopyPixels(nullptr, stride, buflen, buf);\n                }\n            }\n\n            bitmap->Release();\n        }\n\n        factory->Release();\n    }\n}\n\nHRESULT __stdcall BtrfsContextMenu::QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) {\n    wstring str;\n    ULONG entries = 0;\n\n    if (ignore)\n        return E_INVALIDARG;\n\n    if (uFlags & CMF_DEFAULTONLY)\n        return S_OK;\n\n    if (!bg) {\n        if (allow_snapshot) {\n            if (load_string(module, IDS_CREATE_SNAPSHOT, str) == 0)\n                return E_FAIL;\n\n            if (!InsertMenuW(hmenu, indexMenu, MF_BYPOSITION, idCmdFirst, str.c_str()))\n                return E_FAIL;\n\n            entries = 1;\n        }\n\n        if (idCmdFirst + entries <= idCmdLast) {\n            MENUITEMINFOW mii;\n\n            if (load_string(module, IDS_SEND_SUBVOL, str) == 0)\n                return E_FAIL;\n\n            if (!uacicon)\n                get_uac_icon();\n\n            memset(&mii, 0, sizeof(MENUITEMINFOW));\n            mii.cbSize = sizeof(MENUITEMINFOW);\n            mii.fMask = MIIM_STRING | MIIM_ID | MIIM_BITMAP;\n            mii.dwTypeData = (WCHAR*)str.c_str();\n            mii.wID = idCmdFirst + entries;\n            mii.hbmpItem = uacicon;\n\n            if (!InsertMenuItemW(hmenu, indexMenu + entries, true, &mii))\n                return E_FAIL;\n\n            entries++;\n        }\n    } else {\n        if (load_string(module, IDS_NEW_SUBVOL, str) == 0)\n            return E_FAIL;\n\n        if (!InsertMenuW(hmenu, indexMenu, MF_BYPOSITION, idCmdFirst, str.c_str()))\n            return E_FAIL;\n\n        entries = 1;\n\n        if (idCmdFirst + 1 <= idCmdLast) {\n            MENUITEMINFOW mii;\n\n            if (load_string(module, IDS_RECV_SUBVOL, str) == 0)\n                return E_FAIL;\n\n            if (!uacicon)\n                get_uac_icon();\n\n            memset(&mii, 0, sizeof(MENUITEMINFOW));\n            mii.cbSize = sizeof(MENUITEMINFOW);\n            mii.fMask = MIIM_STRING | MIIM_ID | MIIM_BITMAP;\n            mii.dwTypeData = (WCHAR*)str.c_str();\n            mii.wID = idCmdFirst + 1;\n            mii.hbmpItem = uacicon;\n\n            if (!InsertMenuItemW(hmenu, indexMenu + 1, true, &mii))\n                return E_FAIL;\n\n            entries++;\n        }\n\n        if (idCmdFirst + 2 <= idCmdLast && show_reflink_paste(path)) {\n            if (load_string(module, IDS_REFLINK_PASTE, str) == 0)\n                return E_FAIL;\n\n            if (!InsertMenuW(hmenu, indexMenu + 2, MF_BYPOSITION, idCmdFirst + 2, str.c_str()))\n                return E_FAIL;\n\n            entries++;\n        }\n    }\n\n    return MAKE_HRESULT(SEVERITY_SUCCESS, 0, entries);\n}\n\nstatic void path_remove_file(wstring& path) {\n    size_t bs = path.rfind(L\"\\\\\");\n\n    if (bs == string::npos)\n        return;\n\n    if (bs == path.find(L\"\\\\\")) { // only one backslash\n        path = path.substr(0, bs + 1);\n        return;\n    }\n\n    path = path.substr(0, bs);\n}\n\nstatic void path_strip_path(wstring& path) {\n    size_t bs = path.rfind(L\"\\\\\");\n\n    if (bs == string::npos) {\n        path = L\"\";\n        return;\n    }\n\n    path = path.substr(bs + 1);\n}\n\nstatic void create_snapshot(HWND, const wstring& fn) {\n    win_handle h;\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    btrfs_get_file_ids bgfi;\n\n    h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n\n    if (h != INVALID_HANDLE_VALUE) {\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids));\n\n        if (NT_SUCCESS(Status) && bgfi.inode == 0x100 && !bgfi.top) {\n            wstring subvolname, parpath, searchpath, temp1, name, nameorig;\n            win_handle h2;\n            WIN32_FIND_DATAW wfd;\n            SYSTEMTIME time;\n\n            parpath = fn;\n            path_remove_file(parpath);\n\n            subvolname = fn;\n            path_strip_path(subvolname);\n\n            h2 = CreateFileW(parpath.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n\n            if (h2 == INVALID_HANDLE_VALUE)\n                throw last_error(GetLastError());\n\n            if (!load_string(module, IDS_SNAPSHOT_FILENAME, temp1))\n                throw last_error(GetLastError());\n\n            GetLocalTime(&time);\n\n            wstring_sprintf(name, temp1, subvolname.c_str(), time.wYear, time.wMonth, time.wDay);\n            nameorig = name;\n\n            searchpath = parpath + L\"\\\\\" + name;\n\n            fff_handle fff = FindFirstFileW(searchpath.c_str(), &wfd);\n\n            if (fff != INVALID_HANDLE_VALUE) {\n                ULONG num = 2;\n\n                do {\n                    name = nameorig + L\" (\" + to_wstring(num) + L\")\";\n                    searchpath = parpath + L\"\\\\\" + name;\n\n                    fff = FindFirstFileW(searchpath.c_str(), &wfd);\n                    num++;\n                } while (fff != INVALID_HANDLE_VALUE);\n            }\n\n            size_t namelen = name.length() * sizeof(WCHAR);\n\n            auto bcs = (btrfs_create_snapshot*)malloc(sizeof(btrfs_create_snapshot) - 1 + namelen);\n            bcs->readonly = false;\n            bcs->posix = false;\n            bcs->subvol = h;\n            bcs->namelen = (uint16_t)namelen;\n            memcpy(bcs->name, name.c_str(), namelen);\n\n            Status = NtFsControlFile(h2, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs,\n                                     (ULONG)(sizeof(btrfs_create_snapshot) - 1 + namelen), nullptr, 0);\n\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n        }\n    } else\n        throw last_error(GetLastError());\n}\n\nstatic uint64_t __inline sector_align(uint64_t n, uint64_t a) {\n    if (n & (a - 1))\n        n = (n + a) & ~(a - 1);\n\n    return n;\n}\n\nvoid BtrfsContextMenu::reflink_copy(HWND hwnd, const WCHAR* fn, const WCHAR* dir) {\n    win_handle source, dest;\n    WCHAR* name, volpath1[255], volpath2[255];\n    wstring dirw, newpath;\n    FILE_BASIC_INFO fbi;\n    FILETIME atime, mtime;\n    btrfs_inode_info bii;\n    btrfs_set_inode_info bsii;\n    ULONG bytesret;\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    btrfs_set_xattr bsxa;\n\n    // Thanks to 0xbadfca11, whose version of reflink for Windows provided a few pointers on what\n    // to do here - https://github.com/0xbadfca11/reflink\n\n    name = PathFindFileNameW(fn);\n\n    dirw = dir;\n\n    if (dir[0] != 0 && dir[wcslen(dir) - 1] != '\\\\')\n        dirw += L\"\\\\\";\n\n    newpath = dirw;\n    newpath += name;\n\n    if (!get_volume_path_parent(fn, volpath1, sizeof(volpath1) / sizeof(WCHAR)))\n        throw last_error(GetLastError());\n\n    if (!GetVolumePathNameW(dir, volpath2, sizeof(volpath2) / sizeof(WCHAR)))\n        throw last_error(GetLastError());\n\n    if (wcscmp(volpath1, volpath2)) // different filesystems\n        throw string_error(IDS_CANT_REFLINK_DIFFERENT_FS);\n\n    source = CreateFileW(fn, GENERIC_READ | FILE_TRAVERSE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr);\n    if (source == INVALID_HANDLE_VALUE)\n        throw last_error(GetLastError());\n\n    Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii, sizeof(btrfs_inode_info));\n    if (!NT_SUCCESS(Status))\n        throw ntstatus_error(Status);\n\n    // if subvol, do snapshot instead\n    if (bii.inode == SUBVOL_ROOT_INODE) {\n        btrfs_create_snapshot* bcs;\n        win_handle dirh;\n        wstring destname, search;\n        WIN32_FIND_DATAW wfd;\n        int num = 2;\n\n        dirh = CreateFileW(dir, FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n        if (dirh == INVALID_HANDLE_VALUE)\n            throw last_error(GetLastError());\n\n        search = dirw;\n        search += name;\n        destname = name;\n\n        fff_handle fff = FindFirstFileW(search.c_str(), &wfd);\n\n        if (fff != INVALID_HANDLE_VALUE) {\n            do {\n                wstringstream ss;\n\n                ss << name;\n                ss << L\" (\";\n                ss << num;\n                ss << L\")\";\n                destname = ss.str();\n\n                search = dirw + destname;\n\n                fff = FindFirstFileW(search.c_str(), &wfd);\n                num++;\n            } while (fff != INVALID_HANDLE_VALUE);\n        }\n\n        bcs = (btrfs_create_snapshot*)malloc(sizeof(btrfs_create_snapshot) - sizeof(WCHAR) + (destname.length() * sizeof(WCHAR)));\n        bcs->subvol = source;\n        bcs->namelen = (uint16_t)(destname.length() * sizeof(WCHAR));\n        memcpy(bcs->name, destname.c_str(), destname.length() * sizeof(WCHAR));\n\n        Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs,\n                                 (ULONG)(sizeof(btrfs_create_snapshot) - sizeof(WCHAR) + bcs->namelen), nullptr, 0);\n\n        free(bcs);\n\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n\n        return;\n    }\n\n    Status = NtQueryInformationFile(source, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation);\n    if (!NT_SUCCESS(Status))\n        throw ntstatus_error(Status);\n\n    if (bii.type == BTRFS_TYPE_CHARDEV || bii.type == BTRFS_TYPE_BLOCKDEV || bii.type == BTRFS_TYPE_FIFO || bii.type == BTRFS_TYPE_SOCKET) {\n        win_handle dirh;\n        btrfs_mknod* bmn;\n\n        dirh = CreateFileW(dir, FILE_ADD_FILE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n        if (dirh == INVALID_HANDLE_VALUE)\n            throw last_error(GetLastError());\n\n        size_t bmnsize = offsetof(btrfs_mknod, name[0]) + (wcslen(name) * sizeof(WCHAR));\n        bmn = (btrfs_mknod*)malloc(bmnsize);\n\n        bmn->inode = 0;\n        bmn->type = bii.type;\n        bmn->st_rdev = bii.st_rdev;\n        bmn->namelen = (uint16_t)(wcslen(name) * sizeof(WCHAR));\n        memcpy(bmn->name, name, bmn->namelen);\n\n        Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_MKNOD, bmn, (ULONG)bmnsize, nullptr, 0);\n        if (!NT_SUCCESS(Status)) {\n            free(bmn);\n            throw ntstatus_error(Status);\n        }\n\n        free(bmn);\n\n        dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, OPEN_EXISTING, 0, nullptr);\n    } else if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\n        if (CreateDirectoryExW(fn, newpath.c_str(), nullptr))\n            dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                               nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n        else\n            dest = INVALID_HANDLE_VALUE;\n    } else\n        dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, source);\n\n    if (dest == INVALID_HANDLE_VALUE) {\n        int num = 2;\n\n        if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_ALREADY_EXISTS && wcscmp(fn, newpath.c_str()))\n            throw last_error(GetLastError());\n\n        do {\n            WCHAR* ext;\n            wstringstream ss;\n\n            ext = PathFindExtensionW(fn);\n\n            ss << dirw;\n\n            if (*ext == 0) {\n                ss << name;\n                ss << L\" (\";\n                ss << num;\n                ss << L\")\";\n            } else {\n                wstring namew = name;\n\n                ss << namew.substr(0, ext - name);\n                ss << L\" (\";\n                ss << num;\n                ss << L\")\";\n                ss << ext;\n            }\n\n            newpath = ss.str();\n            if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\n                if (CreateDirectoryExW(fn, newpath.c_str(), nullptr))\n                    dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n                else\n                    dest = INVALID_HANDLE_VALUE;\n            } else\n                dest = CreateFileW(newpath.c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, source);\n\n            if (dest == INVALID_HANDLE_VALUE) {\n                if (GetLastError() != ERROR_FILE_EXISTS && GetLastError() != ERROR_ALREADY_EXISTS)\n                    throw last_error(GetLastError());\n\n                num++;\n            } else\n                break;\n        } while (true);\n    }\n\n    try {\n        memset(&bsii, 0, sizeof(btrfs_set_inode_info));\n\n        bsii.flags_changed = true;\n        bsii.flags = bii.flags;\n\n        if (bii.flags & BTRFS_INODE_COMPRESS) {\n            bsii.compression_type_changed = true;\n            bsii.compression_type = bii.compression_type;\n        }\n\n        Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0);\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n\n        if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\n            if (!(fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {\n                fff_handle h;\n                WIN32_FIND_DATAW fff;\n                wstring qs;\n\n                qs = fn;\n                qs += L\"\\\\*\";\n\n                h = FindFirstFileW(qs.c_str(), &fff);\n                if (h != INVALID_HANDLE_VALUE) {\n                    do {\n                        wstring fn2;\n\n                        if (fff.cFileName[0] == '.' && (fff.cFileName[1] == 0 || (fff.cFileName[1] == '.' && fff.cFileName[2] == 0)))\n                            continue;\n\n                        fn2 = fn;\n                        fn2 += L\"\\\\\";\n                        fn2 += fff.cFileName;\n\n                        reflink_copy(hwnd, fn2.c_str(), newpath.c_str());\n                    } while (FindNextFileW(h, &fff));\n                }\n            }\n\n            // CreateDirectoryExW also copies streams, no need to do it here\n        } else {\n            if (fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {\n                reparse_header rh;\n                uint8_t* rp;\n\n                if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, &rh, sizeof(reparse_header), &bytesret, nullptr)) {\n                    if (GetLastError() != ERROR_MORE_DATA)\n                        throw last_error(GetLastError());\n                }\n\n                size_t rplen = sizeof(reparse_header) + rh.ReparseDataLength;\n                rp = (uint8_t*)malloc(rplen);\n\n                if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, rp, (ULONG)rplen, &bytesret, nullptr))\n                    throw last_error(GetLastError());\n\n                if (!DeviceIoControl(dest, FSCTL_SET_REPARSE_POINT, rp, (ULONG)rplen, nullptr, 0, &bytesret, nullptr))\n                    throw last_error(GetLastError());\n\n                free(rp);\n            } else {\n                FILE_STANDARD_INFO fsi;\n                FILE_END_OF_FILE_INFO feofi;\n                FSCTL_GET_INTEGRITY_INFORMATION_BUFFER fgiib;\n                FSCTL_SET_INTEGRITY_INFORMATION_BUFFER fsiib;\n                DUPLICATE_EXTENTS_DATA ded;\n                uint64_t offset, alloc_size;\n                ULONG maxdup;\n\n                Status = NtQueryInformationFile(source, &iosb, &fsi, sizeof(FILE_STANDARD_INFO), FileStandardInformation);\n                if (!NT_SUCCESS(Status))\n                    throw ntstatus_error(Status);\n\n                if (!DeviceIoControl(source, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &fgiib, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER), &bytesret, nullptr))\n                    throw last_error(GetLastError());\n\n                if (fbi.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) {\n                    if (!DeviceIoControl(dest, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesret, nullptr))\n                        throw last_error(GetLastError());\n                }\n\n                fsiib.ChecksumAlgorithm = fgiib.ChecksumAlgorithm;\n                fsiib.Reserved = 0;\n                fsiib.Flags = fgiib.Flags;\n                if (!DeviceIoControl(dest, FSCTL_SET_INTEGRITY_INFORMATION, &fsiib, sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER), nullptr, 0, &bytesret, nullptr))\n                    throw last_error(GetLastError());\n\n                feofi.EndOfFile = fsi.EndOfFile;\n                Status = NtSetInformationFile(dest, &iosb, &feofi, sizeof(FILE_END_OF_FILE_INFO), FileEndOfFileInformation);\n                if (!NT_SUCCESS(Status))\n                    throw ntstatus_error(Status);\n\n                ded.FileHandle = source;\n                maxdup = 0xffffffff - fgiib.ClusterSizeInBytes + 1;\n\n                alloc_size = sector_align(fsi.EndOfFile.QuadPart, fgiib.ClusterSizeInBytes);\n\n                offset = 0;\n                while (offset < alloc_size) {\n                    ded.SourceFileOffset.QuadPart = ded.TargetFileOffset.QuadPart = offset;\n                    ded.ByteCount.QuadPart = maxdup < (alloc_size - offset) ? maxdup : (alloc_size - offset);\n                    if (!DeviceIoControl(dest, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &ded, sizeof(DUPLICATE_EXTENTS_DATA), nullptr, 0, &bytesret, nullptr))\n                        throw last_error(GetLastError());\n\n                    offset += ded.ByteCount.QuadPart;\n                }\n            }\n\n            ULONG streambufsize = 0;\n            vector<char> streambuf;\n\n            do {\n                streambufsize += 0x1000;\n                streambuf.resize(streambufsize);\n\n                memset(streambuf.data(), 0, streambufsize);\n\n                Status = NtQueryInformationFile(source, &iosb, streambuf.data(), streambufsize, FileStreamInformation);\n            } while (Status == STATUS_BUFFER_OVERFLOW);\n\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n\n            auto fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(streambuf.data());\n\n            while (true) {\n                if (fsi->StreamNameLength > 0) {\n                    wstring sn = wstring(fsi->StreamName, fsi->StreamNameLength / sizeof(WCHAR));\n\n                    if (sn != L\"::$DATA\" && sn.length() > 6 && sn.substr(sn.length() - 6, 6) == L\":$DATA\") {\n                        win_handle stream;\n                        uint8_t* data = nullptr;\n                        auto stream_size = (uint16_t)fsi->StreamSize.QuadPart;\n\n\n                        if (stream_size > 0) {\n                            wstring fn2;\n\n                            fn2 = fn;\n                            fn2 += sn;\n\n                            stream = CreateFileW(fn2.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);\n\n                            if (stream == INVALID_HANDLE_VALUE)\n                                throw last_error(GetLastError());\n\n                            // We can get away with this because our streams are guaranteed to be below 64 KB -\n                            // don't do this on NTFS!\n                            data = (uint8_t*)malloc(stream_size);\n\n                            if (!ReadFile(stream, data, stream_size, &bytesret, nullptr)) {\n                                free(data);\n                                throw last_error(GetLastError());\n                            }\n                        }\n\n                        stream = CreateFileW((newpath + sn).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, nullptr);\n\n                        if (stream == INVALID_HANDLE_VALUE) {\n                            if (data) free(data);\n                            throw last_error(GetLastError());\n                        }\n\n                        if (data) {\n                            if (!WriteFile(stream, data, stream_size, &bytesret, nullptr)) {\n                                free(data);\n                                throw last_error(GetLastError());\n                            }\n\n                            free(data);\n                        }\n                    }\n                }\n\n                if (fsi->NextEntryOffset == 0)\n                    break;\n\n                fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(reinterpret_cast<char*>(fsi) + fsi->NextEntryOffset);\n            }\n        }\n\n        atime.dwLowDateTime = fbi.LastAccessTime.LowPart;\n        atime.dwHighDateTime = fbi.LastAccessTime.HighPart;\n        mtime.dwLowDateTime = fbi.LastWriteTime.LowPart;\n        mtime.dwHighDateTime = fbi.LastWriteTime.HighPart;\n        SetFileTime(dest, nullptr, &atime, &mtime);\n\n        Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, &bsxa, sizeof(btrfs_set_xattr));\n\n        if (Status == STATUS_BUFFER_OVERFLOW || (NT_SUCCESS(Status) && bsxa.valuelen > 0)) {\n            ULONG xalen = 0;\n            btrfs_set_xattr *xa = nullptr, *xa2;\n\n            do {\n                xalen += 1024;\n\n                if (xa) free(xa);\n                xa = (btrfs_set_xattr*)malloc(xalen);\n\n                Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, xa, xalen);\n            } while (Status == STATUS_BUFFER_OVERFLOW);\n\n            if (!NT_SUCCESS(Status)) {\n                free(xa);\n                throw ntstatus_error(Status);\n            }\n\n            xa2 = xa;\n            while (xa2->valuelen > 0) {\n                Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, xa2,\n                                         (ULONG)(offsetof(btrfs_set_xattr, data[0]) + xa2->namelen + xa2->valuelen), nullptr, 0);\n                if (!NT_SUCCESS(Status)) {\n                    free(xa);\n                    throw ntstatus_error(Status);\n                }\n                xa2 = (btrfs_set_xattr*)&xa2->data[xa2->namelen + xa2->valuelen];\n            }\n\n            free(xa);\n        } else if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n    } catch (...) {\n        FILE_DISPOSITION_INFO fdi;\n\n        fdi.DeleteFile = true;\n        Status = NtSetInformationFile(dest, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n\n        throw;\n    }\n}\n\nHRESULT __stdcall BtrfsContextMenu::InvokeCommand(LPCMINVOKECOMMANDINFO picia) {\n    LPCMINVOKECOMMANDINFOEX pici = (LPCMINVOKECOMMANDINFOEX)picia;\n\n    try {\n        if (ignore)\n            return E_INVALIDARG;\n\n        if (!bg) {\n            if ((IS_INTRESOURCE(pici->lpVerb) && allow_snapshot && pici->lpVerb == 0) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, SNAPSHOT_VERBA))) {\n                UINT num_files, i;\n                WCHAR fn[MAX_PATH];\n\n                if (!stgm_set)\n                    return E_FAIL;\n\n                num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);\n\n                if (num_files == 0)\n                    return E_FAIL;\n\n                for (i = 0; i < num_files; i++) {\n                    if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) {\n                        create_snapshot(pici->hwnd, fn);\n                    }\n                }\n\n                return S_OK;\n            } else if ((IS_INTRESOURCE(pici->lpVerb) && ((allow_snapshot && (ULONG_PTR)pici->lpVerb == 1) || (!allow_snapshot && (ULONG_PTR)pici->lpVerb == 0))) ||\n                    (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, SEND_VERBA))) {\n                UINT num_files, i;\n                WCHAR dll[MAX_PATH], fn[MAX_PATH];\n                wstring t;\n                SHELLEXECUTEINFOW sei;\n\n                GetModuleFileNameW(module, dll, sizeof(dll) / sizeof(WCHAR));\n\n                if (!stgm_set)\n                    return E_FAIL;\n\n                num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);\n\n                if (num_files == 0)\n                    return E_FAIL;\n\n                for (i = 0; i < num_files; i++) {\n                    if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) {\n                        t = L\"\\\"\";\n                        t += dll;\n                        t += L\"\\\",SendSubvolGUI \";\n                        t += fn;\n\n                        RtlZeroMemory(&sei, sizeof(sei));\n\n                        sei.cbSize = sizeof(sei);\n                        sei.hwnd = pici->hwnd;\n                        sei.lpVerb = L\"runas\";\n                        sei.lpFile = L\"rundll32.exe\";\n                        sei.lpParameters = t.c_str();\n                        sei.nShow = SW_SHOW;\n                        sei.fMask = SEE_MASK_NOCLOSEPROCESS;\n\n                        if (!ShellExecuteExW(&sei))\n                            throw last_error(GetLastError());\n\n                        WaitForSingleObject(sei.hProcess, INFINITE);\n                        CloseHandle(sei.hProcess);\n                    }\n                }\n\n                return S_OK;\n            }\n        } else {\n            if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 0) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, NEW_SUBVOL_VERBA))) {\n                win_handle h;\n                IO_STATUS_BLOCK iosb;\n                NTSTATUS Status;\n                wstring name, nameorig, searchpath;\n                btrfs_create_subvol* bcs;\n                WIN32_FIND_DATAW wfd;\n\n                if (!load_string(module, IDS_NEW_SUBVOL_FILENAME, name))\n                    throw last_error(GetLastError());\n\n                h = CreateFileW(path.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n\n                if (h == INVALID_HANDLE_VALUE)\n                    throw last_error(GetLastError());\n\n                searchpath = path + L\"\\\\\" + name;\n                nameorig = name;\n\n                {\n                    fff_handle fff = FindFirstFileW(searchpath.c_str(), &wfd);\n\n                    if (fff != INVALID_HANDLE_VALUE) {\n                        ULONG num = 2;\n\n                        do {\n                            name = nameorig + L\" (\" + to_wstring(num) + L\")\";\n                            searchpath = path + L\"\\\\\" + name;\n\n                            fff = FindFirstFileW(searchpath.c_str(), &wfd);\n                            num++;\n                        } while (fff != INVALID_HANDLE_VALUE);\n                    }\n                }\n\n                size_t bcslen = offsetof(btrfs_create_subvol, name[0]) + (name.length() * sizeof(WCHAR));\n                bcs = (btrfs_create_subvol*)malloc(bcslen);\n\n                bcs->readonly = false;\n                bcs->posix = false;\n                bcs->namelen = (uint16_t)(name.length() * sizeof(WCHAR));\n                memcpy(bcs->name, name.c_str(), name.length() * sizeof(WCHAR));\n\n                Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0);\n\n                free(bcs);\n\n                if (!NT_SUCCESS(Status))\n                    throw ntstatus_error(Status);\n\n                return S_OK;\n            } else if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 1) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, RECV_VERBA))) {\n                WCHAR dll[MAX_PATH];\n                wstring t;\n                SHELLEXECUTEINFOW sei;\n\n                GetModuleFileNameW(module, dll, sizeof(dll) / sizeof(WCHAR));\n\n                t = L\"\\\"\";\n                t += dll;\n                t += L\"\\\",RecvSubvolGUI \";\n                t += path;\n\n                RtlZeroMemory(&sei, sizeof(sei));\n\n                sei.cbSize = sizeof(sei);\n                sei.hwnd = pici->hwnd;\n                sei.lpVerb = L\"runas\";\n                sei.lpFile = L\"rundll32.exe\";\n                sei.lpParameters = t.c_str();\n                sei.nShow = SW_SHOW;\n                sei.fMask = SEE_MASK_NOCLOSEPROCESS;\n\n                if (!ShellExecuteExW(&sei))\n                    throw last_error(GetLastError());\n\n                WaitForSingleObject(sei.hProcess, INFINITE);\n                CloseHandle(sei.hProcess);\n\n                return S_OK;\n            } else if ((IS_INTRESOURCE(pici->lpVerb) && (ULONG_PTR)pici->lpVerb == 2) || (!IS_INTRESOURCE(pici->lpVerb) && !strcmp(pici->lpVerb, REFLINK_VERBA))) {\n                HDROP hdrop;\n\n                if (!IsClipboardFormatAvailable(CF_HDROP))\n                    return S_OK;\n\n                if (!OpenClipboard(pici->hwnd))\n                    throw last_error(GetLastError());\n\n                try {\n                    hdrop = (HDROP)GetClipboardData(CF_HDROP);\n\n                    if (hdrop) {\n                        global_lock gl(hdrop);\n\n                        if (gl.ptr) {\n                            ULONG num_files, i;\n                            WCHAR fn[MAX_PATH];\n\n                            num_files = DragQueryFileW(hdrop, 0xFFFFFFFF, nullptr, 0);\n\n                            for (i = 0; i < num_files; i++) {\n                                if (DragQueryFileW(hdrop, i, fn, sizeof(fn) / sizeof(WCHAR))) {\n                                    reflink_copy(pici->hwnd, fn, pici->lpDirectoryW);\n                                }\n                            }\n                        }\n                    }\n                } catch (...) {\n                    CloseClipboard();\n                    throw;\n                }\n\n                CloseClipboard();\n\n                return S_OK;\n            }\n        }\n    } catch (const exception& e) {\n        error_message(pici->hwnd, e.what());\n    }\n\n    return E_FAIL;\n}\n\nHRESULT __stdcall BtrfsContextMenu::GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT*, LPSTR pszName, UINT cchMax) {\n    if (ignore)\n        return E_INVALIDARG;\n\n    if (idCmd != 0)\n        return E_INVALIDARG;\n\n    if (!bg) {\n        if (idCmd == 0) {\n            switch (uFlags) {\n                case GCS_HELPTEXTA:\n                    if (LoadStringA(module, IDS_CREATE_SNAPSHOT_HELP_TEXT, pszName, cchMax))\n                        return S_OK;\n                    else\n                        return E_FAIL;\n\n                case GCS_HELPTEXTW:\n                    if (LoadStringW(module, IDS_CREATE_SNAPSHOT_HELP_TEXT, (LPWSTR)pszName, cchMax))\n                        return S_OK;\n                    else\n                        return E_FAIL;\n\n                case GCS_VALIDATEA:\n                case GCS_VALIDATEW:\n                    return S_OK;\n\n                case GCS_VERBA:\n                    return StringCchCopyA(pszName, cchMax, SNAPSHOT_VERBA);\n\n                case GCS_VERBW:\n                    return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, SNAPSHOT_VERBW);\n\n                default:\n                    return E_INVALIDARG;\n            }\n        } else if (idCmd == 1) {\n            switch (uFlags) {\n                case GCS_HELPTEXTA:\n                    if (LoadStringA(module, IDS_SEND_SUBVOL_HELP, pszName, cchMax))\n                        return S_OK;\n                    else\n                        return E_FAIL;\n\n                case GCS_HELPTEXTW:\n                    if (LoadStringW(module, IDS_SEND_SUBVOL_HELP, (LPWSTR)pszName, cchMax))\n                        return S_OK;\n                    else\n                        return E_FAIL;\n\n                case GCS_VALIDATEA:\n                case GCS_VALIDATEW:\n                    return S_OK;\n\n                case GCS_VERBA:\n                    return StringCchCopyA(pszName, cchMax, SEND_VERBA);\n\n                case GCS_VERBW:\n                    return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, SEND_VERBW);\n\n                default:\n                    return E_INVALIDARG;\n                }\n        } else\n            return E_INVALIDARG;\n    } else {\n        if (idCmd == 0) {\n            switch (uFlags) {\n                case GCS_HELPTEXTA:\n                    if (LoadStringA(module, IDS_NEW_SUBVOL_HELP_TEXT, pszName, cchMax))\n                        return S_OK;\n                    else\n                        return E_FAIL;\n\n                case GCS_HELPTEXTW:\n                    if (LoadStringW(module, IDS_NEW_SUBVOL_HELP_TEXT, (LPWSTR)pszName, cchMax))\n                        return S_OK;\n                    else\n                        return E_FAIL;\n\n                case GCS_VALIDATEA:\n                case GCS_VALIDATEW:\n                    return S_OK;\n\n                case GCS_VERBA:\n                    return StringCchCopyA(pszName, cchMax, NEW_SUBVOL_VERBA);\n\n                case GCS_VERBW:\n                    return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, NEW_SUBVOL_VERBW);\n\n                default:\n                    return E_INVALIDARG;\n            }\n        } else if (idCmd == 1) {\n            switch (uFlags) {\n                case GCS_HELPTEXTA:\n                    if (LoadStringA(module, IDS_RECV_SUBVOL_HELP, pszName, cchMax))\n                        return S_OK;\n                    else\n                        return E_FAIL;\n\n                case GCS_HELPTEXTW:\n                    if (LoadStringW(module, IDS_RECV_SUBVOL_HELP, (LPWSTR)pszName, cchMax))\n                        return S_OK;\n                    else\n                        return E_FAIL;\n\n                case GCS_VALIDATEA:\n                case GCS_VALIDATEW:\n                    return S_OK;\n\n                case GCS_VERBA:\n                    return StringCchCopyA(pszName, cchMax, RECV_VERBA);\n\n                case GCS_VERBW:\n                    return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, RECV_VERBW);\n\n                default:\n                    return E_INVALIDARG;\n            }\n        } else if (idCmd == 2) {\n            switch (uFlags) {\n                case GCS_HELPTEXTA:\n                    if (LoadStringA(module, IDS_REFLINK_PASTE_HELP, pszName, cchMax))\n                        return S_OK;\n                    else\n                        return E_FAIL;\n\n                case GCS_HELPTEXTW:\n                    if (LoadStringW(module, IDS_REFLINK_PASTE_HELP, (LPWSTR)pszName, cchMax))\n                        return S_OK;\n                    else\n                        return E_FAIL;\n\n                case GCS_VALIDATEA:\n                case GCS_VALIDATEW:\n                    return S_OK;\n\n                case GCS_VERBA:\n                    return StringCchCopyA(pszName, cchMax, REFLINK_VERBA);\n\n                case GCS_VERBW:\n                    return StringCchCopyW((STRSAFE_LPWSTR)pszName, cchMax, REFLINK_VERBW);\n\n                default:\n                    return E_INVALIDARG;\n            }\n        } else\n            return E_INVALIDARG;\n    }\n}\n\nstatic void reflink_copy2(const wstring& srcfn, const wstring& destdir, const wstring& destname) {\n    win_handle source, dest;\n    FILE_BASIC_INFO fbi;\n    FILETIME atime, mtime;\n    btrfs_inode_info bii;\n    btrfs_set_inode_info bsii;\n    ULONG bytesret;\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    btrfs_set_xattr bsxa;\n\n    source = CreateFileW(srcfn.c_str(), GENERIC_READ | FILE_TRAVERSE, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr);\n    if (source == INVALID_HANDLE_VALUE)\n        throw last_error(GetLastError());\n\n    Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii, sizeof(btrfs_inode_info));\n    if (!NT_SUCCESS(Status))\n        throw ntstatus_error(Status);\n\n    // if subvol, do snapshot instead\n    if (bii.inode == SUBVOL_ROOT_INODE) {\n        btrfs_create_snapshot* bcs;\n        win_handle dirh;\n\n        dirh = CreateFileW(destdir.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n        if (dirh == INVALID_HANDLE_VALUE)\n            throw last_error(GetLastError());\n\n        size_t bcslen = offsetof(btrfs_create_snapshot, name[0]) + (destname.length() * sizeof(WCHAR));\n        bcs = (btrfs_create_snapshot*)malloc(bcslen);\n        bcs->subvol = source;\n        bcs->namelen = (uint16_t)(destname.length() * sizeof(WCHAR));\n        memcpy(bcs->name, destname.c_str(), destname.length() * sizeof(WCHAR));\n\n        Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, (ULONG)bcslen, nullptr, 0);\n        if (!NT_SUCCESS(Status)) {\n            free(bcs);\n            throw ntstatus_error(Status);\n        }\n\n        free(bcs);\n\n        return;\n    }\n\n    Status = NtQueryInformationFile(source, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation);\n    if (!NT_SUCCESS(Status))\n        throw ntstatus_error(Status);\n\n    if (bii.type == BTRFS_TYPE_CHARDEV || bii.type == BTRFS_TYPE_BLOCKDEV || bii.type == BTRFS_TYPE_FIFO || bii.type == BTRFS_TYPE_SOCKET) {\n        win_handle dirh;\n        btrfs_mknod* bmn;\n\n        dirh = CreateFileW(destdir.c_str(), FILE_ADD_FILE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n        if (dirh == INVALID_HANDLE_VALUE)\n            throw last_error(GetLastError());\n\n        size_t bmnsize = offsetof(btrfs_mknod, name[0]) + (destname.length() * sizeof(WCHAR));\n        bmn = (btrfs_mknod*)malloc(bmnsize);\n\n        bmn->inode = 0;\n        bmn->type = bii.type;\n        bmn->st_rdev = bii.st_rdev;\n        bmn->namelen = (uint16_t)(destname.length() * sizeof(WCHAR));\n        memcpy(bmn->name, destname.c_str(), bmn->namelen);\n\n        Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_MKNOD, bmn, (ULONG)bmnsize, nullptr, 0);\n        if (!NT_SUCCESS(Status)) {\n            free(bmn);\n            throw ntstatus_error(Status);\n        }\n\n        free(bmn);\n\n        dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, OPEN_EXISTING, 0, nullptr);\n    } else if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\n        if (CreateDirectoryExW(srcfn.c_str(), (destdir + destname).c_str(), nullptr))\n            dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                               nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n        else\n            dest = INVALID_HANDLE_VALUE;\n    } else\n        dest = CreateFileW((destdir + destname).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, source);\n\n    if (dest == INVALID_HANDLE_VALUE)\n        throw last_error(GetLastError());\n\n    memset(&bsii, 0, sizeof(btrfs_set_inode_info));\n\n    bsii.flags_changed = true;\n    bsii.flags = bii.flags;\n\n    if (bii.flags & BTRFS_INODE_COMPRESS) {\n        bsii.compression_type_changed = true;\n        bsii.compression_type = bii.compression_type;\n    }\n\n    try {\n        Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0);\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n\n        if (fbi.FileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\n            if (!(fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)) {\n                WIN32_FIND_DATAW fff;\n                wstring qs;\n\n                qs = srcfn;\n                qs += L\"\\\\*\";\n\n                fff_handle h = FindFirstFileW(qs.c_str(), &fff);\n                if (h != INVALID_HANDLE_VALUE) {\n                    do {\n                        wstring fn2;\n\n                        if (fff.cFileName[0] == '.' && (fff.cFileName[1] == 0 || (fff.cFileName[1] == '.' && fff.cFileName[2] == 0)))\n                            continue;\n\n                        fn2 = srcfn;\n                        fn2 += L\"\\\\\";\n                        fn2 += fff.cFileName;\n\n                        reflink_copy2(fn2, destdir + destname + L\"\\\\\", fff.cFileName);\n                    } while (FindNextFileW(h, &fff));\n                }\n            }\n\n            // CreateDirectoryExW also copies streams, no need to do it here\n        } else {\n            if (fbi.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) {\n                reparse_header rh;\n                uint8_t* rp;\n\n                if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, &rh, sizeof(reparse_header), &bytesret, nullptr)) {\n                    if (GetLastError() != ERROR_MORE_DATA)\n                        throw last_error(GetLastError());\n                }\n\n                size_t rplen = sizeof(reparse_header) + rh.ReparseDataLength;\n                rp = (uint8_t*)malloc(rplen);\n\n                try {\n                    if (!DeviceIoControl(source, FSCTL_GET_REPARSE_POINT, nullptr, 0, rp, (DWORD)rplen, &bytesret, nullptr))\n                        throw last_error(GetLastError());\n\n                    if (!DeviceIoControl(dest, FSCTL_SET_REPARSE_POINT, rp, (DWORD)rplen, nullptr, 0, &bytesret, nullptr))\n                        throw last_error(GetLastError());\n                } catch (...) {\n                    free(rp);\n                    throw;\n                }\n\n                free(rp);\n            } else {\n                FILE_STANDARD_INFO fsi;\n                FILE_END_OF_FILE_INFO feofi;\n                FSCTL_GET_INTEGRITY_INFORMATION_BUFFER fgiib;\n                FSCTL_SET_INTEGRITY_INFORMATION_BUFFER fsiib;\n                DUPLICATE_EXTENTS_DATA ded;\n                uint64_t offset, alloc_size;\n                ULONG maxdup;\n\n                Status = NtQueryInformationFile(source, &iosb, &fsi, sizeof(FILE_STANDARD_INFO), FileStandardInformation);\n                if (!NT_SUCCESS(Status))\n                    throw ntstatus_error(Status);\n\n                if (!DeviceIoControl(source, FSCTL_GET_INTEGRITY_INFORMATION, nullptr, 0, &fgiib, sizeof(FSCTL_GET_INTEGRITY_INFORMATION_BUFFER), &bytesret, nullptr))\n                    throw last_error(GetLastError());\n\n                if (fbi.FileAttributes & FILE_ATTRIBUTE_SPARSE_FILE) {\n                    if (!DeviceIoControl(dest, FSCTL_SET_SPARSE, nullptr, 0, nullptr, 0, &bytesret, nullptr))\n                        throw last_error(GetLastError());\n                }\n\n                fsiib.ChecksumAlgorithm = fgiib.ChecksumAlgorithm;\n                fsiib.Reserved = 0;\n                fsiib.Flags = fgiib.Flags;\n                if (!DeviceIoControl(dest, FSCTL_SET_INTEGRITY_INFORMATION, &fsiib, sizeof(FSCTL_SET_INTEGRITY_INFORMATION_BUFFER), nullptr, 0, &bytesret, nullptr))\n                    throw last_error(GetLastError());\n\n                feofi.EndOfFile = fsi.EndOfFile;\n                Status = NtSetInformationFile(dest, &iosb, &feofi, sizeof(FILE_END_OF_FILE_INFO), FileEndOfFileInformation);\n                if (!NT_SUCCESS(Status))\n                    throw ntstatus_error(Status);\n\n                ded.FileHandle = source;\n                maxdup = 0xffffffff - fgiib.ClusterSizeInBytes + 1;\n\n                alloc_size = sector_align(fsi.EndOfFile.QuadPart, fgiib.ClusterSizeInBytes);\n\n                offset = 0;\n                while (offset < alloc_size) {\n                    ded.SourceFileOffset.QuadPart = ded.TargetFileOffset.QuadPart = offset;\n                    ded.ByteCount.QuadPart = maxdup < (alloc_size - offset) ? maxdup : (alloc_size - offset);\n                    if (!DeviceIoControl(dest, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &ded, sizeof(DUPLICATE_EXTENTS_DATA), nullptr, 0, &bytesret, nullptr))\n                        throw last_error(GetLastError());\n\n                    offset += ded.ByteCount.QuadPart;\n                }\n            }\n\n            ULONG streambufsize = 0;\n            vector<char> streambuf;\n\n            do {\n                streambufsize += 0x1000;\n                streambuf.resize(streambufsize);\n\n                memset(streambuf.data(), 0, streambufsize);\n\n                Status = NtQueryInformationFile(source, &iosb, streambuf.data(), streambufsize, FileStreamInformation);\n            } while (Status == STATUS_BUFFER_OVERFLOW);\n\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n\n            auto fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(streambuf.data());\n\n            while (true) {\n                if (fsi->StreamNameLength > 0) {\n                    wstring sn = wstring(fsi->StreamName, fsi->StreamNameLength / sizeof(WCHAR));\n\n                    if (sn != L\"::$DATA\" && sn.length() > 6 && sn.substr(sn.length() - 6, 6) == L\":$DATA\") {\n                        win_handle stream;\n                        uint8_t* data = nullptr;\n                        auto stream_size = (uint16_t)fsi->StreamSize.QuadPart;\n\n                        if (stream_size > 0) {\n                            wstring fn2;\n\n                            fn2 = srcfn;\n                            fn2 += sn;\n\n                            stream = CreateFileW(fn2.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);\n\n                            if (stream == INVALID_HANDLE_VALUE)\n                                throw last_error(GetLastError());\n\n                            // We can get away with this because our streams are guaranteed to be below 64 KB -\n                            // don't do this on NTFS!\n                            data = (uint8_t*)malloc(stream_size);\n\n                            if (!ReadFile(stream, data, stream_size, &bytesret, nullptr)) {\n                                free(data);\n                                throw last_error(GetLastError());\n                            }\n                        }\n\n                        stream = CreateFileW((destdir + destname + sn).c_str(), GENERIC_READ | GENERIC_WRITE | DELETE, 0, nullptr, CREATE_NEW, 0, nullptr);\n\n                        if (stream == INVALID_HANDLE_VALUE) {\n                            if (data) free(data);\n                            throw last_error(GetLastError());\n                        }\n\n                        if (data) {\n                            if (!WriteFile(stream, data, stream_size, &bytesret, nullptr)) {\n                                free(data);\n                                throw last_error(GetLastError());\n                            }\n\n                            free(data);\n                        }\n                    }\n\n                }\n\n                if (fsi->NextEntryOffset == 0)\n                    break;\n\n                fsi = reinterpret_cast<FILE_STREAM_INFORMATION*>(reinterpret_cast<char*>(fsi) + fsi->NextEntryOffset);\n            }\n        }\n\n        atime.dwLowDateTime = fbi.LastAccessTime.LowPart;\n        atime.dwHighDateTime = fbi.LastAccessTime.HighPart;\n        mtime.dwLowDateTime = fbi.LastWriteTime.LowPart;\n        mtime.dwHighDateTime = fbi.LastWriteTime.HighPart;\n        SetFileTime(dest, nullptr, &atime, &mtime);\n\n        Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, &bsxa, sizeof(btrfs_set_xattr));\n\n        if (Status == STATUS_BUFFER_OVERFLOW || (NT_SUCCESS(Status) && bsxa.valuelen > 0)) {\n            ULONG xalen = 0;\n            btrfs_set_xattr *xa = nullptr, *xa2;\n\n            do {\n                xalen += 1024;\n\n                if (xa) free(xa);\n                xa = (btrfs_set_xattr*)malloc(xalen);\n\n                Status = NtFsControlFile(source, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_XATTRS, nullptr, 0, xa, xalen);\n            } while (Status == STATUS_BUFFER_OVERFLOW);\n\n            if (!NT_SUCCESS(Status)) {\n                free(xa);\n                throw ntstatus_error(Status);\n            }\n\n            xa2 = xa;\n            while (xa2->valuelen > 0) {\n                Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, xa2,\n                                         (ULONG)(offsetof(btrfs_set_xattr, data[0]) + xa2->namelen + xa2->valuelen), nullptr, 0);\n                if (!NT_SUCCESS(Status)) {\n                    free(xa);\n                    throw ntstatus_error(Status);\n                }\n                xa2 = (btrfs_set_xattr*)&xa2->data[xa2->namelen + xa2->valuelen];\n            }\n\n            free(xa);\n        } else if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n    } catch (...) {\n        FILE_DISPOSITION_INFO fdi;\n\n        fdi.DeleteFile = true;\n        Status = NtSetInformationFile(dest, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n\n        throw;\n    }\n}\n\nextern \"C\" void CALLBACK ReflinkCopyW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    vector<wstring> args;\n\n    command_line_to_args(lpszCmdLine, args);\n\n    if (args.size() >= 2) {\n        bool dest_is_dir = false;\n        wstring dest = args[args.size() - 1], destdir, destname;\n        WCHAR volpath2[MAX_PATH];\n\n        {\n            win_handle destdirh = CreateFileW(dest.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                              nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n\n            if (destdirh != INVALID_HANDLE_VALUE) {\n                BY_HANDLE_FILE_INFORMATION bhfi;\n\n                if (GetFileInformationByHandle(destdirh, &bhfi) && bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\n                    dest_is_dir = true;\n\n                    destdir = dest;\n                    if (destdir.substr(destdir.length() - 1, 1) != L\"\\\\\")\n                        destdir += L\"\\\\\";\n                }\n            }\n        }\n\n        if (!dest_is_dir) {\n            size_t found = dest.rfind(L\"\\\\\");\n\n            if (found == wstring::npos) {\n                destdir = L\"\";\n                destname = dest;\n            } else {\n                destdir = dest.substr(0, found);\n                destname = dest.substr(found + 1);\n            }\n        }\n\n        if (!GetVolumePathNameW(dest.c_str(), volpath2, sizeof(volpath2) / sizeof(WCHAR)))\n            return;\n\n        for (unsigned int i = 0; i < args.size() - 1; i++) {\n            WIN32_FIND_DATAW ffd;\n\n            fff_handle h = FindFirstFileW(args[i].c_str(), &ffd);\n            if (h != INVALID_HANDLE_VALUE) {\n                WCHAR volpath1[MAX_PATH];\n                wstring path = args[i];\n                size_t found = path.rfind(L\"\\\\\");\n\n                if (found == wstring::npos)\n                    path = L\"\";\n                else\n                    path = path.substr(0, found);\n\n                path += L\"\\\\\";\n\n                if (get_volume_path_parent(path.c_str(), volpath1, sizeof(volpath1) / sizeof(WCHAR))) {\n                    if (!wcscmp(volpath1, volpath2)) {\n                        do {\n                            try {\n                                reflink_copy2(path + ffd.cFileName, destdir, dest_is_dir ? ffd.cFileName : destname);\n                            } catch (const exception& e) {\n                                cerr << \"Error: \" << e.what() << endl;\n                            }\n                        } while (FindNextFileW(h, &ffd));\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/shellext/contextmenu.h",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\n#include <shlobj.h>\n\nextern LONG objs_loaded;\n\nclass BtrfsContextMenu : public IShellExtInit, IContextMenu {\npublic:\n    BtrfsContextMenu() {\n        refcount = 0;\n        ignore = true;\n        stgm_set = false;\n        uacicon = nullptr;\n        allow_snapshot = false;\n        InterlockedIncrement(&objs_loaded);\n    }\n\n    virtual ~BtrfsContextMenu() {\n        if (stgm_set)\n            ReleaseStgMedium(&stgm);\n\n        if (uacicon)\n            DeleteObject(uacicon);\n\n        InterlockedDecrement(&objs_loaded);\n    }\n\n    // IUnknown\n\n    HRESULT __stdcall QueryInterface(REFIID riid, void **ppObj);\n\n    ULONG __stdcall AddRef() {\n        return InterlockedIncrement(&refcount);\n    }\n\n    ULONG __stdcall Release() {\n        LONG rc = InterlockedDecrement(&refcount);\n\n        if (rc == 0)\n            delete this;\n\n        return rc;\n    }\n\n    // IShellExtInit\n\n    virtual HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) override;\n\n    // IContextMenu\n\n    virtual HRESULT __stdcall QueryContextMenu(HMENU hmenu, UINT indexMenu, UINT idCmdFirst, UINT idCmdLast, UINT uFlags) override;\n    virtual HRESULT __stdcall InvokeCommand(LPCMINVOKECOMMANDINFO pici) override;\n    virtual HRESULT __stdcall GetCommandString(UINT_PTR idCmd, UINT uFlags, UINT* pwReserved, LPSTR pszName, UINT cchMax) override;\n\nprivate:\n    LONG refcount;\n    bool ignore, allow_snapshot;\n    bool bg;\n    wstring path;\n    STGMEDIUM stgm;\n    bool stgm_set;\n    HBITMAP uacicon;\n\n    void reflink_copy(HWND hwnd, const WCHAR* fn, const WCHAR* dir);\n    void get_uac_icon();\n};\n"
  },
  {
    "path": "src/shellext/devices.cpp",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#define ISOLATION_AWARE_ENABLED 1\n#define STRSAFE_NO_DEPRECATE\n\n#include \"shellext.h\"\n#include \"devices.h\"\n#include \"resource.h\"\n#include \"balance.h\"\n#include <stddef.h>\n#include <uxtheme.h>\n#include <setupapi.h>\n#include <strsafe.h>\n#include <mountmgr.h>\n#include <algorithm>\n#include \"../btrfs.h\"\n#include \"mountmgr.h\"\n\nDEFINE_GUID(GUID_DEVINTERFACE_HIDDEN_VOLUME, 0x7f108a28L, 0x9833, 0x4b3b, 0xb7, 0x80, 0x2c, 0x6b, 0x5f, 0xa5, 0xc0, 0x62);\n\nstatic wstring get_mountdev_name(const nt_handle& h ) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    MOUNTDEV_NAME mdn, *mdn2;\n    wstring name;\n\n    Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME,\n                                   nullptr, 0, &mdn, sizeof(MOUNTDEV_NAME));\n    if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)\n        return L\"\";\n\n    size_t mdnsize = offsetof(MOUNTDEV_NAME, Name[0]) + mdn.NameLength;\n\n    mdn2 = (MOUNTDEV_NAME*)malloc(mdnsize);\n\n    Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTDEV_QUERY_DEVICE_NAME,\n                                   nullptr, 0, mdn2, (ULONG)mdnsize);\n    if (!NT_SUCCESS(Status)) {\n        free(mdn2);\n        return L\"\";\n    }\n\n    name = wstring(mdn2->Name, mdn2->NameLength / sizeof(WCHAR));\n\n    free(mdn2);\n\n    return name;\n}\n\nstatic void find_devices(HWND, const GUID* guid, const mountmgr& mm, vector<device>& device_list) {\n    HDEVINFO h;\n\n    static const wstring dosdevices = L\"\\\\DosDevices\\\\\";\n\n    h = SetupDiGetClassDevsW(guid, nullptr, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);\n\n    if (h != INVALID_HANDLE_VALUE) {\n        DWORD index = 0;\n        SP_DEVICE_INTERFACE_DATA did;\n\n        did.cbSize = sizeof(did);\n\n        if (!SetupDiEnumDeviceInterfaces(h, nullptr, guid, index, &did))\n            return;\n\n        do {\n            SP_DEVINFO_DATA dd;\n            SP_DEVICE_INTERFACE_DETAIL_DATA_W* detail;\n            DWORD size;\n\n            dd.cbSize = sizeof(dd);\n\n            SetupDiGetDeviceInterfaceDetailW(h, &did, nullptr, 0, &size, nullptr);\n\n            detail = (SP_DEVICE_INTERFACE_DETAIL_DATA_W*)malloc(size);\n            memset(detail, 0, size);\n\n            detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W);\n\n            if (SetupDiGetDeviceInterfaceDetailW(h, &did, detail, size, &size, &dd)) {\n                NTSTATUS Status;\n                nt_handle file;\n                device dev;\n                STORAGE_DEVICE_NUMBER sdn;\n                IO_STATUS_BLOCK iosb;\n                UNICODE_STRING path;\n                OBJECT_ATTRIBUTES attr;\n                GET_LENGTH_INFORMATION gli;\n                ULONG i;\n                uint8_t sb[4096];\n\n                path.Buffer = detail->DevicePath;\n                path.Length = path.MaximumLength = (uint16_t)(wcslen(detail->DevicePath) * sizeof(WCHAR));\n\n                if (path.Length > 4 * sizeof(WCHAR) && path.Buffer[0] == '\\\\' && path.Buffer[1] == '\\\\'  && path.Buffer[2] == '?'  && path.Buffer[3] == '\\\\')\n                    path.Buffer[1] = '?';\n\n                InitializeObjectAttributes(&attr, &path, 0, nullptr, nullptr);\n\n                Status = NtOpenFile(&file, FILE_GENERIC_READ, &attr, &iosb, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_ALERT);\n\n                if (!NT_SUCCESS(Status)) {\n                    free(detail);\n                    index++;\n                    continue;\n                }\n\n                dev.pnp_name = detail->DevicePath;\n\n                Status = NtDeviceIoControlFile(file, nullptr, nullptr, nullptr, &iosb, IOCTL_DISK_GET_LENGTH_INFO, nullptr, 0, &gli, sizeof(GET_LENGTH_INFORMATION));\n                if (!NT_SUCCESS(Status)) {\n                    free(detail);\n                    index++;\n                    continue;\n                }\n\n                dev.size = gli.Length.QuadPart;\n\n                Status = NtDeviceIoControlFile(file, nullptr, nullptr, nullptr, &iosb, IOCTL_STORAGE_GET_DEVICE_NUMBER, nullptr, 0, &sdn, sizeof(STORAGE_DEVICE_NUMBER));\n                if (!NT_SUCCESS(Status)) {\n                    dev.disk_num = 0xffffffff;\n                    dev.part_num = 0xffffffff;\n                } else {\n                    dev.disk_num = sdn.DeviceNumber;\n                    dev.part_num = sdn.PartitionNumber;\n                }\n\n                dev.friendly_name = L\"\";\n                dev.drive = L\"\";\n                dev.fstype = L\"\";\n                dev.has_parts = false;\n                dev.ignore = false;\n                dev.multi_device = false;\n\n                dev.is_disk = RtlCompareMemory(guid, &GUID_DEVINTERFACE_DISK, sizeof(GUID)) == sizeof(GUID);\n\n                if (dev.is_disk) {\n                    STORAGE_PROPERTY_QUERY spq;\n                    STORAGE_DEVICE_DESCRIPTOR sdd, *sdd2;\n                    ULONG dlisize;\n                    DRIVE_LAYOUT_INFORMATION_EX* dli;\n\n                    spq.PropertyId = StorageDeviceProperty;\n                    spq.QueryType = PropertyStandardQuery;\n                    spq.AdditionalParameters[0] = 0;\n\n                    Status = NtDeviceIoControlFile(file, nullptr, nullptr, nullptr, &iosb, IOCTL_STORAGE_QUERY_PROPERTY,\n                                                   &spq, sizeof(STORAGE_PROPERTY_QUERY), &sdd, sizeof(STORAGE_DEVICE_DESCRIPTOR));\n\n                    if (NT_SUCCESS(Status) || Status == STATUS_BUFFER_OVERFLOW) {\n                        sdd2 = (STORAGE_DEVICE_DESCRIPTOR*)malloc(sdd.Size);\n\n                        Status = NtDeviceIoControlFile(file, nullptr, nullptr, nullptr, &iosb, IOCTL_STORAGE_QUERY_PROPERTY,\n                                                       &spq, sizeof(STORAGE_PROPERTY_QUERY), sdd2, sdd.Size);\n                        if (NT_SUCCESS(Status)) {\n                            string desc2;\n\n                            desc2 = \"\";\n\n                            if (sdd2->VendorIdOffset != 0) {\n                                desc2 += (char*)((uint8_t*)sdd2 + sdd2->VendorIdOffset);\n\n                                while (desc2.length() > 0 && desc2[desc2.length() - 1] == ' ')\n                                    desc2 = desc2.substr(0, desc2.length() - 1);\n                            }\n\n                            if (sdd2->ProductIdOffset != 0) {\n                                if (sdd2->VendorIdOffset != 0 && desc2.length() != 0 && desc2[desc2.length() - 1] != ' ')\n                                    desc2 += \" \";\n\n                                desc2 += (char*)((uint8_t*)sdd2 + sdd2->ProductIdOffset);\n\n                                while (desc2.length() > 0 && desc2[desc2.length() - 1] == ' ')\n                                    desc2 = desc2.substr(0, desc2.length() - 1);\n                            }\n\n                            if (sdd2->VendorIdOffset != 0 || sdd2->ProductIdOffset != 0) {\n                                ULONG ss;\n\n                                ss = MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, desc2.c_str(), -1, nullptr, 0);\n\n                                if (ss > 0) {\n                                    WCHAR* desc3 = (WCHAR*)malloc(ss * sizeof(WCHAR));\n\n                                    if (MultiByteToWideChar(CP_OEMCP, MB_PRECOMPOSED, desc2.c_str(), -1, desc3, ss))\n                                        dev.friendly_name = desc3;\n\n                                    free(desc3);\n                                }\n                            }\n                        }\n\n                        free(sdd2);\n                    }\n\n                    dlisize = 0;\n                    dli = nullptr;\n\n                    do {\n                        dlisize += 1024;\n\n                        if (dli)\n                            free(dli);\n\n                        dli = (DRIVE_LAYOUT_INFORMATION_EX*)malloc(dlisize);\n\n                        Status = NtDeviceIoControlFile(file, nullptr, nullptr, nullptr, &iosb, IOCTL_DISK_GET_DRIVE_LAYOUT_EX,\n                                                       nullptr, 0, dli, dlisize);\n                    } while (Status == STATUS_BUFFER_TOO_SMALL);\n\n                    if (NT_SUCCESS(Status) && dli->PartitionCount > 0)\n                        dev.has_parts = true;\n\n                    free(dli);\n                } else {\n                    try {\n                        auto v = mm.query_points(L\"\", L\"\", wstring_view(path.Buffer, path.Length / sizeof(WCHAR)));\n\n                        for (const auto& p : v) {\n                            if (p.symlink.length() == 14 && p.symlink.substr(0, dosdevices.length()) == dosdevices && p.symlink[13] == ':') {\n                                WCHAR dr[3];\n\n                                dr[0] = p.symlink[12];\n                                dr[1] = ':';\n                                dr[2] = 0;\n\n                                dev.drive = dr;\n                                break;\n                            }\n                        }\n                    } catch (...) { // don't fail entirely if mountmgr refuses to co-operate\n                    }\n                }\n\n                if (!dev.is_disk || !dev.has_parts) {\n                    i = 0;\n                    while (fs_ident[i].name) {\n                        if (i == 0 || fs_ident[i].kboff != fs_ident[i-1].kboff) {\n                            LARGE_INTEGER off;\n\n                            off.QuadPart = fs_ident[i].kboff * 1024;\n                            Status = NtReadFile(file, nullptr, nullptr, nullptr, &iosb, sb, sizeof(sb), &off, nullptr);\n                        }\n\n                        if (NT_SUCCESS(Status)) {\n                            if (RtlCompareMemory(sb + fs_ident[i].sboff, fs_ident[i].magic, fs_ident[i].magiclen) == fs_ident[i].magiclen) {\n                                dev.fstype = fs_ident[i].name;\n\n                                if (dev.fstype == L\"Btrfs\") {\n                                    superblock* bsb = (superblock*)sb;\n\n                                    RtlCopyMemory(&dev.fs_uuid, &bsb->uuid, sizeof(BTRFS_UUID));\n                                    RtlCopyMemory(&dev.dev_uuid, &bsb->dev_item.device_uuid, sizeof(BTRFS_UUID));\n                                }\n\n                                break;\n                            }\n                        }\n\n                        i++;\n                    }\n\n                    if (dev.fstype == L\"Btrfs\" && RtlCompareMemory(guid, &GUID_DEVINTERFACE_DISK, sizeof(GUID)) != sizeof(GUID)) {\n                        wstring name;\n                        wstring pref = L\"\\\\Device\\\\Btrfs{\";\n\n                        name = get_mountdev_name(file);\n\n                        if (name.length() > pref.length() && RtlCompareMemory(name.c_str(), pref.c_str(), pref.length() * sizeof(WCHAR)) == pref.length() * sizeof(WCHAR))\n                            dev.ignore = true;\n                    }\n                }\n\n                device_list.push_back(dev);\n            }\n\n            free(detail);\n\n            index++;\n        } while (SetupDiEnumDeviceInterfaces(h, nullptr, guid, index, &did));\n\n        SetupDiDestroyDeviceInfoList(h);\n    } else\n        throw last_error(GetLastError());\n}\n\nstatic bool sort_devices(device i, device j) {\n    if (i.disk_num < j.disk_num)\n        return true;\n\n    if (i.disk_num == j.disk_num && i.part_num < j.part_num)\n        return true;\n\n    return false;\n}\n\nvoid BtrfsDeviceAdd::populate_device_tree(HWND tree) {\n    HWND hwnd = GetParent(tree);\n    unsigned int i;\n    ULONG last_disk_num = 0xffffffff;\n    HTREEITEM diskitem;\n    NTSTATUS Status;\n    OBJECT_ATTRIBUTES attr;\n    UNICODE_STRING us;\n    IO_STATUS_BLOCK iosb;\n    btrfs_filesystem* bfs = nullptr;\n\n    static WCHAR btrfs[] = L\"\\\\Btrfs\";\n\n    device_list.clear();\n\n    {\n        mountmgr mm;\n\n        {\n            nt_handle btrfsh;\n\n            us.Length = us.MaximumLength = (uint16_t)(wcslen(btrfs) * sizeof(WCHAR));\n            us.Buffer = btrfs;\n\n            InitializeObjectAttributes(&attr, &us, 0, nullptr, nullptr);\n\n            Status = NtOpenFile(&btrfsh, SYNCHRONIZE | FILE_READ_ATTRIBUTES, &attr, &iosb,\n                                FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_ALERT);\n            if (NT_SUCCESS(Status)) {\n                ULONG bfssize = 0;\n\n                do {\n                    bfssize += 1024;\n\n                    if (bfs) free(bfs);\n                    bfs = (btrfs_filesystem*)malloc(bfssize);\n\n                    Status = NtDeviceIoControlFile(btrfsh, nullptr, nullptr, nullptr, &iosb, IOCTL_BTRFS_QUERY_FILESYSTEMS, nullptr, 0, bfs, bfssize);\n                    if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {\n                        free(bfs);\n                        bfs = nullptr;\n                        break;\n                    }\n                } while (Status == STATUS_BUFFER_OVERFLOW);\n\n                if (bfs && bfs->num_devices == 0) { // no mounted filesystems found\n                    free(bfs);\n                    bfs = nullptr;\n                }\n            }\n        }\n\n        find_devices(hwnd, &GUID_DEVINTERFACE_DISK, mm, device_list);\n        find_devices(hwnd, &GUID_DEVINTERFACE_VOLUME, mm, device_list);\n        find_devices(hwnd, &GUID_DEVINTERFACE_HIDDEN_VOLUME, mm, device_list);\n    }\n\n    sort(device_list.begin(), device_list.end(), sort_devices);\n\n    for (i = 0; i < device_list.size(); i++) {\n        if (!device_list[i].ignore) {\n            TVINSERTSTRUCTW tis;\n            HTREEITEM item;\n            wstring name, size;\n\n            if (device_list[i].disk_num != 0xffffffff && device_list[i].disk_num == last_disk_num)\n                tis.hParent = diskitem;\n            else\n                tis.hParent = TVI_ROOT;\n\n            tis.hInsertAfter = TVI_LAST;\n            tis.itemex.mask = TVIF_TEXT | TVIF_STATE | TVIF_PARAM;\n            tis.itemex.state = TVIS_EXPANDED;\n            tis.itemex.stateMask = TVIS_EXPANDED;\n\n            if (device_list[i].disk_num != 0xffffffff) {\n                wstring t;\n\n                if (!load_string(module, device_list[i].part_num != 0 ? IDS_PARTITION : IDS_DISK_NUM, t))\n                    throw last_error(GetLastError());\n\n                wstring_sprintf(name, t, device_list[i].part_num != 0 ? device_list[i].part_num : device_list[i].disk_num);\n            } else\n                name = device_list[i].pnp_name;\n\n            // match child Btrfs devices to their parent\n            if (bfs && device_list[i].drive == L\"\" && device_list[i].fstype == L\"Btrfs\") {\n                btrfs_filesystem* bfs2 = bfs;\n\n                while (true) {\n                    if (RtlCompareMemory(&bfs2->uuid, &device_list[i].fs_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n                        ULONG j, k;\n                        btrfs_filesystem_device* dev;\n\n                        for (j = 0; j < bfs2->num_devices; j++) {\n                            if (j == 0)\n                                dev = &bfs2->device;\n                            else\n                                dev = (btrfs_filesystem_device*)((uint8_t*)dev + offsetof(btrfs_filesystem_device, name[0]) + dev->name_length);\n\n                            if (RtlCompareMemory(&device_list[i].dev_uuid, &device_list[i].dev_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n                                for (k = 0; k < device_list.size(); k++) {\n                                    if (k != i && device_list[k].fstype == L\"Btrfs\" && device_list[k].drive != L\"\" &&\n                                        RtlCompareMemory(&device_list[k].fs_uuid, &device_list[i].fs_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n                                        device_list[i].drive = device_list[k].drive;\n                                        break;\n                                    }\n                                }\n\n                                device_list[i].multi_device = bfs2->num_devices > 1;\n\n                                break;\n                            }\n                        }\n\n                        break;\n                    }\n\n                    if (bfs2->next_entry != 0)\n                        bfs2 = (btrfs_filesystem*)((uint8_t*)bfs2 + bfs2->next_entry);\n                    else\n                        break;\n                }\n            }\n\n            name += L\" (\";\n\n            if (device_list[i].friendly_name != L\"\") {\n                name += device_list[i].friendly_name;\n                name += L\", \";\n            }\n\n            if (device_list[i].drive != L\"\") {\n                name += device_list[i].drive;\n                name += L\", \";\n            }\n\n            if (device_list[i].fstype != L\"\") {\n                name += device_list[i].fstype;\n                name += L\", \";\n            }\n\n            format_size(device_list[i].size, size, false);\n            name += size;\n\n            name += L\")\";\n\n            tis.itemex.pszText = (WCHAR*)name.c_str();\n            tis.itemex.cchTextMax = (int)name.length();\n            tis.itemex.lParam = (LPARAM)&device_list[i];\n\n            item = (HTREEITEM)SendMessageW(tree, TVM_INSERTITEMW, 0, (LPARAM)&tis);\n            if (!item)\n                throw string_error(IDS_TVM_INSERTITEM_FAILED);\n\n            if (device_list[i].part_num == 0) {\n                diskitem = item;\n                last_disk_num = device_list[i].disk_num;\n            }\n        }\n    }\n}\n\nvoid BtrfsDeviceAdd::AddDevice(HWND hwndDlg) {\n    wstring mess, title;\n    NTSTATUS Status;\n    UNICODE_STRING vn;\n    OBJECT_ATTRIBUTES attr;\n    IO_STATUS_BLOCK iosb;\n\n    if (!sel) {\n        EndDialog(hwndDlg, 0);\n        return;\n    }\n\n    if (sel->fstype != L\"\") {\n        wstring s;\n\n        if (!load_string(module, IDS_ADD_DEVICE_CONFIRMATION_FS, s))\n            throw last_error(GetLastError());\n\n        wstring_sprintf(mess, s, sel->fstype.c_str());\n    } else {\n        if (!load_string(module, IDS_ADD_DEVICE_CONFIRMATION, mess))\n            throw last_error(GetLastError());\n    }\n\n    if (!load_string(module, IDS_CONFIRMATION_TITLE, title))\n        throw last_error(GetLastError());\n\n    if (MessageBoxW(hwndDlg, mess.c_str(), title.c_str(), MB_YESNO) != IDYES)\n        return;\n\n    win_handle h = CreateFileW(cmdline, FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                               OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n    if (h == INVALID_HANDLE_VALUE)\n        throw last_error(GetLastError());\n\n    {\n        nt_handle h2;\n\n        vn.Length = vn.MaximumLength = (uint16_t)(sel->pnp_name.length() * sizeof(WCHAR));\n        vn.Buffer = (WCHAR*)sel->pnp_name.c_str();\n\n        InitializeObjectAttributes(&attr, &vn, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, nullptr, nullptr);\n\n        Status = NtOpenFile(&h2, FILE_GENERIC_READ | FILE_GENERIC_WRITE, &attr, &iosb, FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_ALERT);\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n\n        if (!sel->is_disk) {\n            Status = NtFsControlFile(h2, nullptr, nullptr, nullptr, &iosb, FSCTL_LOCK_VOLUME, nullptr, 0, nullptr, 0);\n            if (!NT_SUCCESS(Status))\n                throw string_error(IDS_LOCK_FAILED, Status);\n        }\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_ADD_DEVICE, &h2, sizeof(HANDLE), nullptr, 0);\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n\n        if (!sel->is_disk) {\n            Status = NtFsControlFile(h2, nullptr, nullptr, nullptr, &iosb, FSCTL_DISMOUNT_VOLUME, nullptr, 0, nullptr, 0);\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n\n            Status = NtFsControlFile(h2, nullptr, nullptr, nullptr, &iosb, FSCTL_UNLOCK_VOLUME, nullptr, 0, nullptr, 0);\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n        }\n    }\n\n    EndDialog(hwndDlg, 0);\n}\n\nINT_PTR CALLBACK BtrfsDeviceAdd::DeviceAddDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n            {\n                EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB);\n                populate_device_tree(GetDlgItem(hwndDlg, IDC_DEVICE_TREE));\n                EnableWindow(GetDlgItem(hwndDlg, IDOK), false);\n                break;\n            }\n\n            case WM_COMMAND:\n                switch (HIWORD(wParam)) {\n                    case BN_CLICKED:\n                        switch (LOWORD(wParam)) {\n                            case IDOK:\n                                AddDevice(hwndDlg);\n                            return true;\n\n                            case IDCANCEL:\n                                EndDialog(hwndDlg, 0);\n                            return true;\n                        }\n                    break;\n                }\n            break;\n\n            case WM_NOTIFY:\n                switch (((LPNMHDR)lParam)->code) {\n                    case TVN_SELCHANGEDW:\n                    {\n                        NMTREEVIEWW* nmtv = (NMTREEVIEWW*)lParam;\n                        TVITEMW tvi;\n                        bool enable = false;\n\n                        RtlZeroMemory(&tvi, sizeof(TVITEMW));\n                        tvi.hItem = nmtv->itemNew.hItem;\n                        tvi.mask = TVIF_PARAM | TVIF_HANDLE;\n\n                        if (SendMessageW(GetDlgItem(hwndDlg, IDC_DEVICE_TREE), TVM_GETITEMW, 0, (LPARAM)&tvi))\n                            sel = tvi.lParam == 0 ? nullptr : (device*)tvi.lParam;\n                        else\n                            sel = nullptr;\n\n                        if (sel)\n                            enable = (!sel->is_disk || !sel->has_parts) && !sel->multi_device;\n\n                        EnableWindow(GetDlgItem(hwndDlg, IDOK), enable);\n                        break;\n                    }\n                }\n            break;\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nstatic INT_PTR CALLBACK stub_DeviceAddDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    BtrfsDeviceAdd* bda;\n\n    if (uMsg == WM_INITDIALOG) {\n        SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);\n        bda = (BtrfsDeviceAdd*)lParam;\n    } else {\n        bda = (BtrfsDeviceAdd*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n    }\n\n    if (bda)\n        return bda->DeviceAddDlgProc(hwndDlg, uMsg, wParam, lParam);\n    else\n        return false;\n}\n\nvoid BtrfsDeviceAdd::ShowDialog() {\n    DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_DEVICE_ADD), hwnd, stub_DeviceAddDlgProc, (LPARAM)this);\n}\n\nBtrfsDeviceAdd::BtrfsDeviceAdd(HINSTANCE hinst, HWND hwnd, WCHAR* cmdline) {\n    this->hinst = hinst;\n    this->hwnd = hwnd;\n    this->cmdline = cmdline;\n\n    sel = nullptr;\n}\n\nvoid BtrfsDeviceResize::do_resize(HWND hwndDlg) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    btrfs_resize br;\n\n    {\n        win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                   OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n        if (h == INVALID_HANDLE_VALUE)\n            throw last_error(GetLastError());\n\n        br.device = dev_id;\n        br.size = new_size;\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RESIZE, &br, sizeof(btrfs_resize), nullptr, 0);\n\n        if (Status != STATUS_MORE_PROCESSING_REQUIRED && !NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n    }\n\n    if (Status != STATUS_MORE_PROCESSING_REQUIRED) {\n        wstring s, t, u;\n\n        load_string(module, IDS_RESIZE_SUCCESSFUL, s);\n        format_size(new_size, u, true);\n        wstring_sprintf(t, s, dev_id, u.c_str());\n        MessageBoxW(hwndDlg, t.c_str(), L\"\", MB_OK);\n\n        EndDialog(hwndDlg, 0);\n    } else {\n        HWND par;\n\n        par = GetParent(hwndDlg);\n        EndDialog(hwndDlg, 0);\n\n        BtrfsBalance bb(fn, false, true);\n        bb.ShowBalance(par);\n    }\n}\n\nINT_PTR CALLBACK BtrfsDeviceResize::DeviceResizeDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n            {\n                win_handle h;\n                WCHAR s[255];\n                wstring t, u;\n\n                EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB);\n\n                GetDlgItemTextW(hwndDlg, IDC_RESIZE_DEVICE_ID, s, sizeof(s) / sizeof(WCHAR));\n                wstring_sprintf(t, s, dev_id);\n                SetDlgItemTextW(hwndDlg, IDC_RESIZE_DEVICE_ID, t.c_str());\n\n                h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n                if (h != INVALID_HANDLE_VALUE) {\n                    NTSTATUS Status;\n                    IO_STATUS_BLOCK iosb;\n                    btrfs_device *devices, *bd;\n                    ULONG devsize;\n                    bool found = false;\n                    HWND slider;\n\n                    devsize = 1024;\n                    devices = (btrfs_device*)malloc(devsize);\n\n                    while (true) {\n                        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_DEVICES, nullptr, 0, devices, devsize);\n                        if (Status == STATUS_BUFFER_OVERFLOW) {\n                            devsize += 1024;\n\n                            free(devices);\n                            devices = (btrfs_device*)malloc(devsize);\n                        } else\n                            break;\n                    }\n\n                    if (!NT_SUCCESS(Status)) {\n                        free(devices);\n                        return false;\n                    }\n\n                    bd = devices;\n\n                    while (true) {\n                        if (bd->dev_id == dev_id) {\n                            memcpy(&dev_info, bd, sizeof(btrfs_device));\n                            found = true;\n                            break;\n                        }\n\n                        if (bd->next_entry > 0)\n                            bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry);\n                        else\n                            break;\n                    }\n\n                    if (!found) {\n                        free(devices);\n                        return false;\n                    }\n\n                    free(devices);\n\n                    GetDlgItemTextW(hwndDlg, IDC_RESIZE_CURSIZE, s, sizeof(s) / sizeof(WCHAR));\n                    format_size(dev_info.size, u, true);\n                    wstring_sprintf(t, s, u.c_str());\n                    SetDlgItemTextW(hwndDlg, IDC_RESIZE_CURSIZE, t.c_str());\n\n                    new_size = dev_info.size;\n\n                    GetDlgItemTextW(hwndDlg, IDC_RESIZE_NEWSIZE, new_size_text, sizeof(new_size_text) / sizeof(WCHAR));\n                    wstring_sprintf(t, new_size_text, u.c_str());\n                    SetDlgItemTextW(hwndDlg, IDC_RESIZE_NEWSIZE, t.c_str());\n\n                    slider = GetDlgItem(hwndDlg, IDC_RESIZE_SLIDER);\n                    SendMessageW(slider, TBM_SETRANGEMIN, false, 0);\n                    SendMessageW(slider, TBM_SETRANGEMAX, false, (LPARAM)(dev_info.max_size / 1048576));\n                    SendMessageW(slider, TBM_SETPOS, true, (LPARAM)(new_size / 1048576));\n                } else\n                    return false;\n\n                break;\n            }\n\n            case WM_COMMAND:\n                switch (HIWORD(wParam)) {\n                    case BN_CLICKED:\n                        switch (LOWORD(wParam)) {\n                            case IDOK:\n                                do_resize(hwndDlg);\n                                return true;\n\n                            case IDCANCEL:\n                                EndDialog(hwndDlg, 0);\n                                return true;\n                        }\n                    break;\n                }\n            break;\n\n            case WM_HSCROLL:\n            {\n                wstring t, u;\n\n                new_size = UInt32x32To64(SendMessageW(GetDlgItem(hwndDlg, IDC_RESIZE_SLIDER), TBM_GETPOS, 0, 0), 1048576);\n\n                format_size(new_size, u, true);\n                wstring_sprintf(t, new_size_text, u.c_str());\n                SetDlgItemTextW(hwndDlg, IDC_RESIZE_NEWSIZE, t.c_str());\n\n                EnableWindow(GetDlgItem(hwndDlg, IDOK), new_size > 0 ? true : false);\n\n                break;\n            }\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nstatic INT_PTR CALLBACK stub_DeviceResizeDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    BtrfsDeviceResize* bdr;\n\n    if (uMsg == WM_INITDIALOG) {\n        SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);\n        bdr = (BtrfsDeviceResize*)lParam;\n    } else\n        bdr = (BtrfsDeviceResize*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n\n    if (bdr)\n        return bdr->DeviceResizeDlgProc(hwndDlg, uMsg, wParam, lParam);\n    else\n        return false;\n}\n\nvoid BtrfsDeviceResize::ShowDialog(HWND hwnd, const wstring& fn, uint64_t dev_id) {\n    this->dev_id = dev_id;\n    this->fn = fn;\n\n    DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_RESIZE), hwnd, stub_DeviceResizeDlgProc, (LPARAM)this);\n}\n\nextern \"C\" {\n\nvoid CALLBACK AddDeviceW(HWND hwnd, HINSTANCE hinst, LPWSTR lpszCmdLine, int) {\n    try {\n        win_handle token;\n        TOKEN_PRIVILEGES tp;\n        LUID luid;\n\n        set_dpi_aware();\n\n        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n            throw last_error(GetLastError());\n\n        if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid))\n            throw last_error(GetLastError());\n\n        tp.PrivilegeCount = 1;\n        tp.Privileges[0].Luid = luid;\n        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n        if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))\n            throw last_error(GetLastError());\n\n        BtrfsDeviceAdd bda(hinst, hwnd, lpszCmdLine);\n        bda.ShowDialog();\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n}\n\nvoid CALLBACK RemoveDeviceW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    try {\n        WCHAR *s, *vol, *dev;\n        uint64_t devid;\n        win_handle h, token;\n        TOKEN_PRIVILEGES tp;\n        LUID luid;\n        NTSTATUS Status;\n        IO_STATUS_BLOCK iosb;\n\n        set_dpi_aware();\n\n        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n            throw last_error(GetLastError());\n\n        if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid))\n            throw last_error(GetLastError());\n\n        tp.PrivilegeCount = 1;\n        tp.Privileges[0].Luid = luid;\n        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n        if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))\n            throw last_error(GetLastError());\n\n        s = wcsstr(lpszCmdLine, L\"|\");\n        if (!s)\n            return;\n\n        s[0] = 0;\n\n        vol = lpszCmdLine;\n        dev = &s[1];\n\n        devid = _wtoi(dev);\n        if (devid == 0)\n            return;\n\n        h = CreateFileW(vol, FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                        OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n        if (h == INVALID_HANDLE_VALUE)\n            throw last_error(GetLastError());\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_REMOVE_DEVICE, &devid, sizeof(uint64_t), nullptr, 0);\n        if (!NT_SUCCESS(Status)) {\n            if (Status == STATUS_CANNOT_DELETE)\n                throw string_error(IDS_CANNOT_REMOVE_RAID);\n            else\n                throw ntstatus_error(Status);\n\n            return;\n        }\n\n        BtrfsBalance bb(vol, true);\n        bb.ShowBalance(hwnd);\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n}\n\nvoid CALLBACK ResizeDeviceW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    try {\n        WCHAR *s, *vol, *dev;\n        uint64_t devid;\n        win_handle token;\n        TOKEN_PRIVILEGES tp;\n        LUID luid;\n\n        set_dpi_aware();\n\n        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n            throw last_error(GetLastError());\n\n        if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid))\n            throw last_error(GetLastError());\n\n        tp.PrivilegeCount = 1;\n        tp.Privileges[0].Luid = luid;\n        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n        if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))\n            throw last_error(GetLastError());\n\n        s = wcsstr(lpszCmdLine, L\"|\");\n        if (!s)\n            return;\n\n        s[0] = 0;\n\n        vol = lpszCmdLine;\n        dev = &s[1];\n\n        devid = _wtoi(dev);\n        if (devid == 0)\n            return;\n\n        BtrfsDeviceResize bdr;\n        bdr.ShowDialog(hwnd, vol, devid);\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n}\n\n}\n"
  },
  {
    "path": "src/shellext/devices.h",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\n#include <windows.h>\n#include <winternl.h>\n#include <shlobj.h>\n#include \"../btrfsioctl.h\"\n\ntypedef struct {\n    wstring pnp_name;\n    wstring friendly_name;\n    wstring drive;\n    wstring fstype;\n    ULONG disk_num;\n    ULONG part_num;\n    uint64_t size;\n    bool has_parts;\n    BTRFS_UUID fs_uuid;\n    BTRFS_UUID dev_uuid;\n    bool ignore;\n    bool multi_device;\n    bool is_disk;\n} device;\n\ntypedef struct {\n    const WCHAR* name;\n    const char* magic;\n    ULONG magiclen;\n    uint32_t sboff;\n    uint32_t kboff;\n} fs_identifier;\n\n// This list is compiled from information in libblkid, part of util-linux\n// and likewise under the LGPL. Thanks!\n\nconst static fs_identifier fs_ident[] = {\n    { L\"BeFS\", \"BFS1\", 4, 0x20, 0 },\n    { L\"BeFS\", \"1SFB\", 4, 0x20, 0 },\n    { L\"BeFS\", \"BFS1\", 4, 0x220, 0 },\n    { L\"BeFS\", \"1SFB\", 4, 0x220, 0 },\n    { L\"BFS\", \"\\xce\\xfa\\xad\\x1b\", 4, 0, 0 },\n    { L\"RomFS\", \"-rom1fs-\", 8, 0, 0 },\n    { L\"SquashFS\", \"hsqs\", 4, 0, 0 },\n    { L\"SquashFS\", \"sqsh\", 4, 0, 0 },\n    { L\"SquashFS\", \"hsqs\", 4, 0, 0 },\n    { L\"UBIFS\", \"\\x31\\x18\\x10\\x06\", 4, 0, 0 },\n    { L\"XFS\", \"XFSB\", 4, 0, 0 },\n    { L\"ext2\", \"\\123\\357\", 2, 0x38, 1 },\n    { L\"F2FS\", \"\\x10\\x20\\xF5\\xF2\", 4, 0, 1 },\n    { L\"HFS\", \"BD\", 2, 0, 1 },\n    { L\"HFS\", \"BD\", 2, 0, 1 },\n    { L\"HFS\", \"H+\", 2, 0, 1 },\n    { L\"HFS\", \"HX\", 2, 0, 1 },\n    { L\"Minix\", \"\\177\\023\", 2, 0x10, 1 },\n    { L\"Minix\", \"\\217\\023\", 2, 0x10, 1 },\n    { L\"Minix\", \"\\023\\177\", 2, 0x10, 1 },\n    { L\"Minix\", \"\\023\\217\", 2, 0x10, 1 },\n    { L\"Minix\", \"\\150\\044\", 2, 0x10, 1 },\n    { L\"Minix\", \"\\170\\044\", 2, 0x10, 1 },\n    { L\"Minix\", \"\\044\\150\", 2, 0x10, 1 },\n    { L\"Minix\", \"\\044\\170\", 2, 0x10, 1 },\n    { L\"Minix\", \"\\132\\115\", 2, 0x10, 1 },\n    { L\"Minix\", \"\\115\\132\", 2, 0x10, 1 },\n    { L\"OCFS\", \"OCFSV2\", 6, 0, 1 },\n    { L\"SysV\", \"\\x2b\\x55\\x44\", 3, 0x400, 1 },\n    { L\"SysV\", \"\\x44\\x55\\x2b\", 3, 0x400, 1 },\n    { L\"VXFS\", \"\\365\\374\\001\\245\", 4, 0, 1 },\n    { L\"OCFS\", \"OCFSV2\", 6, 0, 2 },\n    { L\"ExFAT\", \"EXFAT   \", 8, 3, 0 },\n    { L\"NTFS\", \"NTFS    \", 8, 3, 0 },\n    { L\"NetWare\", \"SPB5\", 4, 0, 4 },\n    { L\"OCFS\", \"OCFSV2\", 6, 0, 4 },\n    { L\"HPFS\", \"\\x49\\xe8\\x95\\xf9\", 4, 0, 8 },\n    { L\"OCFS\", \"OracleCFS\", 9, 0, 8 },\n    { L\"OCFS\", \"OCFSV2\", 6, 0, 8 },\n    { L\"ReFS\", \"\\000\\000\\000ReFS\\000\", 8, 0, 0 },\n    { L\"ReiserFS\", \"ReIsErFs\",  8, 0x34, 8 },\n    { L\"ReiserFS\", \"ReIsErFs\",  8, 20, 8 },\n    { L\"ISO9660\", \"CD001\", 5, 1, 32 },\n    { L\"ISO9660\", \"CDROM\", 5, 9, 32 },\n    { L\"JFS\", \"JFS1\", 4, 0, 32 },\n    { L\"OCFS\", \"ORCLDISK\", 8, 32, 0 },\n    { L\"UDF\", \"BEA01\", 5, 1, 32 },\n    { L\"UDF\", \"BOOT2\", 5, 1, 32 },\n    { L\"UDF\", \"CD001\", 5, 1, 32 },\n    { L\"UDF\", \"CDW02\", 5, 1, 32 },\n    { L\"UDF\", \"NSR02\", 5, 1, 32 },\n    { L\"UDF\", \"NSR03\", 5, 1, 32 },\n    { L\"UDF\", \"TEA01\", 5, 1, 32 },\n    { L\"Btrfs\", \"_BHRfS_M\", 8, 0x40, 64 },\n    { L\"GFS\", \"\\x01\\x16\\x19\\x70\", 4, 0, 64 },\n    { L\"GFS\", \"\\x01\\x16\\x19\\x70\", 4, 0, 64 },\n    { L\"ReiserFS\", \"ReIsEr2Fs\", 9, 0x34, 64 },\n    { L\"ReiserFS\", \"ReIsEr3Fs\", 9, 0x34, 64 },\n    { L\"ReiserFS\", \"ReIsErFs\",  8, 0x34, 64 },\n    { L\"ReiserFS\", \"ReIsEr4\", 7, 0, 64 },\n    { L\"VMFS\", \"\\x0d\\xd0\\x01\\xc0\", 4, 0, 1024 },\n    { L\"VMFS\", \"\\x5e\\xf1\\xab\\x2f\", 4, 0, 2048 },\n    { L\"FAT\", \"MSWIN\",    5, 0x52, 0 },\n    { L\"FAT\", \"FAT32   \", 8, 0x52, 0 },\n    { L\"FAT\", \"MSDOS\",    5, 0x36, 0 },\n    { L\"FAT\", \"FAT16   \", 8, 0x36, 0 },\n    { L\"FAT\", \"FAT12   \", 8, 0x36, 0 },\n    { L\"FAT\", \"FAT     \", 8, 0x36, 0 },\n    { L\"FAT\", \"\\353\",     1, 0, 0 },\n    { L\"FAT\", \"\\351\",     1, 0, 0 },\n    { L\"FAT\", \"\\125\\252\", 2, 0x1fe, 0 },\n    { nullptr, nullptr, 0, 0, 0 }\n};\n\nclass BtrfsDeviceAdd {\npublic:\n    INT_PTR CALLBACK DeviceAddDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);\n    void ShowDialog();\n    void AddDevice(HWND hwndDlg);\n    BtrfsDeviceAdd(HINSTANCE hinst, HWND hwnd, WCHAR* cmdline);\n\nprivate:\n    void populate_device_tree(HWND tree);\n\n    HINSTANCE hinst;\n    HWND hwnd;\n    WCHAR* cmdline;\n    device* sel;\n    vector<device> device_list;\n};\n\nclass BtrfsDeviceResize {\npublic:\n    INT_PTR CALLBACK DeviceResizeDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);\n    void ShowDialog(HWND hwnd, const wstring& fn, uint64_t dev_id);\n\nprivate:\n    void do_resize(HWND hwndDlg);\n\n    uint64_t dev_id, new_size;\n    wstring fn;\n    WCHAR new_size_text[255];\n    btrfs_device dev_info;\n};\n"
  },
  {
    "path": "src/shellext/factory.cpp",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"shellext.h\"\n#include <windows.h>\n#include \"factory.h\"\n#include \"iconoverlay.h\"\n#include \"contextmenu.h\"\n#include \"propsheet.h\"\n#include \"volpropsheet.h\"\n\nHRESULT __stdcall Factory::QueryInterface(const IID& iid, void** ppv) {\n    if (iid == IID_IUnknown || iid == IID_IClassFactory) {\n        *ppv = static_cast<IClassFactory*>(this);\n    } else {\n        *ppv = nullptr;\n        return E_NOINTERFACE;\n    }\n\n    reinterpret_cast<IUnknown*>(*ppv)->AddRef();\n\n    return S_OK;\n}\n\nHRESULT __stdcall Factory::LockServer(BOOL) {\n    return E_NOTIMPL;\n}\n\nHRESULT __stdcall Factory::CreateInstance(IUnknown* pUnknownOuter, const IID& iid, void** ppv) {\n    if (pUnknownOuter)\n        return CLASS_E_NOAGGREGATION;\n\n    switch (type) {\n        case FactoryIconHandler:\n            if (iid == IID_IUnknown || iid == IID_IShellIconOverlayIdentifier) {\n                BtrfsIconOverlay* bio = new BtrfsIconOverlay;\n                if (!bio)\n                    return E_OUTOFMEMORY;\n\n                return bio->QueryInterface(iid, ppv);\n            }\n            break;\n\n        case FactoryContextMenu:\n            if (iid == IID_IUnknown || iid == IID_IContextMenu || iid == IID_IShellExtInit) {\n                BtrfsContextMenu* bcm = new BtrfsContextMenu;\n                if (!bcm)\n                    return E_OUTOFMEMORY;\n\n                return bcm->QueryInterface(iid, ppv);\n            }\n            break;\n\n        case FactoryPropSheet:\n            if (iid == IID_IUnknown || iid == IID_IShellPropSheetExt || iid == IID_IShellExtInit) {\n                BtrfsPropSheet* bps = new BtrfsPropSheet;\n                if (!bps)\n                    return E_OUTOFMEMORY;\n\n                return bps->QueryInterface(iid, ppv);\n            }\n            break;\n\n        case FactoryVolPropSheet:\n            if (iid == IID_IUnknown || iid == IID_IShellPropSheetExt || iid == IID_IShellExtInit) {\n                BtrfsVolPropSheet* bps = new BtrfsVolPropSheet;\n                if (!bps)\n                    return E_OUTOFMEMORY;\n\n                return bps->QueryInterface(iid, ppv);\n            }\n            break;\n\n        default:\n            break;\n    }\n\n    *ppv = nullptr;\n    return E_NOINTERFACE;\n}\n"
  },
  {
    "path": "src/shellext/factory.h",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\nextern LONG objs_loaded;\n\ntypedef enum {\n    FactoryUnknown,\n    FactoryIconHandler,\n    FactoryContextMenu,\n    FactoryPropSheet,\n    FactoryVolPropSheet\n} factory_type;\n\nclass Factory : public IClassFactory {\npublic:\n    Factory() {\n        refcount = 0;\n        type = FactoryUnknown;\n        InterlockedIncrement(&objs_loaded);\n    }\n\n    virtual ~Factory() {\n        InterlockedDecrement(&objs_loaded);\n    }\n\n    // IUnknown\n\n    HRESULT __stdcall QueryInterface(REFIID riid, void **ppObj);\n\n    ULONG __stdcall AddRef() {\n        return InterlockedIncrement(&refcount);\n    }\n\n    ULONG __stdcall Release() {\n        LONG rc = InterlockedDecrement(&refcount);\n\n        if (rc == 0)\n            delete this;\n\n        return rc;\n    }\n\n    // IClassFactory\n\n    virtual HRESULT __stdcall CreateInstance(IUnknown* pUnknownOuter, const IID& iid, void** ppv) override;\n    virtual HRESULT __stdcall LockServer(BOOL bLock) override;\n\n    factory_type type;\n\nprivate:\n    LONG refcount;\n};\n"
  },
  {
    "path": "src/shellext/iconoverlay.cpp",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"shellext.h\"\n#include <windows.h>\n#include <winternl.h>\n#include \"iconoverlay.h\"\n#include \"../btrfsioctl.h\"\n\nHRESULT __stdcall BtrfsIconOverlay::QueryInterface(REFIID riid, void **ppObj) {\n    if (riid == IID_IUnknown || riid == IID_IShellIconOverlayIdentifier) {\n        *ppObj = static_cast<IShellIconOverlayIdentifier*>(this);\n        AddRef();\n        return S_OK;\n    }\n\n    *ppObj = nullptr;\n    return E_NOINTERFACE;\n}\n\nHRESULT __stdcall BtrfsIconOverlay::GetOverlayInfo(PWSTR pwszIconFile, int cchMax, int* pIndex, DWORD* pdwFlags) noexcept {\n    if (GetModuleFileNameW(module, pwszIconFile, cchMax) == 0)\n        return E_FAIL;\n\n    if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)\n        return E_FAIL;\n\n    if (!pIndex)\n        return E_INVALIDARG;\n\n    if (!pdwFlags)\n        return E_INVALIDARG;\n\n    *pIndex = 0;\n    *pdwFlags = ISIOI_ICONFILE | ISIOI_ICONINDEX;\n\n    return S_OK;\n}\n\nHRESULT __stdcall BtrfsIconOverlay::GetPriority(int *pPriority) noexcept {\n    if (!pPriority)\n        return E_INVALIDARG;\n\n    *pPriority = 0;\n\n    return S_OK;\n}\n\nHRESULT __stdcall BtrfsIconOverlay::IsMemberOf(PCWSTR pwszPath, DWORD) noexcept {\n    win_handle h;\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    btrfs_get_file_ids bgfi;\n\n    h = CreateFileW(pwszPath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n    if (h == INVALID_HANDLE_VALUE)\n        return S_FALSE;\n\n    Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids));\n\n    if (!NT_SUCCESS(Status))\n        return S_FALSE;\n\n    return (bgfi.inode == 0x100 && !bgfi.top) ? S_OK : S_FALSE;\n}\n"
  },
  {
    "path": "src/shellext/iconoverlay.h",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\n#include <shlobj.h>\n\nextern LONG objs_loaded;\n\nclass BtrfsIconOverlay : public IShellIconOverlayIdentifier {\npublic:\n    BtrfsIconOverlay() {\n        refcount = 0;\n        InterlockedIncrement(&objs_loaded);\n    }\n\n    virtual ~BtrfsIconOverlay() {\n        InterlockedDecrement(&objs_loaded);\n    }\n\n    // IUnknown\n\n    HRESULT __stdcall QueryInterface(REFIID riid, void **ppObj);\n\n    ULONG __stdcall AddRef() {\n        return InterlockedIncrement(&refcount);\n    }\n\n    ULONG __stdcall Release() {\n        LONG rc = InterlockedDecrement(&refcount);\n\n        if (rc == 0)\n            delete this;\n\n        return rc;\n    }\n\n    // IShellIconOverlayIdentifier\n\n    virtual HRESULT __stdcall GetOverlayInfo(PWSTR pwszIconFile, int cchMax, int* pIndex, DWORD* pdwFlags) noexcept override;\n    virtual HRESULT __stdcall GetPriority(int *pPriority) noexcept override;\n    virtual HRESULT __stdcall IsMemberOf(PCWSTR pwszPath, DWORD dwAttrib) noexcept override;\n\nprivate:\n    LONG refcount;\n};\n"
  },
  {
    "path": "src/shellext/main.cpp",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"shellext.h\"\n#include <windows.h>\n#include <commctrl.h>\n#include <strsafe.h>\n#include <stddef.h>\n#include <stdexcept>\n#include \"factory.h\"\n#include \"resource.h\"\n\nstatic const GUID CLSID_ShellBtrfsIconHandler = { 0x2690b74f, 0xf353, 0x422d, { 0xbb, 0x12, 0x40, 0x15, 0x81, 0xee, 0xf8, 0xf0 } };\nstatic const GUID CLSID_ShellBtrfsContextMenu = { 0x2690b74f, 0xf353, 0x422d, { 0xbb, 0x12, 0x40, 0x15, 0x81, 0xee, 0xf8, 0xf1 } };\nstatic const GUID CLSID_ShellBtrfsPropSheet = { 0x2690b74f, 0xf353, 0x422d, { 0xbb, 0x12, 0x40, 0x15, 0x81, 0xee, 0xf8, 0xf2 } };\nstatic const GUID CLSID_ShellBtrfsVolPropSheet = { 0x2690b74f, 0xf353, 0x422d, { 0xbb, 0x12, 0x40, 0x15, 0x81, 0xee, 0xf8, 0xf3 } };\n\n#define COM_DESCRIPTION_ICON_HANDLER L\"WinBtrfs shell extension (icon handler)\"\n#define COM_DESCRIPTION_CONTEXT_MENU L\"WinBtrfs shell extension (context menu)\"\n#define COM_DESCRIPTION_PROP_SHEET L\"WinBtrfs shell extension (property sheet)\"\n#define COM_DESCRIPTION_VOL_PROP_SHEET L\"WinBtrfs shell extension (volume property sheet)\"\n#define ICON_OVERLAY_NAME L\"WinBtrfs\"\n\ntypedef enum _PROCESS_DPI_AWARENESS {\n    PROCESS_DPI_UNAWARE,\n    PROCESS_SYSTEM_DPI_AWARE,\n    PROCESS_PER_MONITOR_DPI_AWARE\n} PROCESS_DPI_AWARENESS;\n\ntypedef ULONG (WINAPI *_RtlNtStatusToDosError)(NTSTATUS Status);\ntypedef HRESULT (WINAPI *_SetProcessDpiAwareness)(PROCESS_DPI_AWARENESS value);\n\nHMODULE module;\nLONG objs_loaded = 0;\n\nvoid set_dpi_aware() {\n    _SetProcessDpiAwareness SetProcessDpiAwareness;\n    HMODULE shcore = LoadLibraryW(L\"shcore.dll\");\n\n    if (!shcore)\n        return;\n\n    SetProcessDpiAwareness = (_SetProcessDpiAwareness)GetProcAddress(shcore, \"SetProcessDpiAwareness\");\n\n    if (!SetProcessDpiAwareness)\n        return;\n\n    SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);\n}\n\nvoid format_size(uint64_t size, wstring& s, bool show_bytes) {\n    wstring t, bytes, kb, nb;\n    WCHAR nb2[255];\n    ULONG sr;\n    float f;\n    NUMBERFMTW fmt;\n    WCHAR dec[2], thou[4], grouping[64], *c;\n\n    nb = to_wstring(size);\n\n    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, thou, sizeof(thou) / sizeof(WCHAR));\n\n    dec[0] = '.'; dec[1] = 0; // not used, but silences gcc warning\n\n    fmt.NumDigits = 0;\n    fmt.LeadingZero = 1;\n    fmt.lpDecimalSep = dec;\n    fmt.lpThousandSep = thou;\n    fmt.NegativeOrder = 0;\n\n    // Grouping code copied from dlls/shlwapi/string.c in Wine - thank you\n\n    fmt.Grouping = 0;\n    GetLocaleInfoW(LOCALE_USER_DEFAULT, LOCALE_SGROUPING, grouping, sizeof(grouping) / sizeof(WCHAR));\n\n    c = grouping;\n    while (*c) {\n        if (*c >= '0' && *c < '9') {\n            fmt.Grouping *= 10;\n            fmt.Grouping += *c - '0';\n        }\n\n        c++;\n    }\n\n    if (fmt.Grouping % 10 == 0)\n        fmt.Grouping /= 10;\n    else\n        fmt.Grouping *= 10;\n\n    GetNumberFormatW(LOCALE_USER_DEFAULT, 0, nb.c_str(), &fmt, nb2, sizeof(nb2) / sizeof(WCHAR));\n\n    if (size < 1024) {\n        if (!load_string(module, size == 1 ? IDS_SIZE_BYTE : IDS_SIZE_BYTES, t))\n            throw last_error(GetLastError());\n\n        wstring_sprintf(s, t, nb2);\n        return;\n    }\n\n    if (show_bytes) {\n        if (!load_string(module, IDS_SIZE_BYTES, t))\n            throw last_error(GetLastError());\n\n        wstring_sprintf(bytes, t, nb2);\n    }\n\n    if (size >= 1152921504606846976) {\n        sr = IDS_SIZE_EB;\n        f = (float)size / 1152921504606846976.0f;\n    } else if (size >= 1125899906842624) {\n        sr = IDS_SIZE_PB;\n        f = (float)size / 1125899906842624.0f;\n    } else if (size >= 1099511627776) {\n        sr = IDS_SIZE_TB;\n        f = (float)size / 1099511627776.0f;\n    } else if (size >= 1073741824) {\n        sr = IDS_SIZE_GB;\n        f = (float)size / 1073741824.0f;\n    } else if (size >= 1048576) {\n        sr = IDS_SIZE_MB;\n        f = (float)size / 1048576.0f;\n    } else {\n        sr = IDS_SIZE_KB;\n        f = (float)size / 1024.0f;\n    }\n\n    if (!load_string(module, sr, t))\n        throw last_error(GetLastError());\n\n    if (show_bytes) {\n        wstring_sprintf(kb, t, f);\n\n        if (!load_string(module, IDS_SIZE_LARGE, t))\n            throw last_error(GetLastError());\n\n        wstring_sprintf(s, t, kb.c_str(), bytes.c_str());\n    } else\n        wstring_sprintf(s, t, f);\n}\n\nwstring format_message(ULONG last_error) {\n    WCHAR* buf;\n    wstring s;\n\n    if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,\n        last_error, 0, (WCHAR*)&buf, 0, nullptr) == 0) {\n        return L\"(error retrieving message)\";\n    }\n\n    s = buf;\n\n    LocalFree(buf);\n\n    // remove trailing newline\n    while (s.length() > 0 && (s.substr(s.length() - 1, 1) == L\"\\r\" || s.substr(s.length() - 1, 1) == L\"\\n\"))\n        s = s.substr(0, s.length() - 1);\n\n    return s;\n}\n\nwstring format_ntstatus(NTSTATUS Status) {\n    _RtlNtStatusToDosError RtlNtStatusToDosError;\n    wstring s;\n    HMODULE ntdll = LoadLibraryW(L\"ntdll.dll\");\n\n    if (!ntdll)\n        return L\"(error loading ntdll.dll)\";\n\n    RtlNtStatusToDosError = (_RtlNtStatusToDosError)GetProcAddress(ntdll, \"RtlNtStatusToDosError\");\n\n    if (!RtlNtStatusToDosError) {\n        FreeLibrary(ntdll);\n        return L\"(error loading RtlNtStatusToDosError)\";\n    }\n\n    s = format_message(RtlNtStatusToDosError(Status));\n\n    FreeLibrary(ntdll);\n\n    return s;\n}\n\nbool load_string(HMODULE module, UINT id, wstring& s) {\n    int len;\n    LPWSTR retstr = nullptr;\n\n    len = LoadStringW(module, id, (LPWSTR)&retstr, 0);\n\n    if (len == 0)\n        return false;\n\n    s = wstring(retstr, len);\n\n    return true;\n}\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable: 4996)\n#endif\n\nvoid wstring_sprintf(wstring& s, wstring fmt, ...) {\n    int len;\n    va_list args;\n\n    va_start(args, fmt);\n    len = _vsnwprintf(nullptr, 0, fmt.c_str(), args);\n\n    if (len == 0)\n        s = L\"\";\n    else {\n        s.resize(len);\n        _vsnwprintf((wchar_t*)s.c_str(), len, fmt.c_str(), args);\n    }\n\n    va_end(args);\n}\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\nSTDAPI DllCanUnloadNow(void) {\n    return objs_loaded == 0 ? S_OK : S_FALSE;\n}\n\nSTDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv) {\n    if (rclsid == CLSID_ShellBtrfsIconHandler) {\n        Factory* fact = new Factory;\n        if (!fact)\n            return E_OUTOFMEMORY;\n        else {\n            fact->type = FactoryIconHandler;\n\n            return fact->QueryInterface(riid, ppv);\n        }\n    } else if (rclsid == CLSID_ShellBtrfsContextMenu) {\n        Factory* fact = new Factory;\n        if (!fact)\n            return E_OUTOFMEMORY;\n        else {\n            fact->type = FactoryContextMenu;\n\n            return fact->QueryInterface(riid, ppv);\n        }\n    } else if (rclsid == CLSID_ShellBtrfsPropSheet) {\n        Factory* fact = new Factory;\n        if (!fact)\n            return E_OUTOFMEMORY;\n        else {\n            fact->type = FactoryPropSheet;\n\n            return fact->QueryInterface(riid, ppv);\n        }\n    } else if (rclsid == CLSID_ShellBtrfsVolPropSheet) {\n        Factory* fact = new Factory;\n        if (!fact)\n            return E_OUTOFMEMORY;\n        else {\n            fact->type = FactoryVolPropSheet;\n\n            return fact->QueryInterface(riid, ppv);\n        }\n    }\n\n    return CLASS_E_CLASSNOTAVAILABLE;\n}\n\nstatic void write_reg_key(HKEY root, const wstring& keyname, const WCHAR* val, const wstring& data) {\n    LONG l;\n    HKEY hk;\n    DWORD dispos;\n\n    l = RegCreateKeyExW(root, keyname.c_str(), 0, nullptr, 0, KEY_ALL_ACCESS, nullptr, &hk, &dispos);\n    if (l != ERROR_SUCCESS)\n        throw string_error(IDS_REGCREATEKEY_FAILED, l);\n\n    l = RegSetValueExW(hk, val, 0, REG_SZ, (const BYTE*)data.c_str(), (DWORD)((data.length() + 1) * sizeof(WCHAR)));\n    if (l != ERROR_SUCCESS)\n        throw string_error(IDS_REGSETVALUEEX_FAILED, l);\n\n    l = RegCloseKey(hk);\n    if (l != ERROR_SUCCESS)\n        throw string_error(IDS_REGCLOSEKEY_FAILED, l);\n}\n\nstatic void register_clsid(const GUID clsid, const WCHAR* description) {\n    WCHAR* clsidstring;\n    wstring inproc, clsidkeyname;\n    WCHAR dllpath[MAX_PATH];\n\n    StringFromCLSID(clsid, &clsidstring);\n\n    try {\n        inproc = L\"CLSID\\\\\"s + clsidstring + L\"\\\\InprocServer32\"s;\n        clsidkeyname = L\"CLSID\\\\\"s + clsidstring;\n\n        write_reg_key(HKEY_CLASSES_ROOT, clsidkeyname, nullptr, description);\n\n        GetModuleFileNameW(module, dllpath, sizeof(dllpath) / sizeof(WCHAR));\n\n        write_reg_key(HKEY_CLASSES_ROOT, inproc, nullptr, dllpath);\n\n        write_reg_key(HKEY_CLASSES_ROOT, inproc, L\"ThreadingModel\", L\"Apartment\");\n    } catch (...) {\n        CoTaskMemFree(clsidstring);\n        throw;\n    }\n\n    CoTaskMemFree(clsidstring);\n}\n\n// implementation of RegDeleteTreeW, only available from Vista on\nstatic void reg_delete_tree(HKEY hkey, const wstring& keyname) {\n    HKEY k;\n    LSTATUS ret;\n\n    ret = RegOpenKeyExW(hkey, keyname.c_str(), 0, KEY_READ, &k);\n\n    if (ret != ERROR_SUCCESS)\n        throw last_error(ret);\n\n    try {\n        WCHAR* buf;\n        ULONG bufsize;\n\n        ret = RegQueryInfoKeyW(k, nullptr, nullptr, nullptr, nullptr, &bufsize, nullptr,\n                               nullptr, nullptr, nullptr, nullptr, nullptr);\n        if (ret != ERROR_SUCCESS)\n            throw last_error(ret);\n\n        bufsize++;\n        buf = new WCHAR[bufsize];\n\n        try {\n            do {\n                ULONG size = bufsize;\n\n                ret = RegEnumKeyExW(k, 0, buf, &size, nullptr, nullptr, nullptr, nullptr);\n\n                if (ret == ERROR_NO_MORE_ITEMS)\n                    break;\n                else if (ret != ERROR_SUCCESS)\n                    throw last_error(ret);\n\n                reg_delete_tree(k, buf);\n            } while (true);\n\n            ret = RegDeleteKeyW(hkey, keyname.c_str());\n            if (ret != ERROR_SUCCESS)\n                throw last_error(ret);\n        } catch (...) {\n            delete[] buf;\n            throw;\n        }\n\n        delete[] buf;\n    } catch (...) {\n        RegCloseKey(k);\n        throw;\n    }\n\n    RegCloseKey(k);\n}\n\nstatic void unregister_clsid(const GUID clsid) {\n    WCHAR* clsidstring;\n\n    StringFromCLSID(clsid, &clsidstring);\n\n    try {\n        reg_delete_tree(HKEY_CLASSES_ROOT, L\"CLSID\\\\\"s + clsidstring);\n    } catch (...) {\n        CoTaskMemFree(clsidstring);\n        throw;\n    }\n\n    CoTaskMemFree(clsidstring);\n}\n\nstatic void reg_icon_overlay(const GUID clsid, const wstring& name) {\n    WCHAR* clsidstring;\n\n    StringFromCLSID(clsid, &clsidstring);\n\n    try {\n        wstring path = L\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\ShellIconOverlayIdentifiers\\\\\"s + name;\n\n        write_reg_key(HKEY_LOCAL_MACHINE, path, nullptr, clsidstring);\n    } catch (...) {\n        CoTaskMemFree(clsidstring);\n        throw;\n    }\n\n    CoTaskMemFree(clsidstring);\n}\n\nstatic void unreg_icon_overlay(const wstring& name) {\n    reg_delete_tree(HKEY_LOCAL_MACHINE, L\"Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Explorer\\\\ShellIconOverlayIdentifiers\\\\\"s + name);\n}\n\nstatic void reg_context_menu_handler(const GUID clsid, const wstring& filetype, const wstring& name) {\n    WCHAR* clsidstring;\n\n    StringFromCLSID(clsid, &clsidstring);\n\n    try {\n        wstring path = filetype + L\"\\\\ShellEx\\\\ContextMenuHandlers\\\\\"s + name;\n\n        write_reg_key(HKEY_CLASSES_ROOT, path, nullptr, clsidstring);\n    } catch (...) {\n        CoTaskMemFree(clsidstring);\n        throw;\n    }\n}\n\nstatic void unreg_context_menu_handler(const wstring& filetype, const wstring& name) {\n    reg_delete_tree(HKEY_CLASSES_ROOT, filetype + L\"\\\\ShellEx\\\\ContextMenuHandlers\\\\\"s + name);\n}\n\nstatic void reg_prop_sheet_handler(const GUID clsid, const wstring& filetype, const wstring& name) {\n    WCHAR* clsidstring;\n\n    StringFromCLSID(clsid, &clsidstring);\n\n    try {\n        wstring path = filetype + L\"\\\\ShellEx\\\\PropertySheetHandlers\\\\\"s + name;\n\n        write_reg_key(HKEY_CLASSES_ROOT, path, nullptr, clsidstring);\n    } catch (...) {\n        CoTaskMemFree(clsidstring);\n        throw;\n    }\n}\n\nstatic void unreg_prop_sheet_handler(const wstring& filetype, const wstring& name) {\n    reg_delete_tree(HKEY_CLASSES_ROOT, filetype + L\"\\\\ShellEx\\\\PropertySheetHandlers\\\\\"s + name);\n}\n\nSTDAPI DllRegisterServer(void) {\n    try {\n        register_clsid(CLSID_ShellBtrfsIconHandler, COM_DESCRIPTION_ICON_HANDLER);\n        register_clsid(CLSID_ShellBtrfsContextMenu, COM_DESCRIPTION_CONTEXT_MENU);\n        register_clsid(CLSID_ShellBtrfsPropSheet, COM_DESCRIPTION_PROP_SHEET);\n        register_clsid(CLSID_ShellBtrfsVolPropSheet, COM_DESCRIPTION_VOL_PROP_SHEET);\n\n        reg_icon_overlay(CLSID_ShellBtrfsIconHandler, ICON_OVERLAY_NAME);\n\n        reg_context_menu_handler(CLSID_ShellBtrfsContextMenu, L\"Directory\\\\Background\", ICON_OVERLAY_NAME);\n        reg_context_menu_handler(CLSID_ShellBtrfsContextMenu, L\"Folder\", ICON_OVERLAY_NAME);\n\n        reg_prop_sheet_handler(CLSID_ShellBtrfsPropSheet, L\"Folder\", ICON_OVERLAY_NAME);\n        reg_prop_sheet_handler(CLSID_ShellBtrfsPropSheet, L\"*\", ICON_OVERLAY_NAME);\n        reg_prop_sheet_handler(CLSID_ShellBtrfsVolPropSheet, L\"Drive\", ICON_OVERLAY_NAME);\n    } catch (const exception& e) {\n        error_message(nullptr, e.what());\n        return E_FAIL;\n    }\n\n    return S_OK;\n}\n\nSTDAPI DllUnregisterServer(void) {\n    try {\n        unreg_prop_sheet_handler(L\"Folder\", ICON_OVERLAY_NAME);\n        unreg_prop_sheet_handler(L\"*\", ICON_OVERLAY_NAME);\n        unreg_prop_sheet_handler(L\"Drive\", ICON_OVERLAY_NAME);\n        unreg_context_menu_handler(L\"Folder\", ICON_OVERLAY_NAME);\n        unreg_context_menu_handler(L\"Directory\\\\Background\", ICON_OVERLAY_NAME);\n        unreg_icon_overlay(ICON_OVERLAY_NAME);\n\n        unregister_clsid(CLSID_ShellBtrfsVolPropSheet);\n        unregister_clsid(CLSID_ShellBtrfsPropSheet);\n        unregister_clsid(CLSID_ShellBtrfsContextMenu);\n        unregister_clsid(CLSID_ShellBtrfsIconHandler);\n    } catch (const exception& e) {\n        error_message(nullptr, e.what());\n        return E_FAIL;\n    }\n\n    return S_OK;\n}\n\nSTDAPI DllInstall(BOOL bInstall, LPCWSTR) {\n    if (bInstall)\n        return DllRegisterServer();\n    else\n        return DllUnregisterServer();\n}\n\nextern \"C\" BOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void*) {\n    if (dwReason == DLL_PROCESS_ATTACH)\n        module = (HMODULE)hModule;\n\n    return true;\n}\n\nstatic void create_subvol(const wstring& fn) {\n    size_t found = fn.rfind(L\"\\\\\");\n    wstring path, file;\n    win_handle h;\n    btrfs_create_subvol* bcs;\n    IO_STATUS_BLOCK iosb;\n\n    if (found == wstring::npos) {\n        path = L\"\";\n        file = fn;\n    } else {\n        path = fn.substr(0, found);\n        file = fn.substr(found + 1);\n    }\n    path += L\"\\\\\";\n\n    h = CreateFileW(path.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n\n    if (h == INVALID_HANDLE_VALUE)\n        return;\n\n    size_t bcslen = offsetof(btrfs_create_subvol, name[0]) + (file.length() * sizeof(WCHAR));\n    bcs = (btrfs_create_subvol*)malloc(bcslen);\n\n    bcs->readonly = false;\n    bcs->posix = false;\n    bcs->namelen = (uint16_t)(file.length() * sizeof(WCHAR));\n    memcpy(bcs->name, file.c_str(), bcs->namelen);\n\n    NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0);\n}\n\nextern \"C\" void CALLBACK CreateSubvolW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    vector<wstring> args;\n\n    command_line_to_args(lpszCmdLine, args);\n\n    if (args.size() >= 1)\n        create_subvol(args[0]);\n}\n\nstatic void create_snapshot2(const wstring& source, const wstring& fn) {\n    size_t found = fn.rfind(L\"\\\\\");\n    wstring path, file;\n    win_handle h, src;\n    btrfs_create_snapshot* bcs;\n    IO_STATUS_BLOCK iosb;\n\n    if (found == wstring::npos) {\n        path = L\"\";\n        file = fn;\n    } else {\n        path = fn.substr(0, found);\n        file = fn.substr(found + 1);\n    }\n    path += L\"\\\\\";\n\n    src = CreateFileW(source.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n    if (src == INVALID_HANDLE_VALUE)\n        return;\n\n    h = CreateFileW(path.c_str(), FILE_ADD_SUBDIRECTORY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n\n    if (h == INVALID_HANDLE_VALUE)\n        return;\n\n    size_t bcslen = offsetof(btrfs_create_snapshot, name[0]) + (file.length() * sizeof(WCHAR));\n    bcs = (btrfs_create_snapshot*)malloc(bcslen);\n\n    bcs->readonly = false;\n    bcs->posix = false;\n    bcs->namelen = (uint16_t)(file.length() * sizeof(WCHAR));\n    memcpy(bcs->name, file.c_str(), bcs->namelen);\n    bcs->subvol = src;\n\n    NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, (ULONG)bcslen, nullptr, 0);\n}\n\nextern \"C\" void CALLBACK CreateSnapshotW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    vector<wstring> args;\n\n    command_line_to_args(lpszCmdLine, args);\n\n    if (args.size() >= 2)\n        create_snapshot2(args[0], args[1]);\n}\n\nvoid command_line_to_args(LPWSTR cmdline, vector<wstring>& args) {\n    LPWSTR* l;\n    int num_args;\n\n    args.clear();\n\n    l = CommandLineToArgvW(cmdline, &num_args);\n\n    if (!l)\n        return;\n\n    try {\n        args.reserve(num_args);\n\n        for (unsigned int i = 0; i < (unsigned int)num_args; i++) {\n            args.push_back(l[i]);\n        }\n    } catch (...) {\n        LocalFree(l);\n        throw;\n    }\n\n    LocalFree(l);\n}\n\nstatic string utf16_to_utf8(wstring_view utf16) {\n    string utf8;\n    char* buf;\n\n    if (utf16.empty())\n        return \"\";\n\n    auto utf8len = WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast<int>(utf16.length()), nullptr, 0, nullptr, nullptr);\n\n    if (utf8len == 0)\n        throw last_error(GetLastError());\n\n    buf = (char*)malloc(utf8len + sizeof(char));\n\n    if (!buf)\n        throw string_error(IDS_OUT_OF_MEMORY);\n\n    if (WideCharToMultiByte(CP_UTF8, 0, utf16.data(), static_cast<int>(utf16.length()), buf, utf8len, nullptr, nullptr) == 0) {\n        auto le = GetLastError();\n        free(buf);\n        throw last_error(le);\n    }\n\n    buf[utf8len] = 0;\n\n    utf8 = buf;\n\n    free(buf);\n\n    return utf8;\n}\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(disable: 4996)\n#endif\n\nstring_error::string_error(int resno, ...) {\n    wstring fmt, s;\n    int len;\n    va_list args;\n\n    if (!load_string(module, resno, fmt))\n        throw runtime_error(\"LoadString failed.\"); // FIXME\n\n    va_start(args, resno);\n    len = _vsnwprintf(nullptr, 0, fmt.c_str(), args);\n\n    if (len == 0)\n        s = L\"\";\n    else {\n        s.resize(len);\n        _vsnwprintf((wchar_t*)s.c_str(), len, fmt.c_str(), args);\n    }\n\n    va_end(args);\n\n    msg = utf16_to_utf8(s);\n}\n\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\nwstring utf8_to_utf16(string_view utf8) {\n    wstring ret;\n    WCHAR* buf;\n\n    if (utf8.empty())\n        return L\"\";\n\n    auto utf16len = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), (int)utf8.length(), nullptr, 0);\n\n    if (utf16len == 0)\n        throw last_error(GetLastError());\n\n    buf = (WCHAR*)malloc((utf16len + 1) * sizeof(WCHAR));\n\n    if (!buf)\n        throw string_error(IDS_OUT_OF_MEMORY);\n\n    if (MultiByteToWideChar(CP_UTF8, 0, utf8.data(), (int)utf8.length(), buf, utf16len) == 0) {\n        auto le = GetLastError();\n        free(buf);\n        throw last_error(le);\n    }\n\n    buf[utf16len] = 0;\n\n    ret = buf;\n\n    free(buf);\n\n    return ret;\n}\n\nlast_error::last_error(DWORD errnum) {\n    WCHAR* buf;\n\n    if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,\n        errnum, 0, (WCHAR*)&buf, 0, nullptr) == 0)\n        throw runtime_error(\"FormatMessage failed\");\n\n    try {\n        msg = utf16_to_utf8(buf);\n    } catch (...) {\n        LocalFree(buf);\n        throw;\n    }\n\n    LocalFree(buf);\n}\n\nvoid error_message(HWND hwnd, const char* msg) {\n    wstring title;\n\n    load_string(module, IDS_ERROR, title);\n\n    auto wmsg = utf8_to_utf16(msg);\n\n    MessageBoxW(hwnd, wmsg.c_str(), title.c_str(), MB_ICONERROR);\n}\n\nntstatus_error::ntstatus_error(NTSTATUS Status) : Status(Status) {\n    _RtlNtStatusToDosError RtlNtStatusToDosError;\n    HMODULE ntdll = LoadLibraryW(L\"ntdll.dll\");\n    WCHAR* buf;\n\n    if (!ntdll)\n        throw runtime_error(\"Error loading ntdll.dll.\");\n\n    try {\n        RtlNtStatusToDosError = (_RtlNtStatusToDosError)GetProcAddress(ntdll, \"RtlNtStatusToDosError\");\n\n        if (!RtlNtStatusToDosError)\n            throw runtime_error(\"Error loading RtlNtStatusToDosError in ntdll.dll.\");\n\n        if (FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, nullptr,\n            RtlNtStatusToDosError(Status), 0, (WCHAR*)&buf, 0, nullptr) == 0)\n            throw runtime_error(\"FormatMessage failed\");\n\n        try {\n            msg = utf16_to_utf8(buf);\n        } catch (...) {\n            LocalFree(buf);\n            throw;\n        }\n\n        LocalFree(buf);\n    } catch (...) {\n        FreeLibrary(ntdll);\n        throw;\n    }\n\n    FreeLibrary(ntdll);\n}\n"
  },
  {
    "path": "src/shellext/mappings.cpp",
    "content": "#include \"shellext.h\"\n#include \"resource.h\"\n#include <windows.h>\n#include <commctrl.h>\n#include <sddl.h>\n#include <ntstatus.h>\n#define _NTDEF_\n#include <ntsecapi.h>\n#include <stdexcept>\n#include <format>\n#include <memory>\n#include <span>\n#include <array>\n\nusing namespace std;\n\nstatic const WCHAR UID_REG_PATH[] = L\"SYSTEM\\\\CurrentControlSet\\\\Services\\\\btrfs\\\\Mappings\";\nstatic const WCHAR GID_REG_PATH[] = L\"SYSTEM\\\\CurrentControlSet\\\\Services\\\\btrfs\\\\GroupMappings\";\n\nclass formatted_error : public exception {\npublic:\n    template<typename... Args>\n    formatted_error(string_view s, Args&&... args) : msg(vformat(s, make_format_args(args...))) {\n    }\n\n    const char* what() const noexcept {\n        return msg.c_str();\n    }\n\nprivate:\n    string msg;\n};\n\nclass lsa_handle_closer {\npublic:\n    using pointer = LSA_HANDLE;\n\n    void operator()(LSA_HANDLE h) {\n        LsaClose(h);\n    }\n};\n\nusing unique_lsa_handle = unique_ptr<LSA_HANDLE, lsa_handle_closer>;\n\ntemplate<typename T>\nclass lsa_pointer_freer {\npublic:\n    using pointer = T;\n\n    void operator()(T ptr) {\n        LsaFreeMemory(ptr);\n    }\n};\n\nusing unique_lsa_translated_name = unique_ptr<LSA_TRANSLATED_NAME, lsa_pointer_freer<LSA_TRANSLATED_NAME*>>;\nusing unique_lsa_referenced_domain_list = unique_ptr<LSA_REFERENCED_DOMAIN_LIST, lsa_pointer_freer<LSA_REFERENCED_DOMAIN_LIST*>>;\n\nclass hkey_closer {\npublic:\n    using pointer = HKEY;\n\n    void operator()(HKEY key) {\n        RegCloseKey(key);\n    }\n};\n\nusing unique_hkey = unique_ptr<HKEY, hkey_closer>;\n\ntemplate<typename T>\nclass local_freer {\npublic:\n    using pointer = T;\n\n    void operator()(T ptr) {\n        LocalFree(ptr);\n    }\n};\n\nusing unique_sid = unique_ptr<PSID, local_freer<PSID>>;\n\nstruct mapping_entry {\n    unique_sid sid;\n    DWORD value;\n    u16string domain;\n    u16string name;\n    SID_NAME_USE use;\n};\n\nstatic unique_lsa_handle lsa_open_policy(ACCESS_MASK access) {\n    LSA_OBJECT_ATTRIBUTES oa;\n    NTSTATUS Status;\n    LSA_HANDLE h;\n\n    memset(&oa, 0, sizeof(oa));\n\n    Status = LsaOpenPolicy(nullptr, &oa, access, &h);\n\n    if (Status != STATUS_SUCCESS)\n        throw formatted_error(\"LsaOpenPolicy returned {:08x}\", (uint32_t)Status);\n\n    return unique_lsa_handle{h};\n}\n\nstatic void lsa_lookup_sids(LSA_HANDLE h, span<const PSID> sids,\n                            unique_lsa_translated_name& names,\n                            unique_lsa_referenced_domain_list& domains) {\n    NTSTATUS Status;\n    LSA_REFERENCED_DOMAIN_LIST* domains_ptr;\n    LSA_TRANSLATED_NAME* names_ptr;\n\n    Status = LsaLookupSids(h, sids.size(), (PSID*)sids.data(),\n                           &domains_ptr, &names_ptr);\n    if (Status != STATUS_SUCCESS && Status != STATUS_NONE_MAPPED && Status != STATUS_SOME_NOT_MAPPED)\n        throw formatted_error(\"LsaLookupSids returned {:08x}\", (uint32_t)Status);\n\n    names.reset(names_ptr);\n    domains.reset(domains_ptr);\n}\n\nstatic void resolve_names(span<mapping_entry> entries) {\n    if (entries.empty())\n        return;\n\n    vector<PSID> sids;\n    auto h = lsa_open_policy(POLICY_LOOKUP_NAMES);\n\n    sids.reserve(entries.size());\n\n    for (const auto& ent : entries) {\n        sids.push_back(ent.sid.get());\n    }\n\n    unique_lsa_translated_name names;\n    unique_lsa_referenced_domain_list domains;\n\n    lsa_lookup_sids(h.get(), sids, names, domains);\n\n    for (unsigned int i = 0; i < entries.size(); i++) {\n        auto& ent = entries[i];\n\n        const auto& n = names.get()[i];\n\n        ent.use = n.Use;\n        ent.name = u16string_view((char16_t*)n.Name.Buffer, n.Name.Length / sizeof(char16_t));\n\n        if (n.DomainIndex >= 0 && (ULONG)n.DomainIndex < domains->Entries) {\n            const auto& d = domains->Domains[n.DomainIndex];\n\n            ent.domain = u16string_view((char16_t*)d.Name.Buffer, d.Name.Length / sizeof(char16_t));\n        }\n    }\n}\n\nstatic void populate_list(HWND hwnd) {\n    LSTATUS ret;\n    unique_hkey k;\n    array<WCHAR, 1000> name;\n    DWORD name_len, type;\n    vector<mapping_entry> entries;\n    wstring uidgid_str;\n    LVCOLUMNW lvc;\n\n    auto tab = GetDlgItem(hwnd, IDC_MAPPINGS_TAB);\n\n    auto tabsel = SendMessageW(tab, TCM_GETCURSEL, 0, 0);\n    auto groups = tabsel == 1;\n\n    auto list = GetDlgItem(hwnd, IDC_MAPPINGS_LIST);\n\n    // change column to UID or GID\n    load_string(module, groups ? IDS_MAPPINGS_GID : IDS_MAPPINGS_UID, uidgid_str);\n    lvc.iSubItem = 1;\n    lvc.pszText = (WCHAR*)uidgid_str.c_str();\n    lvc.mask = LVCF_TEXT;\n    SendMessageW(list, LVM_SETCOLUMNW, 1, (LPARAM)&lvc);\n\n    ret = RegOpenKeyExW(HKEY_LOCAL_MACHINE, groups ? GID_REG_PATH : UID_REG_PATH,\n                        0, KEY_QUERY_VALUE, out_ptr(k));\n    if (ret != ERROR_SUCCESS)\n        throw formatted_error(\"RegOpenKeyEx failed (error {})\", ret);\n\n    for (DWORD index = 0; ; index++) {\n        unique_sid sid;\n        mapping_entry me;\n        DWORD val, val_len;\n\n        name_len = name.size();\n        val_len = sizeof(val);\n\n        ret = RegEnumValueW(k.get(), index, name.data(), &name_len, nullptr,\n                            &type, (LPBYTE)&val, &val_len);\n\n        if (ret == ERROR_NO_MORE_ITEMS)\n            break;\n\n        if ((ret == ERROR_SUCCESS || ret == ERROR_MORE_DATA) && type != REG_DWORD)\n            continue;\n\n        if (ret != ERROR_SUCCESS)\n            throw formatted_error(\"RegEnumValue failed (error {})\", ret);\n\n        if (!ConvertStringSidToSidW(name.data(), out_ptr(sid)))\n            continue;\n\n        me.sid = std::move(sid);\n        me.value = val;\n        entries.emplace_back(std::move(me));\n    }\n\n    resolve_names(entries);\n\n    SendMessageW(list, LVM_DELETEALLITEMS, 0, 0);\n    SendMessageW(list, LVM_SETITEMCOUNT, entries.size(), 0);\n\n    for (size_t i = 0; i < entries.size(); i++) {\n        const auto& ent = entries[i];\n        LVITEMW lvi;\n\n        // FIXME - different icons for SID types\n\n        u16string s;\n\n        if (!ent.domain.empty())\n            s = ent.domain + u\"\\\\\";\n\n        s += ent.name;\n\n        lvi.pszText = (WCHAR*)s.c_str();\n        lvi.mask = LVIF_TEXT;\n        lvi.iItem = i;\n        lvi.iSubItem = 0;\n\n        SendMessageW(list, LVM_INSERTITEMW, 0, (LPARAM)&lvi);\n\n        auto s2 = to_wstring(ent.value);\n\n        lvi.pszText = (WCHAR*)s2.c_str();\n        lvi.mask = LVIF_TEXT;\n        lvi.iSubItem = 1;\n\n        SendMessageW(list, LVM_SETITEMTEXTW, i, (LPARAM)&lvi);\n    }\n}\n\nstatic void init_dialog(HWND hwnd) {\n    TCITEMW tie;\n    LVCOLUMNW lvc;\n    wstring uid_mappings_str, gid_mappings_str, principal_str, uid_str;\n\n    load_string(module, IDS_MAPPINGS_UID_MAPPINGS, uid_mappings_str);\n    load_string(module, IDS_MAPPINGS_GID_MAPPINGS, gid_mappings_str);\n    load_string(module, IDS_MAPPINGS_PRINCIPAL, principal_str);\n    load_string(module, IDS_MAPPINGS_UID, uid_str);\n\n    auto tab = GetDlgItem(hwnd, IDC_MAPPINGS_TAB);\n\n    memset(&tie, 0, sizeof(tie));\n\n    tie.mask = TCIF_TEXT;\n    tie.iImage = -1;\n    tie.pszText = (WCHAR*)uid_mappings_str.c_str();\n    SendMessageW(tab, TCM_INSERTITEMW, 0, (LPARAM)&tie);\n    tie.pszText = (WCHAR*)gid_mappings_str.c_str();\n    SendMessageW(tab, TCM_INSERTITEMW, 1, (LPARAM)&tie);\n\n    auto list = GetDlgItem(hwnd, IDC_MAPPINGS_LIST);\n\n    lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;\n\n    lvc.iSubItem = 0;\n    lvc.pszText = (WCHAR*)principal_str.c_str();\n    lvc.cx = 300;\n    lvc.fmt = LVCFMT_LEFT;\n    SendMessageW(list, LVM_INSERTCOLUMNW, 0, (LPARAM)&lvc);\n\n    lvc.iSubItem = 1;\n    lvc.pszText = (WCHAR*)uid_str.c_str();\n    lvc.cx = 100;\n    lvc.fmt = LVCFMT_LEFT;\n    SendMessageW(list, LVM_INSERTCOLUMNW, 1, (LPARAM)&lvc);\n\n    try {\n        populate_list(GetParent(list));\n    } catch (const exception& e) {\n        error_message(GetParent(list), e.what());\n    }\n}\n\nstatic INT_PTR CALLBACK MappingsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n                init_dialog(hwndDlg);\n                return true;\n\n            case WM_COMMAND:\n                switch (HIWORD(wParam)) {\n                    case BN_CLICKED:\n                        switch (LOWORD(wParam)) {\n                            case IDOK:\n                            case IDCANCEL:\n                                EndDialog(hwndDlg, 1);\n                            return true;\n                        }\n                    break;\n                }\n            break;\n\n            case WM_NOTIFY:\n                switch (((LPNMHDR)lParam)->code) {\n                    case TCN_SELCHANGE:\n                        try {\n                            populate_list(hwndDlg);\n                        } catch (const exception& e) {\n                            error_message(hwndDlg, e.what());\n                        }\n                    break;\n                }\n            break;\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nextern \"C\"\nvoid CALLBACK MappingsTest(HWND hwnd, HINSTANCE, LPWSTR, int) {\n    try {\n        set_dpi_aware();\n\n        if (DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_MAPPINGS), hwnd, MappingsDlgProc, 0) <= 0)\n            throw last_error(GetLastError());\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n}\n"
  },
  {
    "path": "src/shellext/mountmgr.cpp",
    "content": "#include \"shellext.h\"\n#include \"mountmgr.h\"\n#include <mountmgr.h>\n\nusing namespace std;\n\nmountmgr::mountmgr() {\n    UNICODE_STRING us;\n    OBJECT_ATTRIBUTES attr;\n    IO_STATUS_BLOCK iosb;\n    NTSTATUS Status;\n\n    RtlInitUnicodeString(&us, MOUNTMGR_DEVICE_NAME);\n    InitializeObjectAttributes(&attr, &us, 0, nullptr, nullptr);\n\n    Status = NtOpenFile(&h, FILE_GENERIC_READ | FILE_GENERIC_WRITE, &attr, &iosb,\n                        FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_ALERT);\n\n    if (!NT_SUCCESS(Status))\n        throw ntstatus_error(Status);\n}\n\nmountmgr::~mountmgr() {\n    NtClose(h);\n}\n\nvoid mountmgr::create_point(wstring_view symlink, wstring_view device) const {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n\n    vector<uint8_t> buf(sizeof(MOUNTMGR_CREATE_POINT_INPUT) + ((symlink.length() + device.length()) * sizeof(WCHAR)));\n    auto mcpi = reinterpret_cast<MOUNTMGR_CREATE_POINT_INPUT*>(buf.data());\n\n    mcpi->SymbolicLinkNameOffset = sizeof(MOUNTMGR_CREATE_POINT_INPUT);\n    mcpi->SymbolicLinkNameLength = (USHORT)(symlink.length() * sizeof(WCHAR));\n    mcpi->DeviceNameOffset = (USHORT)(mcpi->SymbolicLinkNameOffset + mcpi->SymbolicLinkNameLength);\n    mcpi->DeviceNameLength = (USHORT)(device.length() * sizeof(WCHAR));\n\n    memcpy((uint8_t*)mcpi + mcpi->SymbolicLinkNameOffset, symlink.data(), symlink.length() * sizeof(WCHAR));\n    memcpy((uint8_t*)mcpi + mcpi->DeviceNameOffset, device.data(), device.length() * sizeof(WCHAR));\n\n    Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTMGR_CREATE_POINT,\n                                   buf.data(), (ULONG)buf.size(), nullptr, 0);\n\n    if (!NT_SUCCESS(Status))\n        throw ntstatus_error(Status);\n}\n\nvoid mountmgr::delete_points(wstring_view symlink, wstring_view unique_id, wstring_view device_name) const {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n\n    vector<uint8_t> buf(sizeof(MOUNTMGR_MOUNT_POINT) + ((symlink.length() + unique_id.length() + device_name.length()) * sizeof(WCHAR)));\n    auto mmp = reinterpret_cast<MOUNTMGR_MOUNT_POINT*>(buf.data());\n\n    memset(mmp, 0, sizeof(MOUNTMGR_MOUNT_POINT));\n\n    if (symlink.length() > 0) {\n        mmp->SymbolicLinkNameOffset = sizeof(MOUNTMGR_MOUNT_POINT);\n        mmp->SymbolicLinkNameLength = (USHORT)(symlink.length() * sizeof(WCHAR));\n        memcpy((uint8_t*)mmp + mmp->SymbolicLinkNameOffset, symlink.data(), symlink.length() * sizeof(WCHAR));\n    }\n\n    if (unique_id.length() > 0) {\n        if (mmp->SymbolicLinkNameLength == 0)\n            mmp->UniqueIdOffset = sizeof(MOUNTMGR_MOUNT_POINT);\n        else\n            mmp->UniqueIdOffset = mmp->SymbolicLinkNameOffset + mmp->SymbolicLinkNameLength;\n\n        mmp->UniqueIdLength = (USHORT)(unique_id.length() * sizeof(WCHAR));\n        memcpy((uint8_t*)mmp + mmp->UniqueIdOffset, unique_id.data(), unique_id.length() * sizeof(WCHAR));\n    }\n\n    if (device_name.length() > 0) {\n        if (mmp->SymbolicLinkNameLength == 0 && mmp->UniqueIdOffset == 0)\n            mmp->DeviceNameOffset = sizeof(MOUNTMGR_MOUNT_POINT);\n        else if (mmp->SymbolicLinkNameLength != 0)\n            mmp->DeviceNameOffset = mmp->SymbolicLinkNameOffset + mmp->SymbolicLinkNameLength;\n        else\n            mmp->DeviceNameOffset = mmp->UniqueIdOffset + mmp->UniqueIdLength;\n\n        mmp->DeviceNameLength = (USHORT)(device_name.length() * sizeof(WCHAR));\n        memcpy((uint8_t*)mmp + mmp->DeviceNameOffset, device_name.data(), device_name.length() * sizeof(WCHAR));\n    }\n\n    vector<uint8_t> buf2(sizeof(MOUNTMGR_MOUNT_POINTS));\n    auto mmps = reinterpret_cast<MOUNTMGR_MOUNT_POINTS*>(buf2.data());\n\n    Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTMGR_DELETE_POINTS,\n                                   buf.data(), (ULONG)buf.size(), buf2.data(), (ULONG)buf2.size());\n\n    if (Status == STATUS_BUFFER_OVERFLOW) {\n        buf2.resize(mmps->Size);\n\n        Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTMGR_DELETE_POINTS,\n                                       buf.data(), (ULONG)buf.size(), buf2.data(), (ULONG)buf2.size());\n    }\n\n    if (!NT_SUCCESS(Status))\n        throw ntstatus_error(Status);\n}\n\nvector<mountmgr_point> mountmgr::query_points(wstring_view symlink, wstring_view unique_id, wstring_view device_name) const {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<mountmgr_point> v;\n\n    vector<uint8_t> buf(sizeof(MOUNTMGR_MOUNT_POINT) + ((symlink.length() + unique_id.length() + device_name.length()) * sizeof(WCHAR)));\n    auto mmp = reinterpret_cast<MOUNTMGR_MOUNT_POINT*>(buf.data());\n\n    memset(mmp, 0, sizeof(MOUNTMGR_MOUNT_POINT));\n\n    if (symlink.length() > 0) {\n        mmp->SymbolicLinkNameOffset = sizeof(MOUNTMGR_MOUNT_POINT);\n        mmp->SymbolicLinkNameLength = (USHORT)(symlink.length() * sizeof(WCHAR));\n        memcpy((uint8_t*)mmp + mmp->SymbolicLinkNameOffset, symlink.data(), symlink.length() * sizeof(WCHAR));\n    }\n\n    if (unique_id.length() > 0) {\n        if (mmp->SymbolicLinkNameLength == 0)\n            mmp->UniqueIdOffset = sizeof(MOUNTMGR_MOUNT_POINT);\n        else\n            mmp->UniqueIdOffset = mmp->SymbolicLinkNameOffset + mmp->SymbolicLinkNameLength;\n\n        mmp->UniqueIdLength = (USHORT)(unique_id.length() * sizeof(WCHAR));\n        memcpy((uint8_t*)mmp + mmp->UniqueIdOffset, unique_id.data(), unique_id.length() * sizeof(WCHAR));\n    }\n\n    if (device_name.length() > 0) {\n        if (mmp->SymbolicLinkNameLength == 0 && mmp->UniqueIdOffset == 0)\n            mmp->DeviceNameOffset = sizeof(MOUNTMGR_MOUNT_POINT);\n        else if (mmp->SymbolicLinkNameLength != 0)\n            mmp->DeviceNameOffset = mmp->SymbolicLinkNameOffset + mmp->SymbolicLinkNameLength;\n        else\n            mmp->DeviceNameOffset = mmp->UniqueIdOffset + mmp->UniqueIdLength;\n\n        mmp->DeviceNameLength = (USHORT)(device_name.length() * sizeof(WCHAR));\n        memcpy((uint8_t*)mmp + mmp->DeviceNameOffset, device_name.data(), device_name.length() * sizeof(WCHAR));\n    }\n\n    vector<uint8_t> buf2(sizeof(MOUNTMGR_MOUNT_POINTS));\n    auto mmps = reinterpret_cast<MOUNTMGR_MOUNT_POINTS*>(buf2.data());\n\n    Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTMGR_QUERY_POINTS,\n                                   buf.data(), (ULONG)buf.size(), buf2.data(), (ULONG)buf2.size());\n\n    if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)\n        throw ntstatus_error(Status);\n\n    buf2.resize(mmps->Size);\n    mmps = reinterpret_cast<MOUNTMGR_MOUNT_POINTS*>(buf2.data());\n\n    Status = NtDeviceIoControlFile(h, nullptr, nullptr, nullptr, &iosb, IOCTL_MOUNTMGR_QUERY_POINTS,\n                                   buf.data(), (ULONG)buf.size(), buf2.data(), (ULONG)buf2.size());\n\n    if (!NT_SUCCESS(Status))\n        throw ntstatus_error(Status);\n\n    for (ULONG i = 0; i < mmps->NumberOfMountPoints; i++) {\n        wstring_view mpsl, mpdn;\n        string_view mpuid;\n\n        if (mmps->MountPoints[i].SymbolicLinkNameLength)\n            mpsl = wstring_view((WCHAR*)((uint8_t*)mmps + mmps->MountPoints[i].SymbolicLinkNameOffset), mmps->MountPoints[i].SymbolicLinkNameLength / sizeof(WCHAR));\n\n        if (mmps->MountPoints[i].UniqueIdLength)\n            mpuid = string_view((char*)((uint8_t*)mmps + mmps->MountPoints[i].UniqueIdOffset), mmps->MountPoints[i].UniqueIdLength);\n\n        if (mmps->MountPoints[i].DeviceNameLength)\n            mpdn = wstring_view((WCHAR*)((uint8_t*)mmps + mmps->MountPoints[i].DeviceNameOffset), mmps->MountPoints[i].DeviceNameLength / sizeof(WCHAR));\n\n        v.emplace_back(mpsl, mpuid, mpdn);\n    }\n\n    return v;\n}\n"
  },
  {
    "path": "src/shellext/mountmgr.h",
    "content": "#pragma once\n\n#include <vector>\n#include <string>\n#include <sstream>\n#include <string_view>\n#include <iostream>\n#include <iomanip>\n\nclass mountmgr_point {\npublic:\n    mountmgr_point(std::wstring_view symlink, std::string_view unique_id, std::wstring_view device_name) : symlink(symlink), device_name(device_name), unique_id(unique_id) {\n    }\n\n    std::wstring symlink, device_name;\n    std::string unique_id;\n};\n\nclass mountmgr {\npublic:\n    mountmgr();\n    ~mountmgr();\n    void create_point(std::wstring_view symlink, std::wstring_view device) const;\n    void delete_points(std::wstring_view symlink, std::wstring_view unique_id = L\"\", std::wstring_view device_name = L\"\") const;\n    std::vector<mountmgr_point> query_points(std::wstring_view symlink = L\"\", std::wstring_view unique_id = L\"\", std::wstring_view device_name = L\"\") const;\n\nprivate:\n    HANDLE h;\n};\n"
  },
  {
    "path": "src/shellext/propsheet.cpp",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#define ISOLATION_AWARE_ENABLED 1\n#define STRSAFE_NO_DEPRECATE\n\n#include \"shellext.h\"\n#include <windows.h>\n#include <strsafe.h>\n#include <winternl.h>\n\n#define NO_SHLWAPI_STRFCNS\n#include <shlwapi.h>\n#include <uxtheme.h>\n\n#include \"propsheet.h\"\n#include \"resource.h\"\n\n#define SUBVOL_ROOT_INODE 0x100\n\n#ifndef __MINGW32__ // in winternl.h in mingw\n\ntypedef struct _FILE_ACCESS_INFORMATION {\n    ACCESS_MASK AccessFlags;\n} FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION;\n\n#define FileAccessInformation (FILE_INFORMATION_CLASS)8\n\ntypedef struct _FILE_STANDARD_INFORMATION {\n    LARGE_INTEGER AllocationSize;\n    LARGE_INTEGER EndOfFile;\n    ULONG         NumberOfLinks;\n    BOOLEAN       DeletePending;\n    BOOLEAN       Directory;\n} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;\n\n#define FileStandardInformation (FILE_INFORMATION_CLASS)5\n\ntypedef struct _FILE_FS_SIZE_INFORMATION {\n    LARGE_INTEGER TotalAllocationUnits;\n    LARGE_INTEGER AvailableAllocationUnits;\n    ULONG SectorsPerAllocationUnit;\n    ULONG BytesPerSector;\n} FILE_FS_SIZE_INFORMATION, *PFILE_FS_SIZE_INFORMATION;\n\n#endif\n\nHRESULT __stdcall BtrfsPropSheet::QueryInterface(REFIID riid, void **ppObj) {\n    if (riid == IID_IUnknown || riid == IID_IShellPropSheetExt) {\n        *ppObj = static_cast<IShellPropSheetExt*>(this);\n        AddRef();\n        return S_OK;\n    } else if (riid == IID_IShellExtInit) {\n        *ppObj = static_cast<IShellExtInit*>(this);\n        AddRef();\n        return S_OK;\n    }\n\n    *ppObj = nullptr;\n    return E_NOINTERFACE;\n}\n\nvoid BtrfsPropSheet::do_search(const wstring& fn) {\n    wstring ss;\n    WIN32_FIND_DATAW ffd;\n\n    ss = fn + L\"\\\\*\"s;\n\n    fff_handle h = FindFirstFileW(ss.c_str(), &ffd);\n    if (h == INVALID_HANDLE_VALUE)\n        return;\n\n    do {\n        if (ffd.cFileName[0] != '.' || ((ffd.cFileName[1] != 0) && (ffd.cFileName[1] != '.' || ffd.cFileName[2] != 0))) {\n            wstring fn2;\n\n            fn2 = fn + L\"\\\\\"s + ffd.cFileName;\n\n            if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)\n                search_list.push_back(fn2);\n            else {\n                win_handle fh = CreateFileW(fn2.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                            OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n                if (fh != INVALID_HANDLE_VALUE) {\n                    NTSTATUS Status;\n                    IO_STATUS_BLOCK iosb;\n                    btrfs_inode_info bii2;\n\n                    memset(&bii2, 0, sizeof(bii2));\n\n                    Status = NtFsControlFile(fh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info));\n\n                    if (NT_SUCCESS(Status)) {\n                        sizes[0] += bii2.inline_length;\n                        sizes[1] += bii2.disk_size_uncompressed;\n                        sizes[2] += bii2.disk_size_zlib;\n                        sizes[3] += bii2.disk_size_lzo;\n                        sizes[4] += bii2.disk_size_zstd;\n                        totalsize += bii2.inline_length + bii2.disk_size_uncompressed + bii2.disk_size_zlib + bii2.disk_size_lzo + bii2.disk_size_zstd;\n                        sparsesize += bii2.sparse_size;\n                        num_extents += bii2.num_extents == 0 ? 0 : (bii2.num_extents - 1);\n                    }\n\n                    FILE_STANDARD_INFORMATION fsi;\n\n                    Status = NtQueryInformationFile(fh, &iosb, &fsi, sizeof(fsi), FileStandardInformation);\n\n                    if (NT_SUCCESS(Status)) {\n                        if (bii2.inline_length > 0)\n                            allocsize += fsi.EndOfFile.QuadPart;\n                        else\n                            allocsize += fsi.AllocationSize.QuadPart;\n                    }\n                }\n            }\n        }\n    } while (FindNextFileW(h, &ffd));\n}\n\nDWORD BtrfsPropSheet::search_list_thread() {\n    while (!search_list.empty()) {\n        do_search(search_list.front());\n\n        search_list.pop_front();\n    }\n\n    thread = nullptr;\n\n    return 0;\n}\n\nstatic DWORD WINAPI global_search_list_thread(LPVOID lpParameter) {\n    BtrfsPropSheet* bps = (BtrfsPropSheet*)lpParameter;\n\n    return bps->search_list_thread();\n}\n\nHRESULT BtrfsPropSheet::check_file(const wstring& fn, UINT i, UINT num_files, UINT* sv) {\n    win_handle h;\n    IO_STATUS_BLOCK iosb;\n    NTSTATUS Status;\n    FILE_ACCESS_INFORMATION fai;\n    BY_HANDLE_FILE_INFORMATION bhfi;\n    btrfs_inode_info bii2;\n\n    h = CreateFileW(fn.c_str(), MAXIMUM_ALLOWED, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                    OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n    if (h == INVALID_HANDLE_VALUE)\n        return E_FAIL;\n\n    Status = NtQueryInformationFile(h, &iosb, &fai, sizeof(FILE_ACCESS_INFORMATION), FileAccessInformation);\n    if (!NT_SUCCESS(Status))\n        return E_FAIL;\n\n    if (fai.AccessFlags & FILE_READ_ATTRIBUTES)\n        can_change_perms = fai.AccessFlags & WRITE_DAC;\n\n    readonly = !(fai.AccessFlags & FILE_WRITE_ATTRIBUTES);\n\n    if (!readonly && num_files == 1 && !can_change_perms)\n        show_admin_button = true;\n\n    if (GetFileInformationByHandle(h, &bhfi) && bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)\n        search_list.push_back(fn);\n\n    memset(&bii2, 0, sizeof(bii2));\n\n    Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info));\n\n    if (NT_SUCCESS(Status) && !bii2.top) {\n        LARGE_INTEGER filesize;\n\n        if (i == 0) {\n            subvol = bii2.subvol;\n            inode = bii2.inode;\n            type = bii2.type;\n            uid = bii2.st_uid;\n            gid = bii2.st_gid;\n            rdev = bii2.st_rdev;\n        } else {\n            if (subvol != bii2.subvol)\n                various_subvols = true;\n\n            if (inode != bii2.inode)\n                various_inodes = true;\n\n            if (type != bii2.type)\n                various_types = true;\n\n            if (uid != bii2.st_uid)\n                various_uids = true;\n\n            if (gid != bii2.st_gid)\n                various_gids = true;\n        }\n\n        if (bii2.inline_length > 0) {\n            totalsize += bii2.inline_length;\n            sizes[0] += bii2.inline_length;\n        }\n\n        if (bii2.disk_size_uncompressed > 0) {\n            totalsize += bii2.disk_size_uncompressed;\n            sizes[1] += bii2.disk_size_uncompressed;\n        }\n\n        if (bii2.disk_size_zlib > 0) {\n            totalsize += bii2.disk_size_zlib;\n            sizes[2] += bii2.disk_size_zlib;\n        }\n\n        if (bii2.disk_size_lzo > 0) {\n            totalsize += bii2.disk_size_lzo;\n            sizes[3] += bii2.disk_size_lzo;\n        }\n\n        if (bii2.disk_size_zstd > 0) {\n            totalsize += bii2.disk_size_zstd;\n            sizes[4] += bii2.disk_size_zstd;\n        }\n\n        sparsesize += bii2.sparse_size;\n        num_extents += bii2.num_extents == 0 ? 0 : (bii2.num_extents - 1);\n\n        FILE_STANDARD_INFORMATION fsi;\n\n        Status = NtQueryInformationFile(h, &iosb, &fsi, sizeof(fsi), FileStandardInformation);\n\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n\n        if (bii2.inline_length > 0)\n            allocsize += fsi.EndOfFile.QuadPart;\n        else\n            allocsize += fsi.AllocationSize.QuadPart;\n\n        min_mode |= ~bii2.st_mode;\n        max_mode |= bii2.st_mode;\n        min_flags |= ~bii2.flags;\n        max_flags |= bii2.flags;\n        min_compression_type = bii2.compression_type < min_compression_type ? bii2.compression_type : min_compression_type;\n        max_compression_type = bii2.compression_type > max_compression_type ? bii2.compression_type : max_compression_type;\n\n        if (bii2.inode == SUBVOL_ROOT_INODE) {\n            bool ro = bhfi.dwFileAttributes & FILE_ATTRIBUTE_READONLY;\n\n            has_subvols = true;\n\n            if (*sv == 0)\n                ro_subvol = ro;\n            else {\n                if (ro_subvol != ro)\n                    various_ro = true;\n            }\n\n            (*sv)++;\n        }\n\n        ignore = false;\n\n        if (bii2.type != BTRFS_TYPE_DIRECTORY && GetFileSizeEx(h, &filesize)) {\n            if (filesize.QuadPart != 0)\n                can_change_nocow = false;\n        }\n\n        {\n            FILE_FS_SIZE_INFORMATION ffsi;\n\n            Status = NtQueryVolumeInformationFile(h, &iosb, &ffsi, sizeof(ffsi), FileFsSizeInformation);\n\n            if (NT_SUCCESS(Status))\n                sector_size = ffsi.BytesPerSector;\n\n            if (sector_size == 0)\n                sector_size = 4096;\n        }\n    } else\n        return E_FAIL;\n\n    return S_OK;\n}\n\nHRESULT BtrfsPropSheet::load_file_list() {\n    UINT num_files, i, sv = 0;\n    WCHAR fn[MAX_PATH];\n\n    num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);\n\n    min_mode = 0;\n    max_mode = 0;\n    min_flags = 0;\n    max_flags = 0;\n    min_compression_type = 0xff;\n    max_compression_type = 0;\n    various_subvols = various_inodes = various_types = various_uids = various_gids = various_ro = false;\n\n    can_change_perms = true;\n    can_change_nocow = true;\n\n    sizes[0] = sizes[1] = sizes[2] = sizes[3] = sizes[4] = 0;\n    totalsize = allocsize = sparsesize = 0;\n\n    for (i = 0; i < num_files; i++) {\n        if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) {\n            HRESULT hr;\n\n            hr = check_file(fn, i, num_files, &sv);\n            if (FAILED(hr))\n                return hr;\n        } else\n            return E_FAIL;\n    }\n\n    min_mode = ~min_mode;\n    min_flags = ~min_flags;\n\n    mode = min_mode;\n    mode_set = ~(min_mode ^ max_mode);\n\n    flags = min_flags;\n    flags_set = ~(min_flags ^ max_flags);\n\n    return S_OK;\n}\n\nHRESULT __stdcall BtrfsPropSheet::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY) {\n    try {\n        FORMATETC format = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };\n        HRESULT hr;\n\n        if (pidlFolder)\n            return E_FAIL;\n\n        if (!pdtobj)\n            return E_FAIL;\n\n        stgm.tymed = TYMED_HGLOBAL;\n\n        if (FAILED(pdtobj->GetData(&format, &stgm)))\n            return E_INVALIDARG;\n\n        stgm_set = true;\n\n        global_lock gl(stgm.hGlobal);\n\n        if (!gl.ptr) {\n            ReleaseStgMedium(&stgm);\n            stgm_set = false;\n            return E_INVALIDARG;\n        }\n\n        hr = load_file_list();\n        if (FAILED(hr))\n            return hr;\n\n        if (search_list.size() > 0) {\n            thread = CreateThread(nullptr, 0, global_search_list_thread, this, 0, nullptr);\n\n            if (!thread)\n                throw last_error(GetLastError());\n        }\n    } catch (const exception& e) {\n        error_message(nullptr, e.what());\n\n        return E_FAIL;\n    }\n\n    return S_OK;\n}\n\nvoid BtrfsPropSheet::set_cmdline(const wstring& cmdline) {\n    win_handle h;\n    IO_STATUS_BLOCK iosb;\n    NTSTATUS Status;\n    UINT sv = 0;\n    BY_HANDLE_FILE_INFORMATION bhfi;\n    btrfs_inode_info bii2;\n    FILE_ACCESS_INFORMATION fai;\n\n    min_mode = 0;\n    max_mode = 0;\n    min_flags = 0;\n    max_flags = 0;\n    min_compression_type = 0xff;\n    max_compression_type = 0;\n    various_subvols = various_inodes = various_types = various_uids = various_gids = various_ro = false;\n\n    can_change_perms = true;\n    can_change_nocow = true;\n\n    h = CreateFileW(cmdline.c_str(), MAXIMUM_ALLOWED, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                    OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n    if (h == INVALID_HANDLE_VALUE)\n        throw last_error(GetLastError());\n\n    Status = NtQueryInformationFile(h, &iosb, &fai, sizeof(FILE_ACCESS_INFORMATION), FileAccessInformation);\n    if (!NT_SUCCESS(Status))\n        throw ntstatus_error(Status);\n\n    if (fai.AccessFlags & FILE_READ_ATTRIBUTES)\n        can_change_perms = fai.AccessFlags & WRITE_DAC;\n\n    readonly = !(fai.AccessFlags & FILE_WRITE_ATTRIBUTES);\n\n    if (GetFileInformationByHandle(h, &bhfi) && bhfi.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)\n        search_list.push_back(cmdline);\n\n    memset(&bii2, 0, sizeof(bii2));\n\n    Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info));\n\n    if (!NT_SUCCESS(Status))\n        throw ntstatus_error(Status);\n\n    if (!bii2.top) {\n        LARGE_INTEGER filesize;\n\n        subvol = bii2.subvol;\n        inode = bii2.inode;\n        type = bii2.type;\n        uid = bii2.st_uid;\n        gid = bii2.st_gid;\n        rdev = bii2.st_rdev;\n\n        if (bii2.inline_length > 0) {\n            totalsize += bii2.inline_length;\n            sizes[0] += bii2.inline_length;\n        }\n\n        if (bii2.disk_size_uncompressed > 0) {\n            totalsize += bii2.disk_size_uncompressed;\n            sizes[1] += bii2.disk_size_uncompressed;\n        }\n\n        if (bii2.disk_size_zlib > 0) {\n            totalsize += bii2.disk_size_zlib;\n            sizes[2] += bii2.disk_size_zlib;\n        }\n\n        if (bii2.disk_size_lzo > 0) {\n            totalsize += bii2.disk_size_lzo;\n            sizes[3] += bii2.disk_size_lzo;\n        }\n\n        if (bii2.disk_size_zstd > 0) {\n            totalsize += bii2.disk_size_zstd;\n            sizes[4] += bii2.disk_size_zstd;\n        }\n\n        sparsesize += bii2.sparse_size;\n\n        FILE_STANDARD_INFORMATION fsi;\n\n        Status = NtQueryInformationFile(h, &iosb, &fsi, sizeof(fsi), FileStandardInformation);\n\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n\n        if (bii2.inline_length > 0)\n            allocsize += fsi.EndOfFile.QuadPart;\n        else\n            allocsize += fsi.AllocationSize.QuadPart;\n\n        min_mode |= ~bii2.st_mode;\n        max_mode |= bii2.st_mode;\n        min_flags |= ~bii2.flags;\n        max_flags |= bii2.flags;\n        min_compression_type = bii2.compression_type < min_compression_type ? bii2.compression_type : min_compression_type;\n        max_compression_type = bii2.compression_type > max_compression_type ? bii2.compression_type : max_compression_type;\n\n        if (bii2.inode == SUBVOL_ROOT_INODE) {\n            bool ro = bhfi.dwFileAttributes & FILE_ATTRIBUTE_READONLY;\n\n            has_subvols = true;\n\n            if (sv == 0)\n                ro_subvol = ro;\n            else {\n                if (ro_subvol != ro)\n                    various_ro = true;\n            }\n\n            sv++;\n        }\n\n        ignore = false;\n\n        if (bii2.type != BTRFS_TYPE_DIRECTORY && GetFileSizeEx(h, &filesize)) {\n            if (filesize.QuadPart != 0)\n                can_change_nocow = false;\n        }\n    } else\n        return;\n\n    min_mode = ~min_mode;\n    min_flags = ~min_flags;\n\n    mode = min_mode;\n    mode_set = ~(min_mode ^ max_mode);\n\n    flags = min_flags;\n    flags_set = ~(min_flags ^ max_flags);\n\n    if (search_list.size() > 0) {\n        thread = CreateThread(nullptr, 0, global_search_list_thread, this, 0, nullptr);\n\n        if (!thread)\n            throw last_error(GetLastError());\n    }\n\n    this->filename = cmdline;\n}\n\nstatic ULONG inode_type_to_string_ref(uint8_t type) {\n    switch (type) {\n        case BTRFS_TYPE_FILE:\n            return IDS_INODE_FILE;\n\n        case BTRFS_TYPE_DIRECTORY:\n            return IDS_INODE_DIR;\n\n        case BTRFS_TYPE_CHARDEV:\n            return IDS_INODE_CHAR;\n\n        case BTRFS_TYPE_BLOCKDEV:\n            return IDS_INODE_BLOCK;\n\n        case BTRFS_TYPE_FIFO:\n            return IDS_INODE_FIFO;\n\n        case BTRFS_TYPE_SOCKET:\n            return IDS_INODE_SOCKET;\n\n        case BTRFS_TYPE_SYMLINK:\n            return IDS_INODE_SYMLINK;\n\n        default:\n            return IDS_INODE_UNKNOWN;\n    }\n}\n\nvoid BtrfsPropSheet::change_inode_flag(HWND hDlg, uint64_t flag, UINT state) {\n    if (flag & BTRFS_INODE_NODATACOW)\n        flag |= BTRFS_INODE_NODATASUM;\n\n    if (state == BST_CHECKED) {\n        flags |= flag;\n        flags_set |= flag;\n    } else if (state == BST_UNCHECKED) {\n        flags &= ~flag;\n        flags_set |= flag;\n    } else if (state == BST_INDETERMINATE) {\n        flags_set = ~flag;\n    }\n\n    if (flags & BTRFS_INODE_NODATACOW && flags_set & BTRFS_INODE_NODATACOW) {\n        EnableWindow(GetDlgItem(hDlg, IDC_COMPRESS), false);\n        EnableWindow(GetDlgItem(hDlg, IDC_COMPRESS_TYPE), false);\n    } else {\n        EnableWindow(GetDlgItem(hDlg, IDC_COMPRESS), true);\n        EnableWindow(GetDlgItem(hDlg, IDC_COMPRESS_TYPE), flags & BTRFS_INODE_COMPRESS && flags_set & BTRFS_INODE_COMPRESS);\n    }\n\n    EnableWindow(GetDlgItem(hDlg, IDC_NODATACOW), !(flags & BTRFS_INODE_COMPRESS) || !(flags_set & BTRFS_INODE_COMPRESS));\n\n    flags_changed = true;\n\n    SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);\n}\n\nvoid BtrfsPropSheet::apply_changes_file(HWND, const wstring& fn) {\n    win_handle h;\n    IO_STATUS_BLOCK iosb;\n    NTSTATUS Status;\n    btrfs_set_inode_info bsii;\n    btrfs_inode_info bii2;\n    ULONG perms = FILE_TRAVERSE | FILE_READ_ATTRIBUTES;\n\n    if (flags_changed || ro_changed)\n        perms |= FILE_WRITE_ATTRIBUTES;\n\n    if (perms_changed || gid_changed || uid_changed)\n        perms |= WRITE_DAC;\n\n    if (mode_set & S_ISUID && (((min_mode & S_ISUID) != (max_mode & S_ISUID)) || ((min_mode & S_ISUID) != (mode & S_ISUID))))\n        perms |= WRITE_OWNER;\n\n    h = CreateFileW(fn.c_str(), perms, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                    OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n    if (h == INVALID_HANDLE_VALUE)\n        throw last_error(GetLastError());\n\n    ZeroMemory(&bsii, sizeof(btrfs_set_inode_info));\n\n    Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_INODE_INFO, nullptr, 0, &bii2, sizeof(btrfs_inode_info));\n\n    if (!NT_SUCCESS(Status))\n        throw ntstatus_error(Status);\n\n    if (bii2.inode == SUBVOL_ROOT_INODE && ro_changed) {\n        BY_HANDLE_FILE_INFORMATION bhfi;\n        FILE_BASIC_INFO fbi;\n\n        if (!GetFileInformationByHandle(h, &bhfi))\n            throw last_error(GetLastError());\n\n        memset(&fbi, 0, sizeof(fbi));\n        fbi.FileAttributes = bhfi.dwFileAttributes;\n\n        if (ro_subvol)\n            fbi.FileAttributes |= FILE_ATTRIBUTE_READONLY;\n        else\n            fbi.FileAttributes &= ~FILE_ATTRIBUTE_READONLY;\n\n        Status = NtSetInformationFile(h, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation);\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n    }\n\n    if (flags_changed || perms_changed || uid_changed || gid_changed || compress_type_changed) {\n        if (flags_changed) {\n            bsii.flags_changed = true;\n            bsii.flags = (bii2.flags & ~flags_set) | (flags & flags_set);\n        }\n\n        if (perms_changed) {\n            bsii.mode_changed = true;\n            bsii.st_mode = (bii2.st_mode & ~mode_set) | (mode & mode_set);\n        }\n\n        if (uid_changed) {\n            bsii.uid_changed = true;\n            bsii.st_uid = uid;\n        }\n\n        if (gid_changed) {\n            bsii.gid_changed = true;\n            bsii.st_gid = gid;\n        }\n\n        if (compress_type_changed) {\n            bsii.compression_type_changed = true;\n            bsii.compression_type = compress_type;\n        }\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0);\n\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n    }\n}\n\nvoid BtrfsPropSheet::apply_changes(HWND hDlg) {\n    UINT num_files, i;\n    WCHAR fn[MAX_PATH]; // FIXME - is this long enough?\n\n    if (various_uids)\n        uid_changed = false;\n\n    if (various_gids)\n        gid_changed = false;\n\n    if (!flags_changed && !perms_changed && !uid_changed && !gid_changed && !compress_type_changed && !ro_changed)\n        return;\n\n    if (filename[0] != 0)\n        apply_changes_file(hDlg, filename);\n    else {\n        num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);\n\n        for (i = 0; i < num_files; i++) {\n            if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) {\n                apply_changes_file(hDlg, fn);\n            }\n        }\n    }\n\n    flags_changed = false;\n    perms_changed = false;\n    uid_changed = false;\n    gid_changed = false;\n    ro_changed = false;\n}\n\nvoid BtrfsPropSheet::set_size_on_disk(HWND hwndDlg) {\n    wstring s, size_on_disk, cr, frag;\n    WCHAR old_text[1024];\n    float ratio;\n\n    format_size(totalsize, size_on_disk, true);\n\n    wstring_sprintf(s, size_format, size_on_disk.c_str());\n\n    if (allocsize == sparsesize || totalsize == 0)\n        ratio = 0.0f;\n    else\n        ratio = 100.0f * (1.0f - ((float)totalsize / (float)(allocsize - sparsesize)));\n\n    wstring_sprintf(cr, cr_format, ratio);\n\n    GetDlgItemTextW(hwndDlg, IDC_SIZE_ON_DISK, old_text, sizeof(old_text) / sizeof(WCHAR));\n\n    if (s != old_text)\n        SetDlgItemTextW(hwndDlg, IDC_SIZE_ON_DISK, s.c_str());\n\n    GetDlgItemTextW(hwndDlg, IDC_COMPRESSION_RATIO, old_text, sizeof(old_text) / sizeof(WCHAR));\n\n    if (cr != old_text)\n        SetDlgItemTextW(hwndDlg, IDC_COMPRESSION_RATIO, cr.c_str());\n\n    uint64_t extent_size = (allocsize - sparsesize - sizes[0]) / (sector_size == 0 ? 4096 : sector_size);\n\n    if (num_extents == 0 || extent_size <= 1)\n        ratio = 0.0f;\n    else\n        ratio = 100.0f * ((float)num_extents / (float)(extent_size - 1));\n\n    wstring_sprintf(frag, frag_format, ratio);\n\n    GetDlgItemTextW(hwndDlg, IDC_FRAGMENTATION, old_text, sizeof(old_text) / sizeof(WCHAR));\n\n    if (frag != old_text)\n        SetDlgItemTextW(hwndDlg, IDC_FRAGMENTATION, frag.c_str());\n}\n\nvoid BtrfsPropSheet::change_perm_flag(HWND hDlg, ULONG flag, UINT state) {\n    if (state == BST_CHECKED) {\n        mode |= flag;\n        mode_set |= flag;\n    } else if (state == BST_UNCHECKED) {\n        mode &= ~flag;\n        mode_set |= flag;\n    } else if (state == BST_INDETERMINATE) {\n        mode_set = ~flag;\n    }\n\n    perms_changed = true;\n\n    SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);\n}\n\nvoid BtrfsPropSheet::change_uid(HWND hDlg, uint32_t uid) {\n    if (this->uid != uid) {\n        this->uid = uid;\n        uid_changed = true;\n\n        SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);\n    }\n}\n\nvoid BtrfsPropSheet::change_gid(HWND hDlg, uint32_t gid) {\n    if (this->gid != gid) {\n        this->gid = gid;\n        gid_changed = true;\n\n        SendMessageW(GetParent(hDlg), PSM_CHANGED, (WPARAM)hDlg, 0);\n    }\n}\n\nvoid BtrfsPropSheet::update_size_details_dialog(HWND hDlg) {\n    wstring size;\n    WCHAR old_text[1024];\n    int i;\n    ULONG items[] = { IDC_SIZE_INLINE, IDC_SIZE_UNCOMPRESSED, IDC_SIZE_ZLIB, IDC_SIZE_LZO, IDC_SIZE_ZSTD };\n\n    for (i = 0; i < 5; i++) {\n        format_size(sizes[i], size, true);\n\n        GetDlgItemTextW(hDlg, items[i], old_text, sizeof(old_text) / sizeof(WCHAR));\n\n        if (size != old_text)\n            SetDlgItemTextW(hDlg, items[i], size.c_str());\n    }\n}\n\nstatic INT_PTR CALLBACK SizeDetailsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n            {\n                BtrfsPropSheet* bps = (BtrfsPropSheet*)lParam;\n\n                SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)bps);\n\n                bps->update_size_details_dialog(hwndDlg);\n\n                if (bps->thread)\n                    SetTimer(hwndDlg, 1, 250, nullptr);\n\n                return true;\n            }\n\n            case WM_COMMAND:\n                if (HIWORD(wParam) == BN_CLICKED && (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)) {\n                    EndDialog(hwndDlg, 0);\n                    return true;\n                }\n            break;\n\n            case WM_TIMER:\n            {\n                BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n\n                if (bps) {\n                    bps->update_size_details_dialog(hwndDlg);\n\n                    if (!bps->thread)\n                        KillTimer(hwndDlg, 1);\n                }\n\n                break;\n            }\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nstatic void set_check_box(HWND hwndDlg, ULONG id, uint64_t min, uint64_t max) {\n    if (min && max) {\n        SendDlgItemMessageW(hwndDlg, id, BM_SETCHECK, BST_CHECKED, 0);\n    } else if (!min && !max) {\n        SendDlgItemMessageW(hwndDlg, id, BM_SETCHECK, BST_UNCHECKED, 0);\n    } else {\n        LONG_PTR style;\n\n        style = GetWindowLongPtrW(GetDlgItem(hwndDlg, id), GWL_STYLE);\n        style &= ~BS_AUTOCHECKBOX;\n        style |= BS_AUTO3STATE;\n        SetWindowLongPtrW(GetDlgItem(hwndDlg, id), GWL_STYLE, style);\n\n        SendDlgItemMessageW(hwndDlg, id, BM_SETCHECK, BST_INDETERMINATE, 0);\n    }\n}\n\nvoid BtrfsPropSheet::open_as_admin(HWND hwndDlg) {\n    ULONG num_files, i;\n    WCHAR fn[MAX_PATH], modfn[MAX_PATH];\n\n    num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);\n\n    if (num_files == 0)\n        return;\n\n    GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR));\n\n    for (i = 0; i < num_files; i++) {\n        if (DragQueryFileW((HDROP)stgm.hGlobal, i, fn, sizeof(fn) / sizeof(WCHAR))) {\n            wstring t;\n            SHELLEXECUTEINFOW sei;\n\n            t = L\"\\\"\"s + modfn + L\"\\\",ShowPropSheet \"s + fn;\n\n            RtlZeroMemory(&sei, sizeof(sei));\n\n            sei.cbSize = sizeof(sei);\n            sei.hwnd = hwndDlg;\n            sei.lpVerb = L\"runas\";\n            sei.lpFile = L\"rundll32.exe\";\n            sei.lpParameters = t.c_str();\n            sei.nShow = SW_SHOW;\n            sei.fMask = SEE_MASK_NOCLOSEPROCESS;\n\n            if (!ShellExecuteExW(&sei))\n                throw last_error(GetLastError());\n\n            WaitForSingleObject(sei.hProcess, INFINITE);\n            CloseHandle(sei.hProcess);\n\n            load_file_list();\n            init_propsheet(hwndDlg);\n        }\n    }\n}\n\n// based on functions in sys/sysmacros.h\n#define major(rdev) ((((rdev) >> 8) & 0xFFF) | ((uint32_t)((rdev) >> 32) & ~0xFFF))\n#define minor(rdev) (((rdev) & 0xFF) | ((uint32_t)((rdev) >> 12) & ~0xFF))\n\nvoid BtrfsPropSheet::init_propsheet(HWND hwndDlg) {\n    wstring s;\n    ULONG sr;\n    int i;\n    HWND comptype;\n\n    static ULONG perm_controls[] = { IDC_USERR, IDC_USERW, IDC_USERX, IDC_GROUPR, IDC_GROUPW, IDC_GROUPX, IDC_OTHERR, IDC_OTHERW, IDC_OTHERX,\n                                     IDC_SETUID, IDC_SETGID, IDC_STICKY, 0 };\n    static ULONG perms[] = { S_IRUSR, S_IWUSR, S_IXUSR, S_IRGRP, S_IWGRP, S_IXGRP, S_IROTH, S_IWOTH, S_IXOTH, S_ISUID, S_ISGID, S_ISVTX, 0 };\n    static ULONG comp_types[] = { IDS_COMPRESS_ANY, IDS_COMPRESS_ZLIB, IDS_COMPRESS_LZO, IDS_COMPRESS_ZSTD, 0 };\n\n    if (various_subvols) {\n        if (!load_string(module, IDS_VARIOUS, s))\n            throw last_error(GetLastError());\n    } else\n        wstring_sprintf(s, L\"%llx\", subvol);\n\n    SetDlgItemTextW(hwndDlg, IDC_SUBVOL, s.c_str());\n\n    if (various_inodes) {\n        if (!load_string(module, IDS_VARIOUS, s))\n            throw last_error(GetLastError());\n    } else\n        wstring_sprintf(s, L\"%llx\", inode);\n\n    SetDlgItemTextW(hwndDlg, IDC_INODE, s.c_str());\n\n    if (various_types)\n        sr = IDS_VARIOUS;\n    else\n        sr = inode_type_to_string_ref(type);\n\n    if (various_inodes) {\n        if (sr == IDS_INODE_CHAR)\n            sr = IDS_INODE_CHAR_SIMPLE;\n        else if (sr == IDS_INODE_BLOCK)\n            sr = IDS_INODE_BLOCK_SIMPLE;\n    }\n\n    if (sr == IDS_INODE_UNKNOWN) {\n        wstring t;\n\n        if (!load_string(module, sr, t))\n            throw last_error(GetLastError());\n\n        wstring_sprintf(s, t, type);\n    } else if (sr == IDS_INODE_CHAR || sr == IDS_INODE_BLOCK) {\n        wstring t;\n\n        if (!load_string(module, sr, t))\n            throw last_error(GetLastError());\n\n        wstring_sprintf(s, t, major(rdev), minor(rdev));\n    } else {\n        if (!load_string(module, sr, s))\n            throw last_error(GetLastError());\n    }\n\n    SetDlgItemTextW(hwndDlg, IDC_TYPE, s.c_str());\n\n    if (size_format[0] == 0)\n        GetDlgItemTextW(hwndDlg, IDC_SIZE_ON_DISK, size_format, sizeof(size_format) / sizeof(WCHAR));\n\n    if (cr_format[0] == 0)\n        GetDlgItemTextW(hwndDlg, IDC_COMPRESSION_RATIO, cr_format, sizeof(cr_format) / sizeof(WCHAR));\n\n    if (frag_format[0] == 0)\n        GetDlgItemTextW(hwndDlg, IDC_FRAGMENTATION, frag_format, sizeof(frag_format) / sizeof(WCHAR));\n\n    set_size_on_disk(hwndDlg);\n\n    if (thread)\n        SetTimer(hwndDlg, 1, 250, nullptr);\n\n    set_check_box(hwndDlg, IDC_NODATACOW, min_flags & BTRFS_INODE_NODATACOW, max_flags & BTRFS_INODE_NODATACOW);\n    set_check_box(hwndDlg, IDC_COMPRESS, min_flags & BTRFS_INODE_COMPRESS, max_flags & BTRFS_INODE_COMPRESS);\n\n    if (min_flags & BTRFS_INODE_NODATACOW || max_flags & BTRFS_INODE_NODATACOW)\n        EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS), false);\n    else if (min_flags & BTRFS_INODE_COMPRESS || max_flags & BTRFS_INODE_COMPRESS)\n        EnableWindow(GetDlgItem(hwndDlg, IDC_NODATACOW), false);\n\n    comptype = GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE);\n\n    while (SendMessageW(comptype, CB_GETCOUNT, 0, 0) > 0) {\n        SendMessageW(comptype, CB_DELETESTRING, 0, 0);\n    }\n\n    if (min_compression_type != max_compression_type) {\n        SendMessageW(comptype, CB_ADDSTRING, 0, (LPARAM)L\"\");\n        SendMessageW(comptype, CB_SETCURSEL, 0, 0);\n    }\n\n    i = 0;\n    while (comp_types[i] != 0) {\n        wstring t;\n\n        if (!load_string(module, comp_types[i], t))\n            throw last_error(GetLastError());\n\n        SendMessageW(comptype, CB_ADDSTRING, 0, (LPARAM)t.c_str());\n\n        i++;\n    }\n\n    if (min_compression_type == max_compression_type) {\n        SendMessageW(comptype, CB_SETCURSEL, min_compression_type, 0);\n        compress_type = min_compression_type;\n    }\n\n    EnableWindow(comptype, max_flags & BTRFS_INODE_COMPRESS);\n\n    i = 0;\n    while (perm_controls[i] != 0) {\n        set_check_box(hwndDlg, perm_controls[i], min_mode & perms[i], max_mode & perms[i]);\n        i++;\n    }\n\n    if (various_uids) {\n        if (!load_string(module, IDS_VARIOUS, s))\n            throw last_error(GetLastError());\n\n        EnableWindow(GetDlgItem(hwndDlg, IDC_UID), 0);\n    } else\n        s = to_wstring(uid);\n\n    SetDlgItemTextW(hwndDlg, IDC_UID, s.c_str());\n\n    if (various_gids) {\n        if (!load_string(module, IDS_VARIOUS, s))\n            throw last_error(GetLastError());\n\n        EnableWindow(GetDlgItem(hwndDlg, IDC_GID), 0);\n    } else\n        s = to_wstring(gid);\n\n    SetDlgItemTextW(hwndDlg, IDC_GID, s.c_str());\n\n    ShowWindow(GetDlgItem(hwndDlg, IDC_SUBVOL_RO), has_subvols);\n\n    if (has_subvols)\n        set_check_box(hwndDlg, IDC_SUBVOL_RO, ro_subvol, various_ro ? (!ro_subvol) : ro_subvol);\n\n    if (!can_change_nocow)\n        EnableWindow(GetDlgItem(hwndDlg, IDC_NODATACOW), 0);\n\n    if (!can_change_perms) {\n        i = 0;\n        while (perm_controls[i] != 0) {\n            EnableWindow(GetDlgItem(hwndDlg, perm_controls[i]), 0);\n            i++;\n        }\n\n        EnableWindow(GetDlgItem(hwndDlg, IDC_UID), 0);\n        EnableWindow(GetDlgItem(hwndDlg, IDC_GID), 0);\n        EnableWindow(GetDlgItem(hwndDlg, IDC_SETUID), 0);\n    }\n\n    if (readonly) {\n        EnableWindow(GetDlgItem(hwndDlg, IDC_NODATACOW), 0);\n        EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS), 0);\n        EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE), 0);\n    }\n\n    if (show_admin_button) {\n        SendMessageW(GetDlgItem(hwndDlg, IDC_OPEN_ADMIN), BCM_SETSHIELD, 0, true);\n        ShowWindow(GetDlgItem(hwndDlg, IDC_OPEN_ADMIN), SW_SHOW);\n    } else\n        ShowWindow(GetDlgItem(hwndDlg, IDC_OPEN_ADMIN), SW_HIDE);\n}\n\nstatic INT_PTR CALLBACK PropSheetDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n            {\n                PROPSHEETPAGEW* psp = (PROPSHEETPAGEW*)lParam;\n                BtrfsPropSheet* bps = (BtrfsPropSheet*)psp->lParam;\n\n                EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB);\n\n                SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)bps);\n\n                bps->init_propsheet(hwndDlg);\n\n                return false;\n            }\n\n            case WM_COMMAND:\n            {\n                BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n\n                if (bps && !bps->readonly) {\n                    switch (HIWORD(wParam)) {\n                        case BN_CLICKED: {\n                            switch (LOWORD(wParam)) {\n                                case IDC_NODATACOW:\n                                    bps->change_inode_flag(hwndDlg, BTRFS_INODE_NODATACOW, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_COMPRESS:\n                                    bps->change_inode_flag(hwndDlg, BTRFS_INODE_COMPRESS, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n\n                                    EnableWindow(GetDlgItem(hwndDlg, IDC_COMPRESS_TYPE), IsDlgButtonChecked(hwndDlg, LOWORD(wParam)) != BST_UNCHECKED);\n                                break;\n\n                                case IDC_USERR:\n                                    bps->change_perm_flag(hwndDlg, S_IRUSR, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_USERW:\n                                    bps->change_perm_flag(hwndDlg, S_IWUSR, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_USERX:\n                                    bps->change_perm_flag(hwndDlg, S_IXUSR, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_GROUPR:\n                                    bps->change_perm_flag(hwndDlg, S_IRGRP, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_GROUPW:\n                                    bps->change_perm_flag(hwndDlg, S_IWGRP, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_GROUPX:\n                                    bps->change_perm_flag(hwndDlg, S_IXGRP, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_OTHERR:\n                                    bps->change_perm_flag(hwndDlg, S_IROTH, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_OTHERW:\n                                    bps->change_perm_flag(hwndDlg, S_IWOTH, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_OTHERX:\n                                    bps->change_perm_flag(hwndDlg, S_IXOTH, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_SETUID:\n                                    bps->change_perm_flag(hwndDlg, S_ISUID, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_SETGID:\n                                    bps->change_perm_flag(hwndDlg, S_ISGID, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_STICKY:\n                                    bps->change_perm_flag(hwndDlg, S_ISVTX, IsDlgButtonChecked(hwndDlg, LOWORD(wParam)));\n                                break;\n\n                                case IDC_SUBVOL_RO:\n                                    switch (IsDlgButtonChecked(hwndDlg, LOWORD(wParam))) {\n                                        case BST_CHECKED:\n                                            bps->ro_subvol = true;\n                                            bps->ro_changed = true;\n                                        break;\n\n                                        case BST_UNCHECKED:\n                                            bps->ro_subvol = false;\n                                            bps->ro_changed = true;\n                                        break;\n\n                                        case BST_INDETERMINATE:\n                                            bps->ro_changed = false;\n                                        break;\n                                    }\n\n                                    SendMessageW(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0);\n                                break;\n\n                                case IDC_OPEN_ADMIN:\n                                    bps->open_as_admin(hwndDlg);\n                                break;\n                            }\n\n                            break;\n                        }\n\n                        case EN_CHANGE: {\n                            switch (LOWORD(wParam)) {\n                                case IDC_UID: {\n                                    WCHAR s[255];\n\n                                    GetDlgItemTextW(hwndDlg, LOWORD(wParam), s, sizeof(s) / sizeof(WCHAR));\n\n                                    bps->change_uid(hwndDlg, _wtoi(s));\n                                    break;\n                                }\n\n                                case IDC_GID: {\n                                    WCHAR s[255];\n\n                                    GetDlgItemTextW(hwndDlg, LOWORD(wParam), s, sizeof(s) / sizeof(WCHAR));\n\n                                    bps->change_gid(hwndDlg, _wtoi(s));\n                                    break;\n                                }\n                            }\n\n                            break;\n                        }\n\n                        case CBN_SELCHANGE: {\n                            switch (LOWORD(wParam)) {\n                                case IDC_COMPRESS_TYPE: {\n                                    auto sel = SendMessageW(GetDlgItem(hwndDlg, LOWORD(wParam)), CB_GETCURSEL, 0, 0);\n\n                                    if (bps->min_compression_type != bps->max_compression_type) {\n                                        if (sel == 0)\n                                            bps->compress_type_changed = false;\n                                        else {\n                                            bps->compress_type = (uint8_t)(sel - 1);\n                                            bps->compress_type_changed = true;\n                                        }\n                                    } else {\n                                        bps->compress_type = (uint8_t)sel;\n                                        bps->compress_type_changed = true;\n                                    }\n\n                                    SendMessageW(GetParent(hwndDlg), PSM_CHANGED, (WPARAM)hwndDlg, 0);\n\n                                    break;\n                                }\n                            }\n\n                            break;\n                        }\n                    }\n                }\n\n                break;\n            }\n\n            case WM_NOTIFY:\n            {\n                switch (((LPNMHDR)lParam)->code) {\n                    case PSN_KILLACTIVE:\n                        SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, false);\n                    break;\n\n                    case PSN_APPLY: {\n                        BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n\n                        bps->apply_changes(hwndDlg);\n                        SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR);\n                        break;\n                    }\n\n                    case NM_CLICK:\n                    case NM_RETURN: {\n                        if (((LPNMHDR)lParam)->hwndFrom == GetDlgItem(hwndDlg, IDC_SIZE_ON_DISK)) {\n                            PNMLINK pNMLink = (PNMLINK)lParam;\n\n                            if (pNMLink->item.iLink == 0)\n                                DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_SIZE_DETAILS), hwndDlg, SizeDetailsDlgProc, GetWindowLongPtrW(hwndDlg, GWLP_USERDATA));\n                        }\n                        break;\n                    }\n                }\n\n                break;\n            }\n\n            case WM_TIMER:\n            {\n                BtrfsPropSheet* bps = (BtrfsPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n\n                if (bps) {\n                    bps->set_size_on_disk(hwndDlg);\n\n                    if (!bps->thread)\n                        KillTimer(hwndDlg, 1);\n                }\n\n                break;\n            }\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nHRESULT __stdcall BtrfsPropSheet::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) {\n    try {\n        PROPSHEETPAGEW psp;\n        HPROPSHEETPAGE hPage;\n        INITCOMMONCONTROLSEX icex;\n\n        if (ignore)\n            return S_OK;\n\n        icex.dwSize = sizeof(icex);\n        icex.dwICC = ICC_LINK_CLASS;\n\n        if (!InitCommonControlsEx(&icex))\n            throw string_error(IDS_INITCOMMONCONTROLSEX_FAILED);\n\n        psp.dwSize = sizeof(psp);\n        psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE;\n        psp.hInstance = module;\n        psp.pszTemplate = MAKEINTRESOURCEW(IDD_PROP_SHEET);\n        psp.hIcon = 0;\n        psp.pszTitle = MAKEINTRESOURCEW(IDS_PROP_SHEET_TITLE);\n        psp.pfnDlgProc = (DLGPROC)PropSheetDlgProc;\n        psp.pcRefParent = (UINT*)&objs_loaded;\n        psp.pfnCallback = nullptr;\n        psp.lParam = (LPARAM)this;\n\n        hPage = CreatePropertySheetPageW(&psp);\n\n        if (hPage) {\n            if (pfnAddPage(hPage, lParam)) {\n                this->AddRef();\n                return S_OK;\n            } else\n                DestroyPropertySheetPage(hPage);\n        } else\n            return E_OUTOFMEMORY;\n    } catch (const exception& e) {\n        error_message(nullptr, e.what());\n    }\n\n    return E_FAIL;\n}\n\nHRESULT __stdcall BtrfsPropSheet::ReplacePage(UINT, LPFNADDPROPSHEETPAGE, LPARAM) {\n    return S_OK;\n}\n\nextern \"C\" {\n\nvoid CALLBACK ShowPropSheetW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    try {\n        BtrfsPropSheet bps;\n        PROPSHEETPAGEW psp;\n        PROPSHEETHEADERW psh;\n        INITCOMMONCONTROLSEX icex;\n        wstring title;\n\n        set_dpi_aware();\n\n        load_string(module, IDS_STANDALONE_PROPSHEET_TITLE, title);\n\n        bps.set_cmdline(lpszCmdLine);\n\n        icex.dwSize = sizeof(icex);\n        icex.dwICC = ICC_LINK_CLASS;\n\n        if (!InitCommonControlsEx(&icex))\n            throw string_error(IDS_INITCOMMONCONTROLSEX_FAILED);\n\n        psp.dwSize = sizeof(psp);\n        psp.dwFlags = PSP_USETITLE;\n        psp.hInstance = module;\n        psp.pszTemplate = MAKEINTRESOURCEW(IDD_PROP_SHEET);\n        psp.hIcon = 0;\n        psp.pszTitle = MAKEINTRESOURCEW(IDS_PROP_SHEET_TITLE);\n        psp.pfnDlgProc = (DLGPROC)PropSheetDlgProc;\n        psp.pfnCallback = nullptr;\n        psp.lParam = (LPARAM)&bps;\n\n        memset(&psh, 0, sizeof(PROPSHEETHEADERW));\n\n        psh.dwSize = sizeof(PROPSHEETHEADERW);\n        psh.dwFlags = PSH_PROPSHEETPAGE;\n        psh.hwndParent = hwnd;\n        psh.hInstance = psp.hInstance;\n        psh.pszCaption = title.c_str();\n        psh.nPages = 1;\n        psh.ppsp = &psp;\n\n        if (PropertySheetW(&psh) < 0)\n            throw last_error(GetLastError());\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n}\n\n}\n"
  },
  {
    "path": "src/shellext/propsheet.h",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\n#include <shlobj.h>\n#include <deque>\n#include \"../btrfsioctl.h\"\n\n#ifndef S_IRUSR\n#define S_IRUSR 0000400\n#endif\n\n#ifndef S_IWUSR\n#define S_IWUSR 0000200\n#endif\n\n#ifndef S_IXUSR\n#define S_IXUSR 0000100\n#endif\n\n#ifndef S_IRGRP\n#define S_IRGRP (S_IRUSR >> 3)\n#endif\n\n#ifndef S_IWGRP\n#define S_IWGRP (S_IWUSR >> 3)\n#endif\n\n#ifndef S_IXGRP\n#define S_IXGRP (S_IXUSR >> 3)\n#endif\n\n#ifndef S_IROTH\n#define S_IROTH (S_IRGRP >> 3)\n#endif\n\n#ifndef S_IWOTH\n#define S_IWOTH (S_IWGRP >> 3)\n#endif\n\n#ifndef S_IXOTH\n#define S_IXOTH (S_IXGRP >> 3)\n#endif\n\n#ifndef S_ISUID\n#define S_ISUID 0004000\n#endif\n\n#ifndef S_ISGID\n#define S_ISGID 0002000\n#endif\n\n#ifndef S_ISVTX\n#define S_ISVTX 0001000\n#endif\n\n#define BTRFS_INODE_NODATASUM   0x001\n#define BTRFS_INODE_NODATACOW   0x002\n#define BTRFS_INODE_READONLY    0x004\n#define BTRFS_INODE_NOCOMPRESS  0x008\n#define BTRFS_INODE_PREALLOC    0x010\n#define BTRFS_INODE_SYNC        0x020\n#define BTRFS_INODE_IMMUTABLE   0x040\n#define BTRFS_INODE_APPEND      0x080\n#define BTRFS_INODE_NODUMP      0x100\n#define BTRFS_INODE_NOATIME     0x200\n#define BTRFS_INODE_DIRSYNC     0x400\n#define BTRFS_INODE_COMPRESS    0x800\n\nextern LONG objs_loaded;\n\nclass BtrfsPropSheet : public IShellExtInit, IShellPropSheetExt {\npublic:\n    BtrfsPropSheet() {\n        refcount = 0;\n        ignore = true;\n        stgm_set = false;\n        readonly = false;\n        flags_changed = false;\n        perms_changed = false;\n        uid_changed = false;\n        gid_changed = false;\n        compress_type_changed = false;\n        ro_changed = false;\n        can_change_perms = false;\n        show_admin_button = false;\n        thread = nullptr;\n        mode = mode_set = 0;\n        flags = flags_set = 0;\n        has_subvols = false;\n        filename = L\"\";\n\n        sizes[0] = sizes[1] = sizes[2] = sizes[3] = sizes[4] = 0;\n        totalsize = allocsize = sparsesize = 0;\n        num_extents = 0;\n        sector_size = 0;\n        size_format[0] = 0;\n        cr_format[0] = 0;\n        frag_format[0] = 0;\n\n        InterlockedIncrement(&objs_loaded);\n    }\n\n    virtual ~BtrfsPropSheet() {\n        if (stgm_set)\n            ReleaseStgMedium(&stgm);\n\n        InterlockedDecrement(&objs_loaded);\n    }\n\n    // IUnknown\n\n    HRESULT __stdcall QueryInterface(REFIID riid, void **ppObj);\n\n    ULONG __stdcall AddRef() {\n        return InterlockedIncrement(&refcount);\n    }\n\n    ULONG __stdcall Release() {\n        LONG rc = InterlockedDecrement(&refcount);\n\n        if (rc == 0)\n            delete this;\n\n        return rc;\n    }\n\n    // IShellExtInit\n\n    virtual HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) override;\n\n    // IShellPropSheetExt\n\n    virtual HRESULT __stdcall AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) override;\n    virtual HRESULT __stdcall ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam) override;\n\n    void init_propsheet(HWND hwndDlg);\n    void change_inode_flag(HWND hDlg, uint64_t flag, UINT state);\n    void change_perm_flag(HWND hDlg, ULONG perm, UINT state);\n    void change_uid(HWND hDlg, uint32_t uid);\n    void change_gid(HWND hDlg, uint32_t gid);\n    void apply_changes(HWND hDlg);\n    void set_size_on_disk(HWND hwndDlg);\n    DWORD search_list_thread();\n    void do_search(const wstring& fn);\n    void update_size_details_dialog(HWND hDlg);\n    void open_as_admin(HWND hwndDlg);\n    void set_cmdline(const wstring& cmdline);\n\n    bool readonly;\n    bool can_change_perms;\n    bool can_change_nocow;\n    WCHAR size_format[255], cr_format[255], frag_format[255];\n    HANDLE thread;\n    uint32_t min_mode, max_mode, mode, mode_set;\n    uint64_t min_flags, max_flags, flags, flags_set;\n    uint64_t subvol, inode, rdev;\n    uint8_t type, min_compression_type, max_compression_type, compress_type;\n    uint32_t uid, gid;\n    bool various_subvols, various_inodes, various_types, various_uids, various_gids, compress_type_changed, has_subvols,\n         ro_subvol, various_ro, ro_changed, show_admin_button;\n\nprivate:\n    LONG refcount;\n    bool ignore;\n    STGMEDIUM stgm;\n    bool stgm_set;\n    bool flags_changed, perms_changed, uid_changed, gid_changed;\n    uint64_t sizes[5], totalsize, allocsize, sparsesize, num_extents;\n    deque<wstring> search_list;\n    wstring filename;\n    uint32_t sector_size;\n\n    void apply_changes_file(HWND hDlg, const wstring& fn);\n    HRESULT check_file(const wstring& fn, UINT i, UINT num_files, UINT* sv);\n    HRESULT load_file_list();\n};\n"
  },
  {
    "path": "src/shellext/recv.cpp",
    "content": "/* Copyright (c) Mark Harmstone 2017\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"shellext.h\"\n#include <windows.h>\n#include <strsafe.h>\n#include <stddef.h>\n#include <sys/stat.h>\n#include <iostream>\n#include \"recv.h\"\n#include \"resource.h\"\n#include \"../crc32c.h\"\n\n#ifndef _MSC_VER\n#include <cpuid.h>\n#else\n#include <intrin.h>\n#endif\n\nstatic const string_view EA_NTACL = \"security.NTACL\";\nstatic const string_view EA_DOSATTRIB = \"user.DOSATTRIB\";\nstatic const string_view EA_REPARSE = \"user.reparse\";\nstatic const string_view EA_EA = \"user.EA\";\nstatic const string_view XATTR_USER = \"user.\";\n\nbool BtrfsRecv::find_tlv(uint8_t* data, ULONG datalen, uint16_t type, void** value, ULONG* len) {\n    size_t off = 0;\n\n    while (off < datalen) {\n        btrfs_send_tlv* tlv = (btrfs_send_tlv*)(data + off);\n        uint8_t* payload = data + off + sizeof(btrfs_send_tlv);\n\n        if (off + sizeof(btrfs_send_tlv) + tlv->length > datalen) // file is truncated\n            return false;\n\n        if (tlv->type == type) {\n            *value = payload;\n            *len = tlv->length;\n            return true;\n        }\n\n        off += sizeof(btrfs_send_tlv) + tlv->length;\n    }\n\n    return false;\n}\n\nvoid BtrfsRecv::cmd_subvol(btrfs_send_command* cmd, uint8_t* data, const win_handle& parent) {\n    string name;\n    BTRFS_UUID* uuid;\n    uint64_t* gen;\n    ULONG uuidlen, genlen;\n    btrfs_create_subvol* bcs;\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n\n    {\n        char* namebuf;\n        ULONG namelen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&namebuf, &namelen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        name = string(namebuf, namelen);\n    }\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_UUID, (void**)&uuid, &uuidlen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"uuid\");\n\n    if (uuidlen < sizeof(BTRFS_UUID))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"uuid\", uuidlen, sizeof(BTRFS_UUID));\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_TRANSID, (void**)&gen, &genlen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"transid\");\n\n    if (genlen < sizeof(uint64_t))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"transid\", genlen, sizeof(uint64_t));\n\n    this->subvol_uuid = *uuid;\n    this->stransid = *gen;\n\n    auto nameu = utf8_to_utf16(name);\n\n    size_t bcslen = offsetof(btrfs_create_subvol, name[0]) + (nameu.length() * sizeof(WCHAR));\n    bcs = (btrfs_create_subvol*)malloc(bcslen);\n\n    bcs->readonly = true;\n    bcs->posix = true;\n    bcs->namelen = (uint16_t)(nameu.length() * sizeof(WCHAR));\n    memcpy(bcs->name, nameu.c_str(), bcs->namelen);\n\n    Status = NtFsControlFile(parent, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0);\n    if (!NT_SUCCESS(Status))\n        throw string_error(IDS_RECV_CREATE_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str());\n\n    subvolpath = dirpath;\n    subvolpath += L\"\\\\\";\n    subvolpath += nameu;\n\n    if (dir != INVALID_HANDLE_VALUE)\n        CloseHandle(dir);\n\n    if (master != INVALID_HANDLE_VALUE)\n        CloseHandle(master);\n\n    master = CreateFileW(subvolpath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                         nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n    if (master == INVALID_HANDLE_VALUE)\n        throw string_error(IDS_RECV_CANT_OPEN_PATH, subvolpath.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n    Status = NtFsControlFile(master, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RESERVE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0);\n    if (!NT_SUCCESS(Status))\n        throw string_error(IDS_RECV_RESERVE_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str());\n\n    dir = CreateFileW(subvolpath.c_str(), FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE,\n                      FILE_SHARE_READ | FILE_SHARE_WRITE,\n                      nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n    if (dir == INVALID_HANDLE_VALUE)\n        throw string_error(IDS_RECV_CANT_OPEN_PATH, subvolpath.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n    subvolpath += L\"\\\\\";\n\n    add_cache_entry(&this->subvol_uuid, this->stransid, subvolpath);\n\n    num_received++;\n}\n\nvoid BtrfsRecv::add_cache_entry(BTRFS_UUID* uuid, uint64_t transid, const wstring& path) {\n    subvol_cache sc;\n\n    sc.uuid = *uuid;\n    sc.transid = transid;\n    sc.path = path;\n\n    cache.push_back(sc);\n}\n\nvoid BtrfsRecv::cmd_snapshot(btrfs_send_command* cmd, uint8_t* data, const win_handle& parent) {\n    string name;\n    BTRFS_UUID *uuid, *parent_uuid;\n    uint64_t *gen, *parent_transid;\n    ULONG uuidlen, genlen, paruuidlen, partransidlen;\n    btrfs_create_snapshot* bcs;\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    wstring parpath;\n    btrfs_find_subvol bfs;\n    WCHAR parpathw[MAX_PATH], volpathw[MAX_PATH];\n    size_t bcslen;\n\n    {\n        char* namebuf;\n        ULONG namelen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&namebuf, &namelen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        name = string(namebuf, namelen);\n    }\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_UUID, (void**)&uuid, &uuidlen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"uuid\");\n\n    if (uuidlen < sizeof(BTRFS_UUID))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"uuid\", uuidlen, sizeof(BTRFS_UUID));\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_TRANSID, (void**)&gen, &genlen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"transid\");\n\n    if (genlen < sizeof(uint64_t))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"transid\", genlen, sizeof(uint64_t));\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_UUID, (void**)&parent_uuid, &paruuidlen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"clone_uuid\");\n\n    if (paruuidlen < sizeof(BTRFS_UUID))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"clone_uuid\", paruuidlen, sizeof(BTRFS_UUID));\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_CTRANSID, (void**)&parent_transid, &partransidlen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"clone_ctransid\");\n\n    if (partransidlen < sizeof(uint64_t))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"clone_ctransid\", partransidlen, sizeof(uint64_t));\n\n    this->subvol_uuid = *uuid;\n    this->stransid = *gen;\n\n    auto nameu = utf8_to_utf16(name);\n\n    bfs.uuid = *parent_uuid;\n    bfs.ctransid = *parent_transid;\n\n    Status = NtFsControlFile(parent, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_FIND_SUBVOL, &bfs, sizeof(btrfs_find_subvol),\n                             parpathw, sizeof(parpathw));\n    if (Status == STATUS_NOT_FOUND)\n        throw string_error(IDS_RECV_CANT_FIND_PARENT_SUBVOL);\n    else if (!NT_SUCCESS(Status))\n        throw string_error(IDS_RECV_FIND_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str());\n\n    if (!GetVolumePathNameW(dirpath.c_str(), volpathw, (sizeof(volpathw) / sizeof(WCHAR)) - 1))\n        throw string_error(IDS_RECV_GETVOLUMEPATHNAME_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n    parpath = volpathw;\n    if (parpath.substr(parpath.length() - 1) == L\"\\\\\")\n        parpath = parpath.substr(0, parpath.length() - 1);\n\n    parpath += parpathw;\n\n    {\n        win_handle subvol = CreateFileW(parpath.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                        nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n        if (subvol == INVALID_HANDLE_VALUE)\n            throw string_error(IDS_RECV_CANT_OPEN_PATH, parpath.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n        bcslen = offsetof(btrfs_create_snapshot, name[0]) + (nameu.length() * sizeof(WCHAR));\n        bcs = (btrfs_create_snapshot*)malloc(bcslen);\n\n        bcs->readonly = true;\n        bcs->posix = true;\n        bcs->subvol = subvol;\n        bcs->namelen = (uint16_t)(nameu.length() * sizeof(WCHAR));\n        memcpy(bcs->name, nameu.c_str(), bcs->namelen);\n\n        Status = NtFsControlFile(parent, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_CREATE_SNAPSHOT, bcs, (ULONG)bcslen, nullptr, 0);\n        if (!NT_SUCCESS(Status))\n            throw string_error(IDS_RECV_CREATE_SNAPSHOT_FAILED, Status, format_ntstatus(Status).c_str());\n    }\n\n    subvolpath = dirpath;\n    subvolpath += L\"\\\\\";\n    subvolpath += nameu;\n\n    if (dir != INVALID_HANDLE_VALUE)\n        CloseHandle(dir);\n\n    if (master != INVALID_HANDLE_VALUE)\n        CloseHandle(master);\n\n    master = CreateFileW(subvolpath.c_str(), GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                         nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n    if (master == INVALID_HANDLE_VALUE)\n        throw string_error(IDS_RECV_CANT_OPEN_PATH, subvolpath.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n    Status = NtFsControlFile(master, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RESERVE_SUBVOL, bcs, (ULONG)bcslen, nullptr, 0);\n    if (!NT_SUCCESS(Status))\n        throw string_error(IDS_RECV_RESERVE_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str());\n\n    dir = CreateFileW(subvolpath.c_str(), FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE,\n                      FILE_SHARE_READ | FILE_SHARE_WRITE,\n                      nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n    if (dir == INVALID_HANDLE_VALUE)\n        throw string_error(IDS_RECV_CANT_OPEN_PATH, subvolpath.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n    subvolpath += L\"\\\\\";\n\n    add_cache_entry(&this->subvol_uuid, this->stransid, subvolpath);\n\n    num_received++;\n}\n\nvoid BtrfsRecv::cmd_mkfile(btrfs_send_command* cmd, uint8_t* data) {\n    uint64_t *inode, *rdev = nullptr, *mode = nullptr;\n    ULONG inodelen;\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    btrfs_mknod* bmn;\n    wstring nameu, pathlinku;\n\n    {\n        char* name;\n        ULONG namelen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&name, &namelen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        nameu = utf8_to_utf16(string(name, namelen));\n    }\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_INODE, (void**)&inode, &inodelen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"inode\");\n\n    if (inodelen < sizeof(uint64_t))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"inode\", inodelen, sizeof(uint64_t));\n\n    if (cmd->cmd == BTRFS_SEND_CMD_MKNOD || cmd->cmd == BTRFS_SEND_CMD_MKFIFO || cmd->cmd == BTRFS_SEND_CMD_MKSOCK) {\n        ULONG rdevlen, modelen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_RDEV, (void**)&rdev, &rdevlen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"rdev\");\n\n        if (rdevlen < sizeof(uint64_t))\n            throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"rdev\", rdev, sizeof(uint64_t));\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_MODE, (void**)&mode, &modelen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"mode\");\n\n        if (modelen < sizeof(uint64_t))\n            throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"mode\", modelen, sizeof(uint64_t));\n    } else if (cmd->cmd == BTRFS_SEND_CMD_SYMLINK) {\n        char* pathlink;\n        ULONG pathlinklen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH_LINK, (void**)&pathlink, &pathlinklen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path_link\");\n\n        pathlinku = utf8_to_utf16(string(pathlink, pathlinklen));\n    }\n\n    size_t bmnsize = sizeof(btrfs_mknod) - sizeof(WCHAR) + (nameu.length() * sizeof(WCHAR));\n    bmn = (btrfs_mknod*)malloc(bmnsize);\n\n    bmn->inode = *inode;\n\n    if (cmd->cmd == BTRFS_SEND_CMD_MKDIR)\n        bmn->type = BTRFS_TYPE_DIRECTORY;\n    else if (cmd->cmd == BTRFS_SEND_CMD_MKNOD)\n        bmn->type = *mode & S_IFCHR ? BTRFS_TYPE_CHARDEV : BTRFS_TYPE_BLOCKDEV;\n    else if (cmd->cmd == BTRFS_SEND_CMD_MKFIFO)\n        bmn->type = BTRFS_TYPE_FIFO;\n    else if (cmd->cmd == BTRFS_SEND_CMD_MKSOCK)\n        bmn->type = BTRFS_TYPE_SOCKET;\n    else\n        bmn->type = BTRFS_TYPE_FILE;\n\n    bmn->st_rdev = rdev ? *rdev : 0;\n    bmn->namelen = (uint16_t)(nameu.length() * sizeof(WCHAR));\n    memcpy(bmn->name, nameu.c_str(), bmn->namelen);\n\n    Status = NtFsControlFile(dir, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_MKNOD, bmn, (ULONG)bmnsize, nullptr, 0);\n    if (!NT_SUCCESS(Status)) {\n        free(bmn);\n        throw string_error(IDS_RECV_MKNOD_FAILED, Status, format_ntstatus(Status).c_str());\n    }\n\n    free(bmn);\n\n    if (cmd->cmd == BTRFS_SEND_CMD_SYMLINK) {\n        REPARSE_DATA_BUFFER* rdb;\n        btrfs_set_inode_info bsii;\n\n        size_t rdblen = offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer[0]) + (2 * pathlinku.length() * sizeof(WCHAR));\n\n        if (rdblen >= 0x10000)\n            throw string_error(IDS_RECV_PATH_TOO_LONG, funcname);\n\n        rdb = (REPARSE_DATA_BUFFER*)malloc(rdblen);\n\n        rdb->ReparseTag = IO_REPARSE_TAG_SYMLINK;\n        rdb->ReparseDataLength = (uint16_t)(rdblen - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer));\n        rdb->Reserved = 0;\n        rdb->SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;\n        rdb->SymbolicLinkReparseBuffer.SubstituteNameLength = (uint16_t)(pathlinku.length() * sizeof(WCHAR));\n        rdb->SymbolicLinkReparseBuffer.PrintNameOffset = (uint16_t)(pathlinku.length() * sizeof(WCHAR));\n        rdb->SymbolicLinkReparseBuffer.PrintNameLength = (uint16_t)(pathlinku.length() * sizeof(WCHAR));\n        rdb->SymbolicLinkReparseBuffer.Flags = SYMLINK_FLAG_RELATIVE;\n\n        memcpy(rdb->SymbolicLinkReparseBuffer.PathBuffer, pathlinku.c_str(), rdb->SymbolicLinkReparseBuffer.SubstituteNameLength);\n        memcpy(rdb->SymbolicLinkReparseBuffer.PathBuffer + (rdb->SymbolicLinkReparseBuffer.SubstituteNameLength / sizeof(WCHAR)),\n                pathlinku.c_str(), rdb->SymbolicLinkReparseBuffer.PrintNameLength);\n\n        win_handle h = CreateFileW((subvolpath + nameu).c_str(), GENERIC_WRITE | WRITE_DAC, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                   nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n        if (h == INVALID_HANDLE_VALUE) {\n            free(rdb);\n            throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, nameu.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n        }\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_SET_REPARSE_POINT, rdb, (ULONG)rdblen, nullptr, 0);\n        if (!NT_SUCCESS(Status)) {\n            free(rdb);\n            throw string_error(IDS_RECV_SET_REPARSE_POINT_FAILED, Status, format_ntstatus(Status).c_str());\n        }\n\n        free(rdb);\n\n        memset(&bsii, 0, sizeof(btrfs_set_inode_info));\n\n        bsii.mode_changed = true;\n        bsii.st_mode = 0777;\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0);\n        if (!NT_SUCCESS(Status))\n            throw string_error(IDS_RECV_SETINODEINFO_FAILED, Status, format_ntstatus(Status).c_str());\n    } else if (cmd->cmd == BTRFS_SEND_CMD_MKNOD || cmd->cmd == BTRFS_SEND_CMD_MKFIFO || cmd->cmd == BTRFS_SEND_CMD_MKSOCK) {\n        uint64_t* mode;\n        ULONG modelen;\n\n        if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_MODE, (void**)&mode, &modelen)) {\n            btrfs_set_inode_info bsii;\n\n            if (modelen < sizeof(uint64_t))\n                throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"mode\", modelen, sizeof(uint64_t));\n\n            win_handle h = CreateFileW((subvolpath + nameu).c_str(), WRITE_DAC, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                       nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n            if (h == INVALID_HANDLE_VALUE)\n                throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, nameu.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n            memset(&bsii, 0, sizeof(btrfs_set_inode_info));\n\n            bsii.mode_changed = true;\n            bsii.st_mode = (uint32_t)*mode;\n\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0);\n            if (!NT_SUCCESS(Status))\n                throw string_error(IDS_RECV_SETINODEINFO_FAILED, Status, format_ntstatus(Status).c_str());\n        }\n    }\n}\n\nvoid BtrfsRecv::cmd_rename(btrfs_send_command* cmd, uint8_t* data) {\n    wstring pathu, path_tou;\n\n    {\n        char* path;\n        ULONG path_len;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &path_len))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        pathu = utf8_to_utf16(string(path, path_len));\n    }\n\n    {\n        char* path_to;\n        ULONG path_to_len;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH_TO, (void**)&path_to, &path_to_len))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path_to\");\n\n        path_tou = utf8_to_utf16(string(path_to, path_to_len));\n    }\n\n    if (!MoveFileW((subvolpath + pathu).c_str(), (subvolpath + path_tou).c_str()))\n        throw string_error(IDS_RECV_MOVEFILE_FAILED, pathu.c_str(), path_tou.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n}\n\nvoid BtrfsRecv::cmd_link(btrfs_send_command* cmd, uint8_t* data) {\n    wstring pathu, path_linku;\n\n    {\n        char* path;\n        ULONG path_len;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &path_len))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        pathu = utf8_to_utf16(string(path, path_len));\n    }\n\n    {\n        char* path_link;\n        ULONG path_link_len;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH_LINK, (void**)&path_link, &path_link_len))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path_link\");\n\n        path_linku = utf8_to_utf16(string(path_link, path_link_len));\n    }\n\n    if (!CreateHardLinkW((subvolpath + pathu).c_str(), (subvolpath + path_linku).c_str(), nullptr))\n        throw string_error(IDS_RECV_CREATEHARDLINK_FAILED, pathu.c_str(), path_linku.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n}\n\nvoid BtrfsRecv::cmd_unlink(btrfs_send_command* cmd, uint8_t* data) {\n    wstring pathu;\n    ULONG att;\n\n    {\n        char* path;\n        ULONG pathlen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        pathu = utf8_to_utf16(string(path, pathlen));\n    }\n\n    att = GetFileAttributesW((subvolpath + pathu).c_str());\n    if (att == INVALID_FILE_ATTRIBUTES)\n        throw string_error(IDS_RECV_GETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n    if (att & FILE_ATTRIBUTE_READONLY) {\n        if (!SetFileAttributesW((subvolpath + pathu).c_str(), att & ~FILE_ATTRIBUTE_READONLY))\n            throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n    }\n\n    if (!DeleteFileW((subvolpath + pathu).c_str()))\n        throw string_error(IDS_RECV_DELETEFILE_FAILED, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n}\n\nvoid BtrfsRecv::cmd_rmdir(btrfs_send_command* cmd, uint8_t* data) {\n    wstring pathu;\n    ULONG att;\n\n    {\n        char* path;\n        ULONG pathlen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        pathu = utf8_to_utf16(string(path, pathlen));\n    }\n\n    att = GetFileAttributesW((subvolpath + pathu).c_str());\n    if (att == INVALID_FILE_ATTRIBUTES)\n        throw string_error(IDS_RECV_GETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n    if (att & FILE_ATTRIBUTE_READONLY) {\n        if (!SetFileAttributesW((subvolpath + pathu).c_str(), att & ~FILE_ATTRIBUTE_READONLY))\n            throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n    }\n\n    if (!RemoveDirectoryW((subvolpath + pathu).c_str()))\n        throw string_error(IDS_RECV_REMOVEDIRECTORY_FAILED, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n}\n\nvoid BtrfsRecv::cmd_setxattr(btrfs_send_command* cmd, uint8_t* data) {\n    string xattrname;\n    uint8_t* xattrdata;\n    ULONG xattrdatalen;\n    wstring pathu;\n\n    {\n        char* path;\n        ULONG pathlen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        pathu = utf8_to_utf16(string(path, pathlen));\n    }\n\n    {\n        char* xattrnamebuf;\n        ULONG xattrnamelen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_XATTR_NAME, (void**)&xattrnamebuf, &xattrnamelen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"xattr_name\");\n\n        xattrname = string(xattrnamebuf, xattrnamelen);\n    }\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_XATTR_DATA, (void**)&xattrdata, &xattrdatalen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"xattr_data\");\n\n    if (xattrname.length() > XATTR_USER.length() && xattrname.substr(0, XATTR_USER.length()) == XATTR_USER &&\n        xattrname != EA_DOSATTRIB && xattrname != EA_EA && xattrname != EA_REPARSE) {\n        ULONG att;\n\n        auto streamname = utf8_to_utf16(xattrname);\n\n        att = GetFileAttributesW((subvolpath + pathu).c_str());\n        if (att == INVALID_FILE_ATTRIBUTES)\n            throw string_error(IDS_RECV_GETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n        if (att & FILE_ATTRIBUTE_READONLY) {\n            if (!SetFileAttributesW((subvolpath + pathu).c_str(), att & ~FILE_ATTRIBUTE_READONLY))\n                throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n        }\n\n        streamname = streamname.substr(XATTR_USER.length());\n\n        win_handle h = CreateFileW((subvolpath + pathu + L\":\" + streamname).c_str(), GENERIC_WRITE, 0,\n                                   nullptr, CREATE_ALWAYS, FILE_FLAG_POSIX_SEMANTICS, nullptr);\n        if (h == INVALID_HANDLE_VALUE)\n            throw string_error(IDS_RECV_CANT_CREATE_FILE, (pathu + L\":\" + streamname).c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n        if (xattrdatalen > 0) {\n            if (!WriteFile(h, xattrdata, xattrdatalen, nullptr, nullptr))\n                throw string_error(IDS_RECV_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n        }\n\n        if (att & FILE_ATTRIBUTE_READONLY) {\n            if (!SetFileAttributesW((subvolpath + pathu).c_str(), att))\n                throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n        }\n    } else {\n        IO_STATUS_BLOCK iosb;\n        NTSTATUS Status;\n        ULONG perms = FILE_WRITE_ATTRIBUTES;\n        btrfs_set_xattr* bsxa;\n\n        if (xattrname == EA_NTACL)\n            perms |= WRITE_DAC | WRITE_OWNER;\n        else if (xattrname == EA_EA)\n            perms |= FILE_WRITE_EA;\n\n        win_handle h = CreateFileW((subvolpath + pathu).c_str(), perms, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                   nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr);\n        if (h == INVALID_HANDLE_VALUE)\n            throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n        size_t bsxalen = offsetof(btrfs_set_xattr, data[0]) + xattrname.length() + xattrdatalen;\n        bsxa = (btrfs_set_xattr*)malloc(bsxalen);\n        if (!bsxa)\n            throw string_error(IDS_OUT_OF_MEMORY);\n\n        bsxa->namelen = (uint16_t)xattrname.length();\n        bsxa->valuelen = (uint16_t)xattrdatalen;\n        memcpy(bsxa->data, xattrname.c_str(), xattrname.length());\n        memcpy(&bsxa->data[xattrname.length()], xattrdata, xattrdatalen);\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, bsxa, (ULONG)bsxalen, nullptr, 0);\n        if (!NT_SUCCESS(Status)) {\n            free(bsxa);\n            throw string_error(IDS_RECV_SETXATTR_FAILED, Status, format_ntstatus(Status).c_str());\n        }\n\n        free(bsxa);\n    }\n}\n\nvoid BtrfsRecv::cmd_removexattr(btrfs_send_command* cmd, uint8_t* data) {\n    wstring pathu;\n    string xattrname;\n\n    {\n        char* path;\n        ULONG pathlen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        pathu = utf8_to_utf16(string(path, pathlen));\n    }\n\n    {\n        char* xattrnamebuf;\n        ULONG xattrnamelen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_XATTR_NAME, (void**)&xattrnamebuf, &xattrnamelen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"xattr_name\");\n\n        xattrname = string(xattrnamebuf, xattrnamelen);\n    }\n\n    if (xattrname.length() > XATTR_USER.length() && xattrname.substr(0, XATTR_USER.length()) == XATTR_USER && xattrname != EA_DOSATTRIB && xattrname != EA_EA) { // deleting stream\n        ULONG att;\n\n        auto streamname = utf8_to_utf16(xattrname);\n\n        streamname = streamname.substr(XATTR_USER.length());\n\n        att = GetFileAttributesW((subvolpath + pathu).c_str());\n        if (att == INVALID_FILE_ATTRIBUTES)\n            throw string_error(IDS_RECV_GETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n        if (att & FILE_ATTRIBUTE_READONLY) {\n            if (!SetFileAttributesW((subvolpath + pathu).c_str(), att & ~FILE_ATTRIBUTE_READONLY))\n                throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n        }\n\n        if (!DeleteFileW((subvolpath + pathu + L\":\" + streamname).c_str()))\n            throw string_error(IDS_RECV_DELETEFILE_FAILED, (pathu + L\":\" + streamname).c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n        if (att & FILE_ATTRIBUTE_READONLY) {\n            if (!SetFileAttributesW((subvolpath + pathu).c_str(), att))\n                throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n        }\n    } else {\n        IO_STATUS_BLOCK iosb;\n        NTSTATUS Status;\n        ULONG perms = FILE_WRITE_ATTRIBUTES;\n        btrfs_set_xattr* bsxa;\n\n        if (xattrname == EA_NTACL)\n            perms |= WRITE_DAC | WRITE_OWNER;\n        else if (xattrname == EA_EA)\n            perms |= FILE_WRITE_EA;\n\n        win_handle h = CreateFileW((subvolpath + pathu).c_str(), perms, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                   nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS | FILE_OPEN_REPARSE_POINT, nullptr);\n        if (h == INVALID_HANDLE_VALUE)\n            throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n        size_t bsxalen = offsetof(btrfs_set_xattr, data[0]) + xattrname.length();\n        bsxa = (btrfs_set_xattr*)malloc(bsxalen);\n        if (!bsxa)\n            throw string_error(IDS_OUT_OF_MEMORY);\n\n        bsxa->namelen = (uint16_t)(xattrname.length());\n        bsxa->valuelen = 0;\n        memcpy(bsxa->data, xattrname.c_str(), xattrname.length());\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_XATTR, bsxa, (ULONG)bsxalen, nullptr, 0);\n        if (!NT_SUCCESS(Status)) {\n            free(bsxa);\n            throw string_error(IDS_RECV_SETXATTR_FAILED, Status, format_ntstatus(Status).c_str());\n        }\n\n        free(bsxa);\n    }\n}\n\nvoid BtrfsRecv::cmd_write(btrfs_send_command* cmd, uint8_t* data) {\n    uint64_t* offset;\n    uint8_t* writedata;\n    ULONG offsetlen, datalen;\n    wstring pathu;\n    HANDLE h;\n    LARGE_INTEGER offli;\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n\n    {\n        char* path;\n        ULONG pathlen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        pathu = utf8_to_utf16(string(path, pathlen));\n    }\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_OFFSET, (void**)&offset, &offsetlen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"offset\");\n\n    if (offsetlen < sizeof(uint64_t))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"offset\", offsetlen, sizeof(uint64_t));\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_DATA, (void**)&writedata, &datalen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"data\");\n\n    if (lastwritepath != pathu) {\n        FILE_BASIC_INFO fbi;\n\n        if (lastwriteatt & FILE_ATTRIBUTE_READONLY) {\n            if (!SetFileAttributesW((subvolpath + lastwritepath).c_str(), lastwriteatt))\n                throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n        }\n\n        CloseHandle(lastwritefile);\n\n        lastwriteatt = GetFileAttributesW((subvolpath + pathu).c_str());\n        if (lastwriteatt == INVALID_FILE_ATTRIBUTES)\n            throw string_error(IDS_RECV_GETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n        if (lastwriteatt & FILE_ATTRIBUTE_READONLY) {\n            if (!SetFileAttributesW((subvolpath + pathu).c_str(), lastwriteatt & ~FILE_ATTRIBUTE_READONLY))\n                throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n        }\n\n        h = CreateFileW((subvolpath + pathu).c_str(), FILE_WRITE_DATA | FILE_WRITE_ATTRIBUTES, 0, nullptr, OPEN_EXISTING,\n                        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n        if (h == INVALID_HANDLE_VALUE)\n            throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n        lastwritepath = pathu;\n        lastwritefile = h;\n\n        memset(&fbi, 0, sizeof(FILE_BASIC_INFO));\n\n        fbi.LastWriteTime.QuadPart = -1;\n\n        Status = NtSetInformationFile(h, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation);\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n    } else\n        h = lastwritefile;\n\n    offli.QuadPart = *offset;\n\n    if (SetFilePointer(h, offli.LowPart, &offli.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER)\n        throw string_error(IDS_RECV_SETFILEPOINTER_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n    if (!WriteFile(h, writedata, datalen, nullptr, nullptr))\n        throw string_error(IDS_RECV_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n}\n\nvoid BtrfsRecv::cmd_clone(btrfs_send_command* cmd, uint8_t* data) {\n    uint64_t *offset, *cloneoffset, *clonetransid, *clonelen;\n    BTRFS_UUID* cloneuuid;\n    ULONG i, offsetlen, cloneoffsetlen, cloneuuidlen, clonetransidlen, clonelenlen;\n    wstring pathu, clonepathu, clonepar;\n    btrfs_find_subvol bfs;\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    WCHAR cloneparw[MAX_PATH];\n    DUPLICATE_EXTENTS_DATA ded;\n    LARGE_INTEGER filesize;\n    bool found = false;\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_OFFSET, (void**)&offset, &offsetlen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"offset\");\n\n    if (offsetlen < sizeof(uint64_t))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"offset\", offsetlen, sizeof(uint64_t));\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_LENGTH, (void**)&clonelen, &clonelenlen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"clone_len\");\n\n    if (clonelenlen < sizeof(uint64_t))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"clone_len\", clonelenlen, sizeof(uint64_t));\n\n    {\n        char* path;\n        ULONG pathlen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        pathu = utf8_to_utf16(string(path, pathlen));\n    }\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_UUID, (void**)&cloneuuid, &cloneuuidlen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"clone_uuid\");\n\n    if (cloneuuidlen < sizeof(BTRFS_UUID))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"clone_uuid\", cloneuuidlen, sizeof(BTRFS_UUID));\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_CTRANSID, (void**)&clonetransid, &clonetransidlen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"clone_ctransid\");\n\n    if (clonetransidlen < sizeof(uint64_t))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"clone_ctransid\", clonetransidlen, sizeof(uint64_t));\n\n    {\n        char* clonepath;\n        ULONG clonepathlen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_PATH, (void**)&clonepath, &clonepathlen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"clone_path\");\n\n        clonepathu = utf8_to_utf16(string(clonepath, clonepathlen));\n    }\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_CLONE_OFFSET, (void**)&cloneoffset, &cloneoffsetlen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"clone_offset\");\n\n    if (cloneoffsetlen < sizeof(uint64_t))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"clone_offset\", cloneoffsetlen, sizeof(uint64_t));\n\n    for (i = 0; i < cache.size(); i++) {\n        if (!memcmp(cloneuuid, &cache[i].uuid, sizeof(BTRFS_UUID)) && *clonetransid == cache[i].transid) {\n            clonepar = cache[i].path;\n            found = true;\n            break;\n        }\n    }\n\n    if (!found) {\n        WCHAR volpathw[MAX_PATH];\n\n        bfs.uuid = *cloneuuid;\n        bfs.ctransid = *clonetransid;\n\n        Status = NtFsControlFile(dir, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_FIND_SUBVOL, &bfs, sizeof(btrfs_find_subvol),\n                                 cloneparw, sizeof(cloneparw));\n        if (Status == STATUS_NOT_FOUND)\n            throw string_error(IDS_RECV_CANT_FIND_CLONE_SUBVOL);\n        else if (!NT_SUCCESS(Status))\n            throw string_error(IDS_RECV_FIND_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str());\n\n        if (!GetVolumePathNameW(dirpath.c_str(), volpathw, (sizeof(volpathw) / sizeof(WCHAR)) - 1))\n            throw string_error(IDS_RECV_GETVOLUMEPATHNAME_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n        clonepar = volpathw;\n        if (clonepar.substr(clonepar.length() - 1) == L\"\\\\\")\n            clonepar = clonepar.substr(0, clonepar.length() - 1);\n\n        clonepar += cloneparw;\n        clonepar += L\"\\\\\";\n\n        add_cache_entry(cloneuuid, *clonetransid, clonepar);\n    }\n\n    {\n        win_handle src = CreateFileW((clonepar + clonepathu).c_str(), FILE_READ_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                     nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n        if (src == INVALID_HANDLE_VALUE)\n            throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, (clonepar + clonepathu).c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n        win_handle dest = CreateFileW((subvolpath + pathu).c_str(), FILE_WRITE_DATA, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                      nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n        if (dest == INVALID_HANDLE_VALUE)\n            throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n        if (!GetFileSizeEx(dest, &filesize))\n            throw string_error(IDS_RECV_GETFILESIZEEX_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n        if ((uint64_t)filesize.QuadPart < *offset + *clonelen) {\n            LARGE_INTEGER sizeli;\n\n            sizeli.QuadPart = *offset + *clonelen;\n\n            if (SetFilePointer(dest, sizeli.LowPart, &sizeli.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER)\n                throw string_error(IDS_RECV_SETFILEPOINTER_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n            if (!SetEndOfFile(dest))\n                throw string_error(IDS_RECV_SETENDOFFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n        }\n\n        ded.FileHandle = src;\n        ded.SourceFileOffset.QuadPart = *cloneoffset;\n        ded.TargetFileOffset.QuadPart = *offset;\n        ded.ByteCount.QuadPart = *clonelen;\n\n        Status = NtFsControlFile(dest, nullptr, nullptr, nullptr, &iosb, FSCTL_DUPLICATE_EXTENTS_TO_FILE, &ded, sizeof(DUPLICATE_EXTENTS_DATA),\n                                 nullptr, 0);\n        if (!NT_SUCCESS(Status))\n            throw string_error(IDS_RECV_DUPLICATE_EXTENTS_FAILED, Status, format_ntstatus(Status).c_str());\n    }\n}\n\nvoid BtrfsRecv::cmd_truncate(btrfs_send_command* cmd, uint8_t* data) {\n    uint64_t* size;\n    ULONG sizelen;\n    wstring pathu;\n    LARGE_INTEGER sizeli;\n    DWORD att;\n\n    {\n        char* path;\n        ULONG pathlen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        pathu = utf8_to_utf16(string(path, pathlen));\n    }\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_SIZE, (void**)&size, &sizelen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"size\");\n\n    if (sizelen < sizeof(uint64_t))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"size\", sizelen, sizeof(uint64_t));\n\n    att = GetFileAttributesW((subvolpath + pathu).c_str());\n    if (att == INVALID_FILE_ATTRIBUTES)\n        throw string_error(IDS_RECV_GETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n    if (att & FILE_ATTRIBUTE_READONLY) {\n        if (!SetFileAttributesW((subvolpath + pathu).c_str(), att & ~FILE_ATTRIBUTE_READONLY))\n            throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n    }\n\n    {\n        win_handle h = CreateFileW((subvolpath + pathu).c_str(), FILE_WRITE_DATA, 0, nullptr, OPEN_EXISTING,\n                                   FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n\n        if (h == INVALID_HANDLE_VALUE)\n            throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n        sizeli.QuadPart = *size;\n\n        if (SetFilePointer(h, sizeli.LowPart, &sizeli.HighPart, FILE_BEGIN) == INVALID_SET_FILE_POINTER)\n            throw string_error(IDS_RECV_SETFILEPOINTER_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n        if (!SetEndOfFile(h))\n            throw string_error(IDS_RECV_SETENDOFFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n    }\n\n    if (att & FILE_ATTRIBUTE_READONLY) {\n        if (!SetFileAttributesW((subvolpath + pathu).c_str(), att))\n            throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n    }\n}\n\nvoid BtrfsRecv::cmd_chmod(btrfs_send_command* cmd, uint8_t* data) {\n    win_handle h;\n    uint32_t* mode;\n    ULONG modelen;\n    wstring pathu;\n    btrfs_set_inode_info bsii;\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n\n    {\n        char* path;\n        ULONG pathlen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        pathu = utf8_to_utf16(string(path, pathlen));\n    }\n\n    if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_MODE, (void**)&mode, &modelen))\n        throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"mode\");\n\n    if (modelen < sizeof(uint32_t))\n        throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"mode\", modelen, sizeof(uint32_t));\n\n    h = CreateFileW((subvolpath + pathu).c_str(), WRITE_DAC, 0, nullptr, OPEN_EXISTING,\n                    FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n    if (h == INVALID_HANDLE_VALUE)\n        throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n    memset(&bsii, 0, sizeof(btrfs_set_inode_info));\n\n    bsii.mode_changed = true;\n    bsii.st_mode = *mode;\n\n    Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0);\n    if (!NT_SUCCESS(Status))\n        throw string_error(IDS_RECV_SETINODEINFO_FAILED, Status, format_ntstatus(Status).c_str());\n}\n\nvoid BtrfsRecv::cmd_chown(btrfs_send_command* cmd, uint8_t* data) {\n    win_handle h;\n    uint32_t *uid, *gid;\n    ULONG uidlen, gidlen;\n    wstring pathu;\n    btrfs_set_inode_info bsii;\n\n    {\n        char* path;\n        ULONG pathlen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        pathu = utf8_to_utf16(string(path, pathlen));\n    }\n\n    h = CreateFileW((subvolpath + pathu).c_str(), FILE_WRITE_ATTRIBUTES | WRITE_OWNER | WRITE_DAC, 0, nullptr, OPEN_EXISTING,\n                    FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n    if (h == INVALID_HANDLE_VALUE)\n        throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n    memset(&bsii, 0, sizeof(btrfs_set_inode_info));\n\n    if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_UID, (void**)&uid, &uidlen)) {\n        if (uidlen < sizeof(uint32_t))\n            throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"uid\", uidlen, sizeof(uint32_t));\n\n        bsii.uid_changed = true;\n        bsii.st_uid = *uid;\n    }\n\n    if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_GID, (void**)&gid, &gidlen)) {\n        if (gidlen < sizeof(uint32_t))\n            throw string_error(IDS_RECV_SHORT_PARAM, funcname, L\"gid\", gidlen, sizeof(uint32_t));\n\n        bsii.gid_changed = true;\n        bsii.st_gid = *gid;\n    }\n\n    if (bsii.uid_changed || bsii.gid_changed) {\n        NTSTATUS Status;\n        IO_STATUS_BLOCK iosb;\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SET_INODE_INFO, &bsii, sizeof(btrfs_set_inode_info), nullptr, 0);\n        if (!NT_SUCCESS(Status))\n            throw string_error(IDS_RECV_SETINODEINFO_FAILED, Status, format_ntstatus(Status).c_str());\n    }\n}\n\nstatic __inline uint64_t unix_time_to_win(BTRFS_TIME* t) {\n    return (t->seconds * 10000000) + (t->nanoseconds / 100) + 116444736000000000;\n}\n\nvoid BtrfsRecv::cmd_utimes(btrfs_send_command* cmd, uint8_t* data) {\n    wstring pathu;\n    win_handle h;\n    FILE_BASIC_INFO fbi;\n    BTRFS_TIME* time;\n    ULONG timelen;\n    IO_STATUS_BLOCK iosb;\n    NTSTATUS Status;\n\n    {\n        char* path;\n        ULONG pathlen;\n\n        if (!find_tlv(data, cmd->length, BTRFS_SEND_TLV_PATH, (void**)&path, &pathlen))\n            throw string_error(IDS_RECV_MISSING_PARAM, funcname, L\"path\");\n\n        pathu = utf8_to_utf16(string(path, pathlen));\n    }\n\n    h = CreateFileW((subvolpath + pathu).c_str(), FILE_WRITE_ATTRIBUTES, 0, nullptr, OPEN_EXISTING,\n                    FILE_FLAG_BACKUP_SEMANTICS | FILE_OPEN_REPARSE_POINT | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n    if (h == INVALID_HANDLE_VALUE)\n        throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, pathu.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n    memset(&fbi, 0, sizeof(FILE_BASIC_INFO));\n\n    if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_OTIME, (void**)&time, &timelen) && timelen >= sizeof(BTRFS_TIME))\n        fbi.CreationTime.QuadPart = unix_time_to_win(time);\n\n    if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_ATIME, (void**)&time, &timelen) && timelen >= sizeof(BTRFS_TIME))\n        fbi.LastAccessTime.QuadPart = unix_time_to_win(time);\n\n    if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_MTIME, (void**)&time, &timelen) && timelen >= sizeof(BTRFS_TIME))\n        fbi.LastWriteTime.QuadPart = unix_time_to_win(time);\n\n    if (find_tlv(data, cmd->length, BTRFS_SEND_TLV_CTIME, (void**)&time, &timelen) && timelen >= sizeof(BTRFS_TIME))\n        fbi.ChangeTime.QuadPart = unix_time_to_win(time);\n\n    Status = NtSetInformationFile(h, &iosb, &fbi, sizeof(FILE_BASIC_INFO), FileBasicInformation);\n    if (!NT_SUCCESS(Status))\n        throw ntstatus_error(Status);\n}\n\nstatic void delete_directory(const wstring& dir) {\n    WIN32_FIND_DATAW fff;\n\n    fff_handle h = FindFirstFileW((dir + L\"*\").c_str(), &fff);\n\n    if (h == INVALID_HANDLE_VALUE)\n        return;\n\n    do {\n        wstring file;\n\n        file = fff.cFileName;\n\n        if (file != L\".\" && file != L\"..\") {\n            if (fff.dwFileAttributes & FILE_ATTRIBUTE_READONLY)\n                SetFileAttributesW((dir + file).c_str(), fff.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);\n\n            if (fff.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {\n                if (!(fff.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))\n                    delete_directory(dir + file + L\"\\\\\");\n                else\n                    RemoveDirectoryW((dir + file).c_str());\n            } else\n                DeleteFileW((dir + file).c_str());\n        }\n    } while (FindNextFileW(h, &fff));\n\n    RemoveDirectoryW(dir.c_str());\n}\n\nstatic bool check_csum(btrfs_send_command* cmd, uint8_t* data) {\n    uint32_t crc32 = cmd->csum, calc;\n\n    cmd->csum = 0;\n\n    calc = calc_crc32c(0, (uint8_t*)cmd, sizeof(btrfs_send_command));\n\n    if (cmd->length > 0)\n        calc = calc_crc32c(calc, data, cmd->length);\n\n    return calc == crc32 ? true : false;\n}\n\nvoid BtrfsRecv::do_recv(const win_handle& f, uint64_t* pos, uint64_t size, const win_handle& parent) {\n    try {\n        btrfs_send_header header;\n        bool ended = false;\n\n        if (!ReadFile(f, &header, sizeof(btrfs_send_header), nullptr, nullptr))\n            throw string_error(IDS_RECV_READFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n        *pos += sizeof(btrfs_send_header);\n\n        if (memcmp(header.magic, BTRFS_SEND_MAGIC, sizeof(header.magic)))\n            throw string_error(IDS_RECV_NOT_A_SEND_STREAM);\n\n        if (header.version > 1)\n            throw string_error(IDS_RECV_UNSUPPORTED_VERSION, header.version);\n\n        SendMessageW(GetDlgItem(hwnd, IDC_RECV_PROGRESS), PBM_SETRANGE32, 0, (LPARAM)65536);\n\n        lastwritefile = INVALID_HANDLE_VALUE;\n        lastwritepath = L\"\";\n        lastwriteatt = 0;\n\n        while (true) {\n            btrfs_send_command cmd;\n            uint8_t* data = nullptr;\n            ULONG progress;\n\n            if (cancelling)\n                break;\n\n            progress = (ULONG)((float)*pos * 65536.0f / (float)size);\n            SendMessageW(GetDlgItem(hwnd, IDC_RECV_PROGRESS), PBM_SETPOS, progress, 0);\n\n            if (!ReadFile(f, &cmd, sizeof(btrfs_send_command), nullptr, nullptr)) {\n                if (GetLastError() != ERROR_HANDLE_EOF)\n                    throw string_error(IDS_RECV_READFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n                break;\n            }\n\n            *pos += sizeof(btrfs_send_command);\n\n            if (cmd.length > 0) {\n                if (*pos + cmd.length > size)\n                    throw string_error(IDS_RECV_FILE_TRUNCATED);\n\n                data = (uint8_t*)malloc(cmd.length);\n                if (!data)\n                    throw string_error(IDS_OUT_OF_MEMORY);\n            }\n\n            try {\n                if (data) {\n                    if (!ReadFile(f, data, cmd.length, nullptr, nullptr))\n                        throw string_error(IDS_RECV_READFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n                    *pos += cmd.length;\n                }\n\n                if (!check_csum(&cmd, data))\n                    throw string_error(IDS_RECV_CSUM_ERROR);\n\n                if (cmd.cmd == BTRFS_SEND_CMD_END) {\n                    ended = true;\n                    break;\n                }\n\n                if (lastwritefile != INVALID_HANDLE_VALUE && cmd.cmd != BTRFS_SEND_CMD_WRITE) {\n                    if (lastwriteatt & FILE_ATTRIBUTE_READONLY) {\n                        if (!SetFileAttributesW((subvolpath + lastwritepath).c_str(), lastwriteatt))\n                            throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n                    }\n\n                    CloseHandle(lastwritefile);\n\n                    lastwritefile = INVALID_HANDLE_VALUE;\n                    lastwritepath = L\"\";\n                    lastwriteatt = 0;\n                }\n\n                switch (cmd.cmd) {\n                    case BTRFS_SEND_CMD_SUBVOL:\n                        cmd_subvol(&cmd, data, parent);\n                    break;\n\n                    case BTRFS_SEND_CMD_SNAPSHOT:\n                        cmd_snapshot(&cmd, data, parent);\n                    break;\n\n                    case BTRFS_SEND_CMD_MKFILE:\n                    case BTRFS_SEND_CMD_MKDIR:\n                    case BTRFS_SEND_CMD_MKNOD:\n                    case BTRFS_SEND_CMD_MKFIFO:\n                    case BTRFS_SEND_CMD_MKSOCK:\n                    case BTRFS_SEND_CMD_SYMLINK:\n                        cmd_mkfile(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_RENAME:\n                        cmd_rename(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_LINK:\n                        cmd_link(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_UNLINK:\n                        cmd_unlink(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_RMDIR:\n                        cmd_rmdir(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_SET_XATTR:\n                        cmd_setxattr(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_REMOVE_XATTR:\n                        cmd_removexattr(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_WRITE:\n                        cmd_write(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_CLONE:\n                        cmd_clone(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_TRUNCATE:\n                        cmd_truncate(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_CHMOD:\n                        cmd_chmod(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_CHOWN:\n                        cmd_chown(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_UTIMES:\n                        cmd_utimes(&cmd, data);\n                    break;\n\n                    case BTRFS_SEND_CMD_UPDATE_EXTENT:\n                        // does nothing\n                    break;\n\n                    default:\n                        throw string_error(IDS_RECV_UNKNOWN_COMMAND, cmd.cmd);\n                }\n            } catch (...) {\n                if (data) free(data);\n                throw;\n            }\n\n            if (data) free(data);\n        }\n\n        if (lastwritefile != INVALID_HANDLE_VALUE) {\n            if (lastwriteatt & FILE_ATTRIBUTE_READONLY) {\n                if (!SetFileAttributesW((subvolpath + lastwritepath).c_str(), lastwriteatt))\n                    throw string_error(IDS_RECV_SETFILEATTRIBUTES_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n            }\n\n            CloseHandle(lastwritefile);\n        }\n\n        if (!ended && !cancelling)\n            throw string_error(IDS_RECV_FILE_TRUNCATED);\n\n        if (!cancelling) {\n            NTSTATUS Status;\n            IO_STATUS_BLOCK iosb;\n            btrfs_received_subvol brs;\n\n            brs.generation = stransid;\n            brs.uuid = subvol_uuid;\n\n            Status = NtFsControlFile(dir, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RECEIVED_SUBVOL, &brs, sizeof(btrfs_received_subvol),\n                                    nullptr, 0);\n            if (!NT_SUCCESS(Status))\n                throw string_error(IDS_RECV_RECEIVED_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str());\n        }\n\n        CloseHandle(dir);\n\n        if (master != INVALID_HANDLE_VALUE)\n            CloseHandle(master);\n    } catch (...) {\n        if (subvolpath != L\"\") {\n            ULONG attrib;\n\n            attrib = GetFileAttributesW(subvolpath.c_str());\n            attrib &= ~FILE_ATTRIBUTE_READONLY;\n\n            if (SetFileAttributesW(subvolpath.c_str(), attrib))\n                delete_directory(subvolpath);\n        }\n\n        throw;\n    }\n}\n\n#if defined(_X86_) || defined(_AMD64_)\nstatic void check_cpu() {\n    bool have_sse42 = false;\n\n#ifndef _MSC_VER\n    {\n        uint32_t eax, ebx, ecx, edx;\n\n        __cpuid(1, eax, ebx, ecx, edx);\n\n        if (__get_cpuid(1, &eax, &ebx, &ecx, &edx))\n            have_sse42 = ecx & bit_SSE4_2;\n    }\n#else\n    {\n        int cpu_info[4];\n\n        __cpuid(cpu_info, 1);\n        have_sse42 = (unsigned int)cpu_info[2] & (1 << 20);\n    }\n#endif\n\n    if (have_sse42)\n        calc_crc32c = calc_crc32c_hw;\n}\n#endif\n\nDWORD BtrfsRecv::recv_thread() {\n    LARGE_INTEGER size;\n    uint64_t pos = 0;\n    bool b = true;\n\n    running = true;\n\n    try {\n        win_handle f = CreateFileW(streamfile.c_str(), GENERIC_READ, 0, nullptr, OPEN_EXISTING, 0, nullptr);\n        if (f == INVALID_HANDLE_VALUE)\n            throw string_error(IDS_RECV_CANT_OPEN_FILE, funcname, streamfile.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n        if (!GetFileSizeEx(f, &size))\n            throw string_error(IDS_RECV_GETFILESIZEEX_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n        {\n            win_handle parent = CreateFileW(dirpath.c_str(), FILE_ADD_SUBDIRECTORY | FILE_ADD_FILE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                            nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_POSIX_SEMANTICS, nullptr);\n            if (parent == INVALID_HANDLE_VALUE)\n                throw string_error(IDS_RECV_CANT_OPEN_PATH, dirpath.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n            do {\n                do_recv(f, &pos, size.QuadPart, parent);\n            } while (pos < (uint64_t)size.QuadPart);\n        }\n    } catch (const exception& e) {\n        auto msg = utf8_to_utf16(e.what());\n\n        SetDlgItemTextW(hwnd, IDC_RECV_MSG, msg.c_str());\n\n        SendMessageW(GetDlgItem(hwnd, IDC_RECV_PROGRESS), PBM_SETSTATE, PBST_ERROR, 0);\n\n        b = false;\n    }\n\n    if (b && hwnd) {\n        wstring s;\n\n        SendMessageW(GetDlgItem(hwnd, IDC_RECV_PROGRESS), PBM_SETPOS, 65536, 0);\n\n        if (num_received == 1) {\n            load_string(module, IDS_RECV_SUCCESS, s);\n            SetDlgItemTextW(hwnd, IDC_RECV_MSG, s.c_str());\n        } else {\n            wstring t;\n\n            load_string(module, IDS_RECV_SUCCESS_PLURAL, s);\n\n            wstring_sprintf(t, s, num_received);\n\n            SetDlgItemTextW(hwnd, IDC_RECV_MSG, t.c_str());\n        }\n\n        load_string(module, IDS_RECV_BUTTON_OK, s);\n\n        SetDlgItemTextW(hwnd, IDCANCEL, s.c_str());\n    }\n\n    thread = nullptr;\n    running = false;\n\n    return 0;\n}\n\nstatic DWORD WINAPI global_recv_thread(LPVOID lpParameter) {\n    BtrfsRecv* br = (BtrfsRecv*)lpParameter;\n\n    return br->recv_thread();\n}\n\nINT_PTR CALLBACK BtrfsRecv::RecvProgressDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) {\n    switch (uMsg) {\n        case WM_INITDIALOG:\n            try {\n                this->hwnd = hwndDlg;\n                thread = CreateThread(nullptr, 0, global_recv_thread, this, 0, nullptr);\n\n                if (!thread)\n                    throw string_error(IDS_RECV_CREATETHREAD_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n            } catch (const exception& e) {\n                auto msg = utf8_to_utf16(e.what());\n\n                SetDlgItemTextW(hwnd, IDC_RECV_MSG, msg.c_str());\n\n                SendMessageW(GetDlgItem(hwnd, IDC_RECV_PROGRESS), PBM_SETSTATE, PBST_ERROR, 0);\n            }\n        break;\n\n        case WM_COMMAND:\n            switch (HIWORD(wParam)) {\n                case BN_CLICKED:\n                    switch (LOWORD(wParam)) {\n                        case IDOK:\n                        case IDCANCEL:\n                            if (running) {\n                                wstring s;\n\n                                cancelling = true;\n\n                                if (!load_string(module, IDS_RECV_CANCELLED, s))\n                                    throw last_error(GetLastError());\n\n                                SetDlgItemTextW(hwnd, IDC_RECV_MSG, s.c_str());\n                                SendMessageW(GetDlgItem(hwnd, IDC_RECV_PROGRESS), PBM_SETPOS, 0, 0);\n\n                                if (!load_string(module, IDS_RECV_BUTTON_OK, s))\n                                    throw last_error(GetLastError());\n\n                                SetDlgItemTextW(hwnd, IDCANCEL, s.c_str());\n                            } else\n                                EndDialog(hwndDlg, 1);\n\n                            return true;\n                    }\n                break;\n            }\n        break;\n    }\n\n    return false;\n}\n\nstatic INT_PTR CALLBACK stub_RecvProgressDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    BtrfsRecv* br;\n\n    if (uMsg == WM_INITDIALOG) {\n        SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);\n        br = (BtrfsRecv*)lParam;\n    } else {\n        br = (BtrfsRecv*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n    }\n\n    if (br)\n        return br->RecvProgressDlgProc(hwndDlg, uMsg, wParam, lParam);\n    else\n        return false;\n}\n\nvoid BtrfsRecv::Open(HWND hwnd, const wstring& file, const wstring& path, bool quiet) {\n    streamfile = file;\n    dirpath = path;\n    subvolpath = L\"\";\n\n#if defined(_X86_) || defined(_AMD64_)\n    check_cpu();\n#endif\n\n    if (quiet)\n        recv_thread();\n    else {\n        if (DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_RECV_PROGRESS), hwnd, stub_RecvProgressDlgProc, (LPARAM)this) <= 0)\n            throw last_error(GetLastError());\n    }\n}\n\nextern \"C\" void CALLBACK RecvSubvolGUIW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    try {\n        OPENFILENAMEW ofn;\n        WCHAR file[MAX_PATH];\n        win_handle token;\n        TOKEN_PRIVILEGES* tp;\n        LUID luid;\n        ULONG tplen;\n\n        set_dpi_aware();\n\n        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n            throw last_error(GetLastError());\n\n        tplen = offsetof(TOKEN_PRIVILEGES, Privileges[0]) + (3 * sizeof(LUID_AND_ATTRIBUTES));\n        tp = (TOKEN_PRIVILEGES*)malloc(tplen);\n        if (!tp)\n            throw string_error(IDS_OUT_OF_MEMORY);\n\n        tp->PrivilegeCount = 3;\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Warray-bounds\"\n        if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid)) {\n            free(tp);\n            throw last_error(GetLastError());\n        }\n\n        tp->Privileges[0].Luid = luid;\n        tp->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n        if (!LookupPrivilegeValueW(nullptr, L\"SeSecurityPrivilege\", &luid)) {\n            free(tp);\n            throw last_error(GetLastError());\n        }\n\n        tp->Privileges[1].Luid = luid;\n        tp->Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;\n\n        if (!LookupPrivilegeValueW(nullptr, L\"SeRestorePrivilege\", &luid)) {\n            free(tp);\n            throw last_error(GetLastError());\n        }\n\n        tp->Privileges[2].Luid = luid;\n        tp->Privileges[2].Attributes = SE_PRIVILEGE_ENABLED;\n\n        if (!AdjustTokenPrivileges(token, false, tp, tplen, nullptr, nullptr)) {\n            free(tp);\n            throw last_error(GetLastError());\n        }\n#pragma clang diagnostic pop\n\n        file[0] = 0;\n\n        memset(&ofn, 0, sizeof(OPENFILENAMEW));\n        ofn.lStructSize = sizeof(OPENFILENAMEW);\n        ofn.hwndOwner = hwnd;\n        ofn.hInstance = module;\n        ofn.lpstrFile = file;\n        ofn.nMaxFile = sizeof(file) / sizeof(WCHAR);\n        ofn.Flags = OFN_FILEMUSTEXIST | OFN_HIDEREADONLY;\n\n        if (GetOpenFileNameW(&ofn)) {\n            BtrfsRecv recv;\n\n            recv.Open(hwnd, file, lpszCmdLine, false);\n        }\n\n        free(tp);\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n}\n\nextern \"C\" void CALLBACK RecvSubvolW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    try {\n        vector<wstring> args;\n\n        command_line_to_args(lpszCmdLine, args);\n\n        if (args.size() >= 2) {\n            win_handle token;\n            TOKEN_PRIVILEGES* tp;\n            ULONG tplen;\n            LUID luid;\n\n            if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n                return;\n\n            tplen = offsetof(TOKEN_PRIVILEGES, Privileges[0]) + (3 * sizeof(LUID_AND_ATTRIBUTES));\n            tp = (TOKEN_PRIVILEGES*)malloc(tplen);\n            if (!tp)\n                return;\n\n            tp->PrivilegeCount = 3;\n\n#pragma clang diagnostic push\n#pragma clang diagnostic ignored \"-Warray-bounds\"\n            if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid)) {\n                free(tp);\n                return;\n            }\n\n            tp->Privileges[0].Luid = luid;\n            tp->Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n            if (!LookupPrivilegeValueW(nullptr, L\"SeSecurityPrivilege\", &luid)) {\n                free(tp);\n                return;\n            }\n\n            tp->Privileges[1].Luid = luid;\n            tp->Privileges[1].Attributes = SE_PRIVILEGE_ENABLED;\n\n            if (!LookupPrivilegeValueW(nullptr, L\"SeRestorePrivilege\", &luid)) {\n                free(tp);\n                return;\n            }\n\n            tp->Privileges[2].Luid = luid;\n            tp->Privileges[2].Attributes = SE_PRIVILEGE_ENABLED;\n#pragma clang diagnostic pop\n\n            if (!AdjustTokenPrivileges(token, false, tp, tplen, nullptr, nullptr)) {\n                free(tp);\n                return;\n            }\n\n            free(tp);\n\n            BtrfsRecv br;\n            br.Open(nullptr, args[0], args[1], true);\n        }\n    } catch (const exception& e) {\n        cerr << \"Error: \" << e.what() << endl;\n    }\n}\n"
  },
  {
    "path": "src/shellext/recv.h",
    "content": "/* Copyright (c) Mark Harmstone 2017\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\n#include <shlobj.h>\n#include \"../btrfs.h\"\n\nextern LONG objs_loaded;\n\ntypedef struct {\n    BTRFS_UUID uuid;\n    uint64_t transid;\n    wstring path;\n} subvol_cache;\n\nclass BtrfsRecv {\npublic:\n    BtrfsRecv() {\n        thread = nullptr;\n        master = INVALID_HANDLE_VALUE;\n        dir = INVALID_HANDLE_VALUE;\n        running = false;\n        cancelling = false;\n        stransid = 0;\n        num_received = 0;\n        hwnd = nullptr;\n        cache.clear();\n    }\n\n    virtual ~BtrfsRecv() {\n        cache.clear();\n    }\n\n    void Open(HWND hwnd, const wstring& file, const wstring& path, bool quiet);\n    DWORD recv_thread();\n    INT_PTR CALLBACK RecvProgressDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);\n\nprivate:\n    void cmd_subvol(btrfs_send_command* cmd, uint8_t* data, const win_handle& parent);\n    void cmd_snapshot(btrfs_send_command* cmd, uint8_t* data, const win_handle& parent);\n    void cmd_mkfile(btrfs_send_command* cmd, uint8_t* data);\n    void cmd_rename(btrfs_send_command* cmd, uint8_t* data);\n    void cmd_link(btrfs_send_command* cmd, uint8_t* data);\n    void cmd_unlink(btrfs_send_command* cmd, uint8_t* data);\n    void cmd_rmdir(btrfs_send_command* cmd, uint8_t* data);\n    void cmd_setxattr(btrfs_send_command* cmd, uint8_t* data);\n    void cmd_removexattr(btrfs_send_command* cmd, uint8_t* data);\n    void cmd_write(btrfs_send_command* cmd, uint8_t* data);\n    void cmd_clone(btrfs_send_command* cmd, uint8_t* data);\n    void cmd_truncate(btrfs_send_command* cmd, uint8_t* data);\n    void cmd_chmod(btrfs_send_command* cmd, uint8_t* data);\n    void cmd_chown(btrfs_send_command* cmd, uint8_t* data);\n    void cmd_utimes(btrfs_send_command* cmd, uint8_t* data);\n    void add_cache_entry(BTRFS_UUID* uuid, uint64_t transid, const wstring& path);\n    bool find_tlv(uint8_t* data, ULONG datalen, uint16_t type, void** value, ULONG* len);\n    void do_recv(const win_handle& f, uint64_t* pos, uint64_t size, const win_handle& parent);\n\n    HANDLE dir, master, thread, lastwritefile;\n    HWND hwnd;\n    wstring streamfile, dirpath, subvolpath, lastwritepath;\n    DWORD lastwriteatt;\n    ULONG num_received;\n    uint64_t stransid;\n    BTRFS_UUID subvol_uuid;\n    bool running, cancelling;\n    vector<subvol_cache> cache;\n};\n"
  },
  {
    "path": "src/shellext/resource.h",
    "content": "//{{NO_DEPENDENCIES}}\r\n// Microsoft Visual C++ generated include file.\r\n// Used by shellbtrfs.rc\r\n//\r\n#define IDI_ICON1                       101\r\n#define IDS_NEW_SUBVOL_HELP_TEXT        102\r\n#define IDS_NEW_SUBVOL                  103\r\n#define IDD_SIZE_DETAILS                103\r\n#define IDS_NEW_SUBVOL_FILENAME         104\r\n#define IDS_CREATE_SNAPSHOT             105\r\n#define IDD_VOL_PROP_SHEET              105\r\n#define IDS_CREATE_SNAPSHOT_HELP_TEXT   106\r\n#define IDD_VOL_USAGE                   106\r\n#define IDS_SNAPSHOT_FILENAME           107\r\n#define IDD_PROP_SHEET                  107\r\n#define IDS_PROP_SHEET_TITLE            108\r\n#define IDD_BALANCE_OPTIONS             108\r\n#define IDS_INODE_FILE                  109\r\n#define IDD_BALANCE                     109\r\n#define IDS_INODE_DIR                   110\r\n#define IDD_DEVICES                     110\r\n#define IDS_INODE_CHAR                  111\r\n#define IDS_INODE_BLOCK                 112\r\n#define IDS_INODE_FIFO                  113\r\n#define IDS_INODE_SOCKET                114\r\n#define IDS_INODE_SYMLINK               115\r\n#define IDS_INODE_UNKNOWN               116\r\n#define IDS_SET_INODE_INFO_ERROR        117\r\n#define IDS_CANNOT_FIND_DEVICE          117\r\n#define IDS_SIZE_BYTE                   118\r\n#define IDS_SIZE_BYTES                  119\r\n#define IDS_SIZE_KB                     120\r\n#define IDS_SIZE_MB                     121\r\n#define IDS_SIZE_GB                     122\r\n#define IDS_SIZE_TB                     123\r\n#define IDS_SIZE_PB                     124\r\n#define IDS_SIZE_EB                     125\r\n#define IDS_VARIOUS                     126\r\n#define IDS_INODE_CHAR_SIMPLE           127\r\n#define IDS_INODE_BLOCK_SIMPLE          128\r\n#define IDS_VOL_PROP_SHEET_TITLE        129\r\n#define IDS_SIZE_LARGE                  130\r\n#define IDS_SINGLE                      131\r\n#define IDS_DUP                         132\r\n#define IDS_RAID0                       133\r\n#define IDS_RAID1                       134\r\n#define IDS_RAID10                      135\r\n#define IDS_RAID5                       136\r\n#define IDS_RAID6                       137\r\n#define IDS_USAGE_DATA                  138\r\n#define IDS_USAGE_MIXED                 139\r\n#define IDS_USAGE_METADATA              140\r\n#define IDS_USAGE_SYSTEM                141\r\n#define IDS_USAGE_UNALLOC               142\r\n#define IDS_UNKNOWN_DEVICE              143\r\n#define IDS_USAGE_DEV_SIZE              144\r\n#define IDS_USAGE_DEV_ALLOC             145\r\n#define IDS_USAGE_DEV_UNALLOC           146\r\n#define IDS_USAGE_DATA_RATIO            147\r\n#define IDS_USAGE_METADATA_RATIO        148\r\n#define IDS_NO_BALANCE                  149\r\n#define IDS_SINGLE2                     150\r\n#define IDS_DEVID_LIST                  151\r\n#define IDS_BALANCE_RUNNING             152\r\n#define IDS_DRANGE_END_BEFORE_START     153\r\n#define IDS_VRANGE_END_BEFORE_START     154\r\n#define IDS_LIMIT_END_BEFORE_START      155\r\n#define IDS_STRIPES_END_BEFORE_START    156\r\n#define IDS_USAGE_END_BEFORE_START      157\r\n#define IDS_ERROR                       158\r\n#define IDS_BALANCE_COMPLETE            159\r\n#define IDS_BALANCE_PAUSED              160\r\n#define IDS_BALANCE_CANCELLED           161\r\n#define IDS_DEVLIST_ID                  162\r\n#define IDS_DEVLIST_DESC                163\r\n#define IDS_DEVLIST_READONLY            164\r\n#define IDS_DEVLIST_SIZE                165\r\n#define IDS_DEVLIST_READONLY_YES        166\r\n#define IDS_DEVLIST_READONLY_NO         167\r\n#define IDD_DEVICE_ADD                  167\r\n#define IDS_DEVLIST_ALLOC               168\r\n#define IDD_SCRUB                       168\r\n#define IDS_DEVLIST_ALLOC_PC            169\r\n#define IDD_DEVICE_STATS                169\r\n#define IDS_BALANCE_RUNNING_REMOVAL     170\r\n#define IDD_RECV_PROGRESS               170\r\n#define IDS_BALANCE_PAUSED_REMOVAL      171\r\n#define IDD_SEND_SUBVOL                 171\r\n#define IDS_BALANCE_CANCELLED_REMOVAL   172\r\n#define IDD_RESIZE                      172\r\n#define IDS_BALANCE_COMPLETE_REMOVAL    173\r\n#define IDS_PARTITION                   174\r\n#define IDS_WHOLE_DISK                  175\r\n#define IDS_CANNOT_REMOVE_RAID          176\r\n#define IDD_DRIVE_LETTER                176\r\n#define IDS_REMOVE_DEVICE_CONFIRMATION  177\r\n#define IDS_CONFIRMATION_TITLE          178\r\n#define IDS_ADD_DEVICE_CONFIRMATION     179\r\n#define IDS_ADD_DEVICE_CONFIRMATION_FS  180\r\n#define IDS_BALANCE_FAILED              181\r\n#define IDS_BALANCE_FAILED_REMOVAL      182\r\n#define IDS_DISK_NUM                    183\r\n#define IDS_DISK_PART_NUM               184\r\n#define IDS_NO_SCRUB                    185\r\n#define IDS_SCRUB_RUNNING               186\r\n#define IDS_SCRUB_FINISHED              187\r\n#define IDS_SCRUB_PAUSED                188\r\n#define IDS_SCRUB_MSG_STARTED           189\r\n#define IDS_SCRUB_MSG_RECOVERABLE_DATA  190\r\n#define IDS_SCRUB_MSG_RECOVERABLE_METADATA 191\r\n#define IDS_SCRUB_MSG_UNRECOVERABLE_DATA 192\r\n#define IDS_SCRUB_MSG_UNRECOVERABLE_DATA_SUBVOL 193\r\n#define IDS_SCRUB_MSG_UNRECOVERABLE_METADATA 194\r\n#define IDS_SCRUB_MSG_UNRECOVERABLE_METADATA_FIRSTITEM 195\r\n#define IDS_SCRUB_MSG_FINISHED          196\r\n#define IDS_SCRUB_MSG_SUMMARY           197\r\n#define IDS_BALANCE_SCRUB_RUNNING       198\r\n#define IDS_SCRUB_BALANCE_RUNNING       199\r\n#define IDS_SCRUB_MSG_SUMMARY_ERRORS_RECOVERABLE 200\r\n#define IDS_SCRUB_MSG_SUMMARY_ERRORS_UNRECOVERABLE 201\r\n#define IDS_SCRUB_FAILED                202\r\n#define IDS_LOCK_FAILED                 203\r\n#define IDS_SCRUB_MSG_RECOVERABLE_PARITY 204\r\n#define IDS_COMPRESS_ANY                205\r\n#define IDS_COMPRESS_ZLIB               206\r\n#define IDS_COMPRESS_LZO                207\r\n#define IDS_STANDALONE_PROPSHEET_TITLE  208\r\n#define IDS_REFLINK_PASTE               209\r\n#define IDS_REFLINK_PASTE_HELP          210\r\n#define IDS_RECV_SUBVOL                 211\r\n#define IDS_RECV_SUBVOL_HELP            212\r\n#define IDS_RECV_CANT_OPEN_FILE         213\r\n#define IDS_RECV_READFILE_FAILED        214\r\n#define IDS_OUT_OF_MEMORY               215\r\n#define IDS_RECV_UNKNOWN_COMMAND        216\r\n#define IDS_RECV_CANT_OPEN_PATH         217\r\n#define IDS_RAID1C3                     218\r\n#define IDS_RECV_CREATE_SUBVOL_FAILED   219\r\n#define IDS_RECV_MISSING_PARAM          220\r\n#define IDS_RECV_SHORT_PARAM            221\r\n#define IDS_RECV_MKNOD_FAILED           222\r\n#define IDS_RECV_SET_REPARSE_POINT_FAILED 223\r\n#define IDS_RECV_MOVEFILE_FAILED        224\r\n#define IDS_RECV_SETFILEPOINTER_FAILED  225\r\n#define IDS_RECV_WRITEFILE_FAILED       226\r\n#define IDS_RECV_CREATEHARDLINK_FAILED  227\r\n#define IDS_RECV_SETENDOFFILE_FAILED    228\r\n#define IDS_RECV_CANT_CREATE_FILE       229\r\n#define IDS_RAID1C4                     230\r\n#define IDS_RECV_SETINODEINFO_FAILED    231\r\n#define IDS_RECV_SUCCESS                232\r\n#define IDS_RECV_BUTTON_OK              233\r\n#define IDS_RECV_SETFILEATTRIBUTES_FAILED 234\r\n#define IDS_RECV_GETFILEATTRIBUTES_FAILED 235\r\n#define IDS_RECV_CSUM_ERROR             236\r\n#define IDS_RECV_NOT_A_SEND_STREAM      237\r\n#define IDS_RECV_UNSUPPORTED_VERSION    238\r\n#define IDS_RECV_SETEAFILE_FAILED       239\r\n#define IDS_RECV_RECEIVED_SUBVOL_FAILED 240\r\n#define IDS_RECV_SETSECURITYOBJECT_FAILED 241\r\n#define IDS_RECV_SETXATTR_FAILED        242\r\n#define IDS_RECV_CREATETHREAD_FAILED    243\r\n#define IDS_RECV_FILE_TRUNCATED         244\r\n#define IDS_RECV_RESERVE_SUBVOL_FAILED  245\r\n#define IDS_RECV_CANCELLED              246\r\n#define IDS_RECV_CANT_FIND_PARENT_SUBVOL 247\r\n#define IDS_RECV_FIND_SUBVOL_FAILED     248\r\n#define IDS_RECV_CREATE_SNAPSHOT_FAILED 249\r\n#define IDS_RECV_GETVOLUMEPATHNAME_FAILED 250\r\n#define IDS_RECV_DELETEFILE_FAILED      251\r\n#define IDS_RECV_REMOVEDIRECTORY_FAILED 252\r\n#define IDS_RECV_CANT_FIND_CLONE_SUBVOL 253\r\n#define IDS_RECV_GETFILESIZEEX_FAILED   254\r\n#define IDS_RECV_DUPLICATE_EXTENTS_FAILED 255\r\n#define IDS_RECV_SUCCESS_PLURAL         256\r\n#define IDS_SEND_SUBVOL                 257\r\n#define IDS_SEND_SUBVOL_HELP            258\r\n#define IDS_SEND_CANT_OPEN_FILE         259\r\n#define IDS_SEND_CANT_OPEN_DIR          260\r\n#define IDS_SEND_FSCTL_BTRFS_SEND_SUBVOL_FAILED 261\r\n#define IDS_SEND_FSCTL_BTRFS_READ_SEND_BUFFER_FAILED 262\r\n#define IDS_SEND_SUCCESS                263\r\n#define IDS_SEND_WRITEFILE_FAILED       264\r\n#define IDS_SEND_GET_FILE_INFO_FAILED   265\r\n#define IDS_SEND_NOT_READONLY           266\r\n#define IDS_NOT_SUBVOL                  267\r\n#define IDS_GET_FILE_IDS_FAILED         268\r\n#define IDS_SHPARSEDISPLAYNAME_FAILED   269\r\n#define IDS_SHGETPATHFROMIDLIST_FAILED  270\r\n#define IDS_SEND_PARENT_NOT_READONLY    271\r\n#define IDS_SEND_CANCEL                 272\r\n#define IDS_SEND_WRITING                273\r\n#define IDS_MISSING                     274\r\n#define IDS_RESIZE_SUCCESSFUL           275\r\n#define IDS_BALANCE_RUNNING_SHRINK      276\r\n#define IDS_BALANCE_PAUSED_SHRINK       277\r\n#define IDS_BALANCE_CANCELLED_SHRINK    278\r\n#define IDS_BALANCE_COMPLETE_SHRINK     279\r\n#define IDS_BALANCE_FAILED_SHRINK       280\r\n#define IDS_COMPRESS_ZSTD               281\r\n#define IDS_REGCREATEKEY_FAILED         283\r\n#define IDS_REGSETVALUEEX_FAILED        284\r\n#define IDS_REGCLOSEKEY_FAILED          285\r\n#define IDS_CANT_REFLINK_DIFFERENT_FS   287\r\n#define IDS_INITCOMMONCONTROLSEX_FAILED 288\r\n#define IDS_CANT_OPEN_MOUNTMGR          289\r\n#define IDS_TVM_INSERTITEM_FAILED       290\r\n#define IDS_RECV_PATH_TOO_LONG          291\r\n#define IDD_MAPPINGS                    292\r\n#define IDS_MAPPINGS_UID_MAPPINGS       293\r\n#define IDS_MAPPINGS_GID_MAPPINGS       294\r\n#define IDS_MAPPINGS_PRINCIPAL          295\r\n#define IDS_MAPPINGS_UID                296\r\n#define IDS_MAPPINGS_GID                297\r\n#define IDC_UID                         1001\r\n#define IDC_GID                         1002\r\n#define IDC_USERR                       1003\r\n#define IDC_GROUPR                      1004\r\n#define IDC_OTHERR                      1005\r\n#define IDC_USERW                       1006\r\n#define IDC_GROUPW                      1007\r\n#define IDC_OTHERW                      1008\r\n#define IDC_USERX                       1009\r\n#define IDC_GROUPX                      1010\r\n#define IDC_OTHERX                      1011\r\n#define IDC_NODATACOW                   1012\r\n#define IDC_SUBVOL                      1013\r\n#define IDC_INODE                       1014\r\n#define IDC_TYPE                        1015\r\n#define IDC_COMPRESS                    1016\r\n#define IDC_SIZE_ON_DISK                1017\r\n#define IDC_GROUP_INFORMATION           1018\r\n#define IDC_SIZE_INLINE                 1019\r\n#define IDC_SETUID                      1019\r\n#define IDC_SIZE_UNCOMPRESSED           1020\r\n#define IDC_USAGE_BOX                   1020\r\n#define IDC_SETGID                      1020\r\n#define IDC_SIZE_ZLIB                   1021\r\n#define IDC_USAGE_REFRESH               1021\r\n#define IDC_STICKY                      1021\r\n#define IDC_SIZE_ZLIB2                  1022\r\n#define IDC_SIZE_LZO                    1022\r\n#define IDC_VOL_SHOW_USAGE              1022\r\n#define IDC_VOL_BALANCE                 1023\r\n#define IDC_SIZE_ZSTD                   1023\r\n#define IDC_COMPRESSION_RATIO           1023\r\n#define IDC_PROFILES                    1024\r\n#define IDC_FRAGMENTATION               1024\r\n#define IDC_PROFILES_SINGLE             1025\r\n#define IDC_PROFILES_DUP                1026\r\n#define IDC_VOL_DEVICES                 1026\r\n#define IDC_PROFILES_RAID0              1027\r\n#define IDC_VOL_DEVICES2                1027\r\n#define IDC_VOL_SCRUB                   1027\r\n#define IDC_PROFILES_RAID1              1028\r\n#define IDC_PROFILES_RAID10             1029\r\n#define IDC_PROFILES_RAID5              1030\r\n#define IDC_PROFILES_RAID6              1031\r\n#define IDC_USAGE                       1032\r\n#define IDC_USAGE_START_SPINNER         1033\r\n#define IDC_USAGE_START                 1034\r\n#define IDC_USAGE_END_SPINNER           1035\r\n#define IDC_USAGE_END                   1036\r\n#define IDC_DEVID                       1037\r\n#define IDC_DEVID_COMBO                 1038\r\n#define IDC_DRANGE_START                1039\r\n#define IDC_DRANGE                      1040\r\n#define IDC_PROGRESS1                   1040\r\n#define IDC_BALANCE_PROGRESS            1040\r\n#define IDC_RECV_PROGRESS               1040\r\n#define IDC_DRANGE_END                  1041\r\n#define IDC_DATA                        1041\r\n#define IDC_LIMIT                       1042\r\n#define IDC_METADATA                    1042\r\n#define IDC_VRANGE                      1043\r\n#define IDC_SYSTEM                      1043\r\n#define IDC_LIMIT_START_SPINNER         1044\r\n#define IDC_BUTTON1                     1044\r\n#define IDC_DATA_OPTIONS                1044\r\n#define IDC_DEVICE_ADD                  1044\r\n#define IDC_RESET_STATS                 1044\r\n#define IDC_OPEN_ADMIN                  1044\r\n#define IDC_BROWSE                      1044\r\n#define IDC_LIMIT_START                 1045\r\n#define IDC_BUTTON2                     1045\r\n#define IDC_METADATA_OPTIONS            1045\r\n#define IDC_DEVICE_REFRESH              1045\r\n#define IDC_PARENT_BROWSE               1045\r\n#define IDC_VRANGE_END                  1046\r\n#define IDC_BUTTON3                     1046\r\n#define IDC_SYSTEM_OPTIONS              1046\r\n#define IDC_DEVICE_REMOVE               1046\r\n#define IDC_CLONE_ADD                   1046\r\n#define IDC_VRANGE_START                1047\r\n#define IDC_BALANCE_STATUS              1047\r\n#define IDC_DEVICE_SHOW_STATS           1047\r\n#define IDC_CLONE_REMOVE                1047\r\n#define IDC_LIMIT_END_SPINNER           1048\r\n#define IDC_START_BALANCE               1048\r\n#define IDC_DEVICE_RESIZE               1048\r\n#define IDC_LIMIT_END                   1049\r\n#define IDC_PAUSE_BALANCE               1049\r\n#define IDC_STRIPES                     1050\r\n#define IDC_BUTTON6                     1050\r\n#define IDC_CANCEL_BALANCE              1050\r\n#define IDC_STRIPES_START_SPINNER       1051\r\n#define IDC_STRIPES_START               1052\r\n#define IDC_DEVLIST                     1052\r\n#define IDC_STRIPES_END_SPINNER         1053\r\n#define IDC_DEVICE_TREE                 1053\r\n#define IDC_STRIPES_END                 1054\r\n#define IDC_UUID                        1054\r\n#define IDC_CONVERT                     1055\r\n#define IDC_SCRUB_INFO                  1055\r\n#define IDC_CONVERT_COMBO               1056\r\n#define IDC_START_SCRUB                 1056\r\n#define IDC_SOFT                        1057\r\n#define IDC_PAUSE_SCRUB                 1057\r\n#define IDC_CANCEL_SCRUB                1058\r\n#define IDC_PROFILES_RAID1C3            1058\r\n#define IDC_SCRUB_PROGRESS              1059\r\n#define IDC_PROFILES_RAID1C4            1059\r\n#define IDC_SCRUB_STATUS                1060\r\n#define IDC_COMPRESS_TYPE               1061\r\n#define IDC_CHECK1                      1062\r\n#define IDC_SUBVOL_RO                   1062\r\n#define IDC_INCREMENTAL                 1062\r\n#define IDC_DEVICE_ID                   1063\r\n#define IDC_WRITE_ERRS                  1064\r\n#define IDC_READ_ERRS                   1065\r\n#define IDC_RECV_MSG                    1065\r\n#define IDC_FLUSH_ERRS                  1066\r\n#define IDC_STREAM_DEST                 1066\r\n#define IDC_CORRUPTION_ERRS             1067\r\n#define IDC_SEND_STATUS                 1067\r\n#define IDC_GENERATION_ERRS             1068\r\n#define IDC_PARENT_SUBVOL               1068\r\n#define IDC_CLONE_LIST                  1069\r\n#define IDC_RESIZE_DEVICE_ID            1070\r\n#define IDC_RESIZE_CURSIZE              1071\r\n#define IDC_RESIZE_SLIDER               1072\r\n#define IDC_RESIZE_NEWSIZE              1073\r\n#define IDC_VOL_CHANGE_DRIVE_LETTER     1073\r\n#define IDC_DRIVE_LETTER_COMBO          1074\r\n#define IDC_MAPPINGS_TAB                1075\r\n#define IDC_MAPPINGS_LIST               1076\r\n\r\n// Next default values for new objects\r\n//\r\n#ifdef APSTUDIO_INVOKED\r\n#ifndef APSTUDIO_READONLY_SYMBOLS\r\n#define _APS_NEXT_RESOURCE_VALUE        179\r\n#define _APS_NEXT_COMMAND_VALUE         40001\r\n#define _APS_NEXT_CONTROL_VALUE         1075\r\n#define _APS_NEXT_SYMED_VALUE           101\r\n#endif\r\n#endif\r\n"
  },
  {
    "path": "src/shellext/scrub.cpp",
    "content": "/* Copyright (c) Mark Harmstone 2017\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"shellext.h\"\n#include \"scrub.h\"\n#include \"resource.h\"\n#include \"../btrfsioctl.h\"\n#include <shlobj.h>\n#include <uxtheme.h>\n#include <stdio.h>\n#include <strsafe.h>\n#include <winternl.h>\n\n#define NO_SHLWAPI_STRFCNS\n#include <shlwapi.h>\n#include <uxtheme.h>\n\nstatic wstring format_duration(uint64_t dur, const wchar_t* fmt) {\n    int len = GetDurationFormatEx(LOCALE_NAME_USER_DEFAULT, 0, nullptr, dur,\n                                  fmt, nullptr, 0);\n    if (len == 0)\n        throw last_error(GetLastError());\n\n    wstring s;\n\n    s.resize(len);\n\n    if (GetDurationFormatEx(LOCALE_NAME_USER_DEFAULT, 0, nullptr, dur,\n                            fmt, s.data(), s.size()) == 0)\n        throw last_error(GetLastError());\n\n    return s;\n}\n\nvoid BtrfsScrub::UpdateTextBox(HWND hwndDlg, btrfs_query_scrub* bqs) {\n    btrfs_query_scrub* bqs2 = nullptr;\n    bool alloc_bqs2 = false;\n    NTSTATUS Status;\n    wstring s, t, u;\n    WCHAR dt[255], tm[255];\n    FILETIME filetime;\n    SYSTEMTIME systime;\n    uint64_t recoverable_errors = 0, unrecoverable_errors = 0;\n\n    try {\n        if (bqs->num_errors > 0) {\n            win_handle h;\n            IO_STATUS_BLOCK iosb;\n            ULONG len;\n\n            h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                            OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n            if (h == INVALID_HANDLE_VALUE)\n                throw last_error(GetLastError());\n\n            len = 0;\n\n            try {\n                do {\n                    len += 1024;\n\n                    if (bqs2) {\n                        free(bqs2);\n                        bqs2 = nullptr;\n                    }\n\n                    bqs2 = (btrfs_query_scrub*)malloc(len);\n\n                    Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_SCRUB, nullptr, 0, bqs2, len);\n\n                    if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)\n                        throw ntstatus_error(Status);\n                } while (Status == STATUS_BUFFER_OVERFLOW);\n            } catch (...) {\n                if (bqs2)\n                    free(bqs2);\n\n                throw;\n            }\n\n            alloc_bqs2 = true;\n        } else\n            bqs2 = bqs;\n\n        // \"scrub started\"\n        if (bqs2->start_time.QuadPart > 0) {\n            filetime.dwLowDateTime = bqs2->start_time.LowPart;\n            filetime.dwHighDateTime = bqs2->start_time.HighPart;\n\n            if (!FileTimeToSystemTime(&filetime, &systime))\n                throw last_error(GetLastError());\n\n            if (!SystemTimeToTzSpecificLocalTime(nullptr, &systime, &systime))\n                throw last_error(GetLastError());\n\n            if (!load_string(module, IDS_SCRUB_MSG_STARTED, t))\n                throw last_error(GetLastError());\n\n            if (!GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime, nullptr, dt, sizeof(dt) / sizeof(WCHAR)))\n                throw last_error(GetLastError());\n\n            if (!GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &systime, nullptr, tm, sizeof(tm) / sizeof(WCHAR)))\n                throw last_error(GetLastError());\n\n            wstring_sprintf(u, t, dt, tm);\n\n            s += u;\n            s += L\"\\r\\n\";\n        }\n\n        // errors\n        if (bqs2->num_errors > 0) {\n            btrfs_scrub_error* bse = &bqs2->errors;\n\n            do {\n                if (bse->recovered)\n                    recoverable_errors++;\n                else\n                    unrecoverable_errors++;\n\n                if (bse->parity) {\n                    if (!load_string(module, IDS_SCRUB_MSG_RECOVERABLE_PARITY, t))\n                        throw last_error(GetLastError());\n\n                    wstring_sprintf(u, t, bse->address, bse->device);\n                } else if (bse->is_metadata) {\n                    int message;\n\n                    if (bse->recovered)\n                        message = IDS_SCRUB_MSG_RECOVERABLE_METADATA;\n                    else if (bse->metadata.firstitem.obj_id == 0 && bse->metadata.firstitem.obj_type == 0 && bse->metadata.firstitem.offset == 0)\n                        message = IDS_SCRUB_MSG_UNRECOVERABLE_METADATA;\n                    else\n                        message = IDS_SCRUB_MSG_UNRECOVERABLE_METADATA_FIRSTITEM;\n\n                    if (!load_string(module, message, t))\n                        throw last_error(GetLastError());\n\n                    if (bse->recovered)\n                        wstring_sprintf(u, t, bse->address, bse->device);\n                    else if (bse->metadata.firstitem.obj_id == 0 && bse->metadata.firstitem.obj_type == 0 && bse->metadata.firstitem.offset == 0)\n                        wstring_sprintf(u, t, bse->address, bse->device, bse->metadata.root, bse->metadata.level);\n                    else\n                        wstring_sprintf(u, t, bse->address, bse->device, bse->metadata.root, bse->metadata.level, bse->metadata.firstitem.obj_id,\n                                        bse->metadata.firstitem.obj_type, bse->metadata.firstitem.offset);\n                } else {\n                    int message;\n\n                    if (bse->recovered)\n                        message = IDS_SCRUB_MSG_RECOVERABLE_DATA;\n                    else if (bse->data.subvol != 0)\n                        message = IDS_SCRUB_MSG_UNRECOVERABLE_DATA_SUBVOL;\n                    else\n                        message = IDS_SCRUB_MSG_UNRECOVERABLE_DATA;\n\n                    if (!load_string(module, message, t))\n                        throw last_error(GetLastError());\n\n                    if (bse->recovered)\n                        wstring_sprintf(u, t, bse->address, bse->device);\n                    else if (bse->data.subvol != 0)\n                        wstring_sprintf(u, t, bse->address, bse->device, bse->data.subvol,\n                            bse->data.filename_length / sizeof(WCHAR), bse->data.filename, bse->data.offset);\n                    else\n                        wstring_sprintf(u, t, bse->address, bse->device, bse->data.filename_length / sizeof(WCHAR),\n                            bse->data.filename, bse->data.offset);\n                }\n\n                s += u;\n                s += L\"\\r\\n\";\n\n                if (bse->next_entry == 0)\n                    break;\n                else\n                    bse = (btrfs_scrub_error*)((uint8_t*)bse + bse->next_entry);\n            } while (true);\n        }\n\n        if (bqs2->finish_time.QuadPart > 0) {\n            wstring d1, d2;\n            float speed;\n\n            // \"scrub finished\"\n\n            filetime.dwLowDateTime = bqs2->finish_time.LowPart;\n            filetime.dwHighDateTime = bqs2->finish_time.HighPart;\n\n            if (!FileTimeToSystemTime(&filetime, &systime))\n                throw last_error(GetLastError());\n\n            if (!SystemTimeToTzSpecificLocalTime(nullptr, &systime, &systime))\n                throw last_error(GetLastError());\n\n            if (!load_string(module, IDS_SCRUB_MSG_FINISHED, t))\n                throw last_error(GetLastError());\n\n            if (!GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, &systime, nullptr, dt, sizeof(dt) / sizeof(WCHAR)))\n                throw last_error(GetLastError());\n\n            if (!GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &systime, nullptr, tm, sizeof(tm) / sizeof(WCHAR)))\n                throw last_error(GetLastError());\n\n            wstring_sprintf(u, t, dt, tm);\n\n            s += u;\n            s += L\"\\r\\n\";\n\n            // summary\n\n            if (!load_string(module, IDS_SCRUB_MSG_SUMMARY, t))\n                throw last_error(GetLastError());\n\n            format_size(bqs2->data_scrubbed, d1, false);\n\n            speed = (float)bqs2->data_scrubbed / ((float)bqs2->duration / 10000000.0f);\n\n            format_size((uint64_t)speed, d2, false);\n\n            wstring dur;\n\n            if (bqs2->duration >= 36000000000)\n                dur = format_duration(bqs2->duration, L\"h:mm:ss\");\n            else\n                dur = format_duration(bqs2->duration, L\"m:ss\");\n\n            wstring_sprintf(u, t, d1.c_str(), dur.c_str(), d2.c_str());\n\n            s += u;\n            s += L\"\\r\\n\";\n\n            // recoverable errors\n\n            if (!load_string(module, IDS_SCRUB_MSG_SUMMARY_ERRORS_RECOVERABLE, t))\n                throw last_error(GetLastError());\n\n            wstring_sprintf(u, t, recoverable_errors);\n\n            s += u;\n            s += L\"\\r\\n\";\n\n            // unrecoverable errors\n\n            if (!load_string(module, IDS_SCRUB_MSG_SUMMARY_ERRORS_UNRECOVERABLE, t))\n                throw last_error(GetLastError());\n\n            wstring_sprintf(u, t, unrecoverable_errors);\n\n            s += u;\n            s += L\"\\r\\n\";\n        }\n\n        SetWindowTextW(GetDlgItem(hwndDlg, IDC_SCRUB_INFO), s.c_str());\n    } catch (...) {\n        if (alloc_bqs2)\n            free(bqs2);\n\n        throw;\n    }\n\n    if (alloc_bqs2)\n        free(bqs2);\n}\n\nvoid BtrfsScrub::RefreshScrubDlg(HWND hwndDlg, bool first_time) {\n    btrfs_query_scrub bqs;\n\n    {\n        win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                   OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n        if (h != INVALID_HANDLE_VALUE) {\n            NTSTATUS Status;\n            IO_STATUS_BLOCK iosb;\n\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_SCRUB, nullptr, 0, &bqs, sizeof(btrfs_query_scrub));\n\n            if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)\n                throw ntstatus_error(Status);\n        } else\n            throw last_error(GetLastError());\n    }\n\n    if (first_time || status != bqs.status || chunks_left != bqs.chunks_left) {\n        wstring s;\n\n        if (bqs.status == BTRFS_SCRUB_STOPPED) {\n            EnableWindow(GetDlgItem(hwndDlg, IDC_START_SCRUB), true);\n            EnableWindow(GetDlgItem(hwndDlg, IDC_PAUSE_SCRUB), false);\n            EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_SCRUB), false);\n\n            if (bqs.error != STATUS_SUCCESS) {\n                wstring t;\n\n                if (!load_string(module, IDS_SCRUB_FAILED, t))\n                    throw last_error(GetLastError());\n\n                wstring_sprintf(s, t, bqs.error);\n            } else {\n                if (!load_string(module, bqs.total_chunks == 0 ? IDS_NO_SCRUB : IDS_SCRUB_FINISHED, s))\n                    throw last_error(GetLastError());\n            }\n        } else {\n            wstring t;\n            float pc;\n\n            EnableWindow(GetDlgItem(hwndDlg, IDC_START_SCRUB), false);\n            EnableWindow(GetDlgItem(hwndDlg, IDC_PAUSE_SCRUB), true);\n            EnableWindow(GetDlgItem(hwndDlg, IDC_CANCEL_SCRUB), true);\n\n            if (!load_string(module, bqs.status == BTRFS_SCRUB_PAUSED ? IDS_SCRUB_PAUSED : IDS_SCRUB_RUNNING, t))\n                throw last_error(GetLastError());\n\n            pc = ((float)(bqs.total_chunks - bqs.chunks_left) / (float)bqs.total_chunks) * 100.0f;\n\n            wstring_sprintf(s, t, bqs.total_chunks - bqs.chunks_left, bqs.total_chunks, pc);\n        }\n\n        SetDlgItemTextW(hwndDlg, IDC_SCRUB_STATUS, s.c_str());\n\n        if (first_time || status != bqs.status) {\n            EnableWindow(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), bqs.status != BTRFS_SCRUB_STOPPED);\n\n            if (bqs.status != BTRFS_SCRUB_STOPPED) {\n                SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETRANGE32, 0, (LPARAM)bqs.total_chunks);\n                SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETPOS, (WPARAM)(bqs.total_chunks - bqs.chunks_left), 0);\n\n                if (bqs.status == BTRFS_SCRUB_PAUSED)\n                    SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETSTATE, PBST_PAUSED, 0);\n                else\n                    SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETSTATE, PBST_NORMAL, 0);\n            } else {\n                SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETRANGE32, 0, 0);\n                SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETPOS, 0, 0);\n            }\n\n            chunks_left = bqs.chunks_left;\n        }\n    }\n\n    if (bqs.status != BTRFS_SCRUB_STOPPED && chunks_left != bqs.chunks_left) {\n        SendMessageW(GetDlgItem(hwndDlg, IDC_SCRUB_PROGRESS), PBM_SETPOS, (WPARAM)(bqs.total_chunks - bqs.chunks_left), 0);\n        chunks_left = bqs.chunks_left;\n    }\n\n    if (first_time || status != bqs.status || num_errors != bqs.num_errors) {\n        UpdateTextBox(hwndDlg, &bqs);\n\n        num_errors = bqs.num_errors;\n    }\n\n    status = bqs.status;\n}\n\nvoid BtrfsScrub::StartScrub(HWND hwndDlg) {\n    win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                               OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n    if (h != INVALID_HANDLE_VALUE) {\n        NTSTATUS Status;\n        IO_STATUS_BLOCK iosb;\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_START_SCRUB, nullptr, 0, nullptr, 0);\n\n        if (Status == STATUS_DEVICE_NOT_READY) {\n            btrfs_query_balance bqb;\n            NTSTATUS Status2;\n\n            Status2 = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_BALANCE, nullptr, 0, &bqb, sizeof(btrfs_query_balance));\n\n            if (NT_SUCCESS(Status2) && bqb.status & (BTRFS_BALANCE_RUNNING | BTRFS_BALANCE_PAUSED))\n                throw string_error(IDS_SCRUB_BALANCE_RUNNING);\n        }\n\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n\n        RefreshScrubDlg(hwndDlg, true);\n    } else\n        throw last_error(GetLastError());\n}\n\nvoid BtrfsScrub::PauseScrub(HWND) {\n    btrfs_query_scrub bqs;\n\n    win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                               OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n    if (h != INVALID_HANDLE_VALUE) {\n        NTSTATUS Status;\n        IO_STATUS_BLOCK iosb;\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_QUERY_SCRUB, nullptr, 0, &bqs, sizeof(btrfs_query_scrub));\n\n        if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW)\n            throw ntstatus_error(Status);\n\n        if (bqs.status == BTRFS_SCRUB_PAUSED)\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RESUME_SCRUB, nullptr, 0, nullptr, 0);\n        else\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_PAUSE_SCRUB, nullptr, 0, nullptr, 0);\n\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n    } else\n        throw last_error(GetLastError());\n}\n\nvoid BtrfsScrub::StopScrub(HWND) {\n    win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                               OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n    if (h != INVALID_HANDLE_VALUE) {\n        NTSTATUS Status;\n        IO_STATUS_BLOCK iosb;\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_STOP_SCRUB, nullptr, 0, nullptr, 0);\n\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n    } else\n        throw last_error(GetLastError());\n}\n\nINT_PTR CALLBACK BtrfsScrub::ScrubDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n                RefreshScrubDlg(hwndDlg, true);\n                SetTimer(hwndDlg, 1, 1000, nullptr);\n            break;\n\n            case WM_COMMAND:\n                switch (HIWORD(wParam)) {\n                    case BN_CLICKED:\n                        switch (LOWORD(wParam)) {\n                            case IDOK:\n                            case IDCANCEL:\n                                EndDialog(hwndDlg, 0);\n                            return true;\n\n                            case IDC_START_SCRUB:\n                                StartScrub(hwndDlg);\n                            return true;\n\n                            case IDC_PAUSE_SCRUB:\n                                PauseScrub(hwndDlg);\n                            return true;\n\n                            case IDC_CANCEL_SCRUB:\n                                StopScrub(hwndDlg);\n                            return true;\n                        }\n                    break;\n                }\n            break;\n\n            case WM_TIMER:\n                RefreshScrubDlg(hwndDlg, false);\n            break;\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nstatic INT_PTR CALLBACK stub_ScrubDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    BtrfsScrub* bs;\n\n    if (uMsg == WM_INITDIALOG) {\n        SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);\n        bs = (BtrfsScrub*)lParam;\n    } else {\n        bs = (BtrfsScrub*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n    }\n\n    if (bs)\n        return bs->ScrubDlgProc(hwndDlg, uMsg, wParam, lParam);\n    else\n        return false;\n}\n\nextern \"C\" void CALLBACK ShowScrubW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    try {\n        win_handle token;\n        TOKEN_PRIVILEGES tp;\n        LUID luid;\n\n        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n            throw last_error(GetLastError());\n\n        if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid))\n            throw last_error(GetLastError());\n\n        tp.PrivilegeCount = 1;\n        tp.Privileges[0].Luid = luid;\n        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n        if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))\n            throw last_error(GetLastError());\n\n        set_dpi_aware();\n\n        BtrfsScrub scrub(lpszCmdLine);\n\n        DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_SCRUB), hwnd, stub_ScrubDlgProc, (LPARAM)&scrub);\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n}\n\nextern \"C\" void CALLBACK StartScrubW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    vector<wstring> args;\n\n    command_line_to_args(lpszCmdLine, args);\n\n    if (args.size() >= 1) {\n        LUID luid;\n        TOKEN_PRIVILEGES tp;\n\n        {\n            win_handle token;\n\n            if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n                return;\n\n            if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid))\n                return;\n\n            tp.PrivilegeCount = 1;\n            tp.Privileges[0].Luid = luid;\n            tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n            if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))\n                return;\n        }\n\n        win_handle h = CreateFileW(args[0].c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                   OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n        if (h != INVALID_HANDLE_VALUE) {\n            IO_STATUS_BLOCK iosb;\n\n            NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_START_SCRUB, nullptr, 0, nullptr, 0);\n        }\n    }\n}\n\nextern \"C\" void CALLBACK StopScrubW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    vector<wstring> args;\n\n    command_line_to_args(lpszCmdLine, args);\n\n    if (args.size() >= 1) {\n        LUID luid;\n        TOKEN_PRIVILEGES tp;\n\n        {\n            win_handle token;\n\n            if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n                return;\n\n            if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid))\n                return;\n\n            tp.PrivilegeCount = 1;\n            tp.Privileges[0].Luid = luid;\n            tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n            if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))\n                return;\n        }\n\n        win_handle h = CreateFileW(args[0].c_str(), FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                   OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n        if (h != INVALID_HANDLE_VALUE) {\n            IO_STATUS_BLOCK iosb;\n\n            NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_STOP_SCRUB, nullptr, 0, nullptr, 0);\n        }\n    }\n}\n"
  },
  {
    "path": "src/shellext/scrub.h",
    "content": "/* Copyright (c) Mark Harmstone 2017\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\n#include <windows.h>\n#include \"../btrfs.h\"\n#include \"../btrfsioctl.h\"\n\nclass BtrfsScrub {\npublic:\n    BtrfsScrub(const wstring& drive) {\n        fn = drive;\n    }\n\n    INT_PTR CALLBACK ScrubDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);\n\nprivate:\n    void RefreshScrubDlg(HWND hwndDlg, bool first_time);\n    void UpdateTextBox(HWND hwndDlg, btrfs_query_scrub* bqs);\n    void StartScrub(HWND hwndDlg);\n    void PauseScrub(HWND hwndDlg);\n    void StopScrub(HWND hwndDlg);\n\n    wstring fn;\n    uint32_t status;\n    uint64_t chunks_left;\n    uint32_t num_errors;\n};\n"
  },
  {
    "path": "src/shellext/send.cpp",
    "content": "/* Copyright (c) Mark Harmstone 2017\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"shellext.h\"\n#include \"send.h\"\n#include \"resource.h\"\n#include <stddef.h>\n#include <shlobj.h>\n#include <iostream>\n\n#define SEND_BUFFER_LEN 1048576\n\nDWORD BtrfsSend::Thread() {\n    try {\n        NTSTATUS Status;\n        IO_STATUS_BLOCK iosb;\n        btrfs_send_subvol* bss;\n        btrfs_send_header header;\n        btrfs_send_command end;\n        ULONG i;\n\n        buf = (char*)malloc(SEND_BUFFER_LEN);\n\n        try {\n            dirh = CreateFileW(subvol.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n            if (dirh == INVALID_HANDLE_VALUE)\n                throw string_error(IDS_SEND_CANT_OPEN_DIR, subvol.c_str(), GetLastError(), format_message(GetLastError()).c_str());\n\n            try {\n                size_t bss_size = offsetof(btrfs_send_subvol, clones[0]) + (clones.size() * sizeof(HANDLE));\n                bss = (btrfs_send_subvol*)malloc(bss_size);\n                memset(bss, 0, bss_size);\n\n                if (incremental) {\n                    WCHAR parent[MAX_PATH];\n                    HANDLE parenth;\n\n                    parent[0] = 0;\n\n                    GetDlgItemTextW(hwnd, IDC_PARENT_SUBVOL, parent, sizeof(parent) / sizeof(WCHAR));\n\n                    parenth = CreateFileW(parent, FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n                    if (parenth == INVALID_HANDLE_VALUE)\n                        throw string_error(IDS_SEND_CANT_OPEN_DIR, parent, GetLastError(), format_message(GetLastError()).c_str());\n\n                    bss->parent = parenth;\n                } else\n                    bss->parent = nullptr;\n\n                bss->num_clones = (ULONG)clones.size();\n\n                for (i = 0; i < bss->num_clones; i++) {\n                    HANDLE h;\n\n                    h = CreateFileW(clones[i].c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n                    if (h == INVALID_HANDLE_VALUE) {\n                        auto le = GetLastError();\n                        ULONG j;\n\n                        for (j = 0; j < i; j++) {\n                            CloseHandle(bss->clones[j]);\n                        }\n\n                        if (bss->parent) CloseHandle(bss->parent);\n\n                        throw string_error(IDS_SEND_CANT_OPEN_DIR, clones[i].c_str(), le, format_message(le).c_str());\n                    }\n\n                    bss->clones[i] = h;\n                }\n\n                Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SEND_SUBVOL, bss, (ULONG)bss_size, nullptr, 0);\n\n                for (i = 0; i < bss->num_clones; i++) {\n                    CloseHandle(bss->clones[i]);\n                }\n\n                if (!NT_SUCCESS(Status)) {\n                    if (Status == (NTSTATUS)STATUS_INVALID_PARAMETER) {\n                        BY_HANDLE_FILE_INFORMATION fileinfo;\n                        if (!GetFileInformationByHandle(dirh, &fileinfo)) {\n                            auto le = GetLastError();\n                            if (bss->parent) CloseHandle(bss->parent);\n                            throw string_error(IDS_SEND_GET_FILE_INFO_FAILED, le, format_message(le).c_str());\n                        }\n\n                        if (!(fileinfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) {\n                            if (bss->parent) CloseHandle(bss->parent);\n                            throw string_error(IDS_SEND_NOT_READONLY);\n                        }\n\n                        if (bss->parent) {\n                            if (!GetFileInformationByHandle(bss->parent, &fileinfo)) {\n                                auto le = GetLastError();\n                                CloseHandle(bss->parent);\n                                throw string_error(IDS_SEND_GET_FILE_INFO_FAILED, le, format_message(le).c_str());\n                            }\n\n                            if (!(fileinfo.dwFileAttributes & FILE_ATTRIBUTE_READONLY)) {\n                                CloseHandle(bss->parent);\n                                throw string_error(IDS_SEND_PARENT_NOT_READONLY);\n                            }\n                        }\n                    }\n\n                    if (bss->parent) CloseHandle(bss->parent);\n                    throw string_error(IDS_SEND_FSCTL_BTRFS_SEND_SUBVOL_FAILED, Status, format_ntstatus(Status).c_str());\n                }\n\n                if (bss->parent) CloseHandle(bss->parent);\n\n                stream = CreateFileW(file, FILE_WRITE_DATA | DELETE, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);\n                if (stream == INVALID_HANDLE_VALUE)\n                    throw string_error(IDS_SEND_CANT_OPEN_FILE, file, GetLastError(), format_message(GetLastError()).c_str());\n\n                try {\n                    memcpy(header.magic, BTRFS_SEND_MAGIC, sizeof(header.magic));\n                    header.version = 1;\n\n                    if (!WriteFile(stream, &header, sizeof(header), nullptr, nullptr))\n                        throw string_error(IDS_SEND_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n                    do {\n                        Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_READ_SEND_BUFFER, nullptr, 0, buf, SEND_BUFFER_LEN);\n\n                        if (NT_SUCCESS(Status)) {\n                            if (!WriteFile(stream, buf, (DWORD)iosb.Information, nullptr, nullptr))\n                                throw string_error(IDS_SEND_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n                        }\n                    } while (NT_SUCCESS(Status));\n\n                    if (Status != STATUS_END_OF_FILE)\n                        throw string_error(IDS_SEND_FSCTL_BTRFS_READ_SEND_BUFFER_FAILED, Status, format_ntstatus(Status).c_str());\n\n                    end.length = 0;\n                    end.cmd = BTRFS_SEND_CMD_END;\n                    end.csum = 0x9dc96c50;\n\n                    if (!WriteFile(stream, &end, sizeof(end), nullptr, nullptr))\n                        throw string_error(IDS_SEND_WRITEFILE_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n                    SetEndOfFile(stream);\n                } catch (...) {\n                    FILE_DISPOSITION_INFO fdi;\n\n                    fdi.DeleteFile = true;\n\n                    Status = NtSetInformationFile(stream, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);\n\n                    CloseHandle(stream);\n                    stream = INVALID_HANDLE_VALUE;\n\n                    if (!NT_SUCCESS(Status))\n                        throw ntstatus_error(Status);\n\n                    throw;\n                }\n\n                CloseHandle(stream);\n                stream = INVALID_HANDLE_VALUE;\n            } catch (...) {\n                CloseHandle(dirh);\n                dirh = INVALID_HANDLE_VALUE;\n\n                throw;\n            }\n\n            CloseHandle(dirh);\n            dirh = INVALID_HANDLE_VALUE;\n        } catch (...) {\n            free(buf);\n            buf = nullptr;\n\n            started = false;\n\n            SetDlgItemTextW(hwnd, IDCANCEL, closetext);\n            EnableWindow(GetDlgItem(hwnd, IDOK), true);\n            EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), true);\n            EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), true);\n\n            throw;\n        }\n    } catch (const exception& e) {\n        auto msg = utf8_to_utf16(e.what());\n\n        SetDlgItemTextW(hwnd, IDC_SEND_STATUS, msg.c_str());\n        return 0;\n    }\n\n\n    free(buf);\n    buf = nullptr;\n\n    started = false;\n\n    SetDlgItemTextW(hwnd, IDCANCEL, closetext);\n    EnableWindow(GetDlgItem(hwnd, IDOK), true);\n    EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), true);\n    EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), true);\n\n    wstring success;\n\n    load_string(module, IDS_SEND_SUCCESS, success);\n\n    SetDlgItemTextW(hwnd, IDC_SEND_STATUS, success.c_str());\n\n    return 0;\n}\n\nstatic DWORD WINAPI send_thread(LPVOID lpParameter) {\n    BtrfsSend* bs = (BtrfsSend*)lpParameter;\n\n    return bs->Thread();\n}\n\nvoid BtrfsSend::StartSend(HWND hwnd) {\n    wstring s;\n    HWND cl;\n\n    if (started)\n        return;\n\n    GetDlgItemTextW(hwnd, IDC_STREAM_DEST, file, sizeof(file) / sizeof(WCHAR));\n\n    if (file[0] == 0)\n        return;\n\n    if (incremental) {\n        WCHAR parent[MAX_PATH];\n\n        GetDlgItemTextW(hwnd, IDC_PARENT_SUBVOL, parent, sizeof(parent) / sizeof(WCHAR));\n\n        if (parent[0] == 0)\n            return;\n    }\n\n    started = true;\n\n    wstring writing;\n\n    load_string(module, IDS_SEND_WRITING, writing);\n\n    SetDlgItemTextW(hwnd, IDC_SEND_STATUS, writing.c_str());\n\n    load_string(module, IDS_SEND_CANCEL, s);\n    SetDlgItemTextW(hwnd, IDCANCEL, s.c_str());\n\n    EnableWindow(GetDlgItem(hwnd, IDOK), false);\n    EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), false);\n    EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), false);\n\n    clones.clear();\n\n    cl = GetDlgItem(hwnd, IDC_CLONE_LIST);\n    auto num_clones = SendMessageW(cl, LB_GETCOUNT, 0, 0);\n\n    if (num_clones != LB_ERR) {\n        for (unsigned int i = 0; i < (unsigned int)num_clones; i++) {\n            WCHAR* t;\n\n            auto len = SendMessageW(cl, LB_GETTEXTLEN, i, 0);\n            t = (WCHAR*)malloc((len + 1) * sizeof(WCHAR));\n\n            SendMessageW(cl, LB_GETTEXT, i, (LPARAM)t);\n\n            clones.push_back(t);\n\n            free(t);\n        }\n    }\n\n    thread = CreateThread(nullptr, 0, send_thread, this, 0, nullptr);\n\n    if (!thread)\n        throw last_error(GetLastError());\n}\n\nvoid BtrfsSend::Browse(HWND hwnd) {\n    OPENFILENAMEW ofn;\n\n    file[0] = 0;\n\n    memset(&ofn, 0, sizeof(OPENFILENAMEW));\n    ofn.lStructSize = sizeof(OPENFILENAMEW);\n    ofn.hwndOwner = hwnd;\n    ofn.hInstance = module;\n    ofn.lpstrFile = file;\n    ofn.nMaxFile = sizeof(file) / sizeof(WCHAR);\n\n    if (!GetSaveFileNameW(&ofn))\n        return;\n\n    SetDlgItemTextW(hwnd, IDC_STREAM_DEST, file);\n}\n\nvoid BtrfsSend::BrowseParent(HWND hwnd) {\n    BROWSEINFOW bi;\n    PIDLIST_ABSOLUTE root, pidl;\n    HRESULT hr;\n    WCHAR parent[MAX_PATH], volpathw[MAX_PATH];\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    btrfs_get_file_ids bgfi;\n\n    if (!GetVolumePathNameW(subvol.c_str(), volpathw, (sizeof(volpathw) / sizeof(WCHAR)) - 1))\n        throw string_error(IDS_RECV_GETVOLUMEPATHNAME_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n    hr = SHParseDisplayName(volpathw, 0, &root, 0, 0);\n    if (FAILED(hr))\n        throw string_error(IDS_SHPARSEDISPLAYNAME_FAILED);\n\n    memset(&bi, 0, sizeof(BROWSEINFOW));\n\n    bi.hwndOwner = hwnd;\n    bi.pidlRoot = root;\n    bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_NONEWFOLDERBUTTON;\n\n    pidl = SHBrowseForFolderW(&bi);\n\n    if (!pidl)\n        return;\n\n    if (!SHGetPathFromIDListW(pidl, parent))\n        throw string_error(IDS_SHGETPATHFROMIDLIST_FAILED);\n\n    {\n        win_handle h = CreateFileW(parent, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n        if (h == INVALID_HANDLE_VALUE)\n            throw string_error(IDS_SEND_CANT_OPEN_DIR, parent, GetLastError(), format_message(GetLastError()).c_str());\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids));\n        if (!NT_SUCCESS(Status))\n            throw string_error(IDS_GET_FILE_IDS_FAILED, Status, format_ntstatus(Status).c_str());\n    }\n\n    if (bgfi.inode != 0x100 || bgfi.top)\n        throw string_error(IDS_NOT_SUBVOL);\n\n    SetDlgItemTextW(hwnd, IDC_PARENT_SUBVOL, parent);\n}\n\nvoid BtrfsSend::AddClone(HWND hwnd) {\n    BROWSEINFOW bi;\n    PIDLIST_ABSOLUTE root, pidl;\n    HRESULT hr;\n    WCHAR path[MAX_PATH], volpathw[MAX_PATH];\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    btrfs_get_file_ids bgfi;\n\n    if (!GetVolumePathNameW(subvol.c_str(), volpathw, (sizeof(volpathw) / sizeof(WCHAR)) - 1))\n        throw string_error(IDS_RECV_GETVOLUMEPATHNAME_FAILED, GetLastError(), format_message(GetLastError()).c_str());\n\n    hr = SHParseDisplayName(volpathw, 0, &root, 0, 0);\n    if (FAILED(hr))\n        throw string_error(IDS_SHPARSEDISPLAYNAME_FAILED);\n\n    memset(&bi, 0, sizeof(BROWSEINFOW));\n\n    bi.hwndOwner = hwnd;\n    bi.pidlRoot = root;\n    bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_USENEWUI | BIF_NONEWFOLDERBUTTON;\n\n    pidl = SHBrowseForFolderW(&bi);\n\n    if (!pidl)\n        return;\n\n    if (!SHGetPathFromIDListW(pidl, path))\n        throw string_error(IDS_SHGETPATHFROMIDLIST_FAILED);\n\n    {\n        win_handle h = CreateFileW(path, FILE_TRAVERSE, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n        if (h == INVALID_HANDLE_VALUE)\n            throw string_error(IDS_SEND_CANT_OPEN_DIR, path, GetLastError(), format_message(GetLastError()).c_str());\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_FILE_IDS, nullptr, 0, &bgfi, sizeof(btrfs_get_file_ids));\n        if (!NT_SUCCESS(Status))\n            throw string_error(IDS_GET_FILE_IDS_FAILED, Status, format_ntstatus(Status).c_str());\n    }\n\n    if (bgfi.inode != 0x100 || bgfi.top)\n        throw string_error(IDS_NOT_SUBVOL);\n\n    SendMessageW(GetDlgItem(hwnd, IDC_CLONE_LIST), LB_ADDSTRING, 0, (LPARAM)path);\n}\n\nvoid BtrfsSend::RemoveClone(HWND hwnd) {\n    LRESULT sel;\n    HWND cl = GetDlgItem(hwnd, IDC_CLONE_LIST);\n\n    sel = SendMessageW(cl, LB_GETCURSEL, 0, 0);\n\n    if (sel == LB_ERR)\n        return;\n\n    SendMessageW(cl, LB_DELETESTRING, sel, 0);\n\n    if (SendMessageW(cl, LB_GETCURSEL, 0, 0) == LB_ERR)\n        EnableWindow(GetDlgItem(hwnd, IDC_CLONE_REMOVE), false);\n}\n\nINT_PTR BtrfsSend::SendDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n                this->hwnd = hwndDlg;\n\n                GetDlgItemTextW(hwndDlg, IDCANCEL, closetext, sizeof(closetext) / sizeof(WCHAR));\n            break;\n\n            case WM_COMMAND:\n                switch (HIWORD(wParam)) {\n                    case BN_CLICKED:\n                        switch (LOWORD(wParam)) {\n                            case IDOK:\n                                StartSend(hwndDlg);\n                            return true;\n\n                            case IDCANCEL:\n                                if (started) {\n                                    TerminateThread(thread, 0);\n\n                                    if (stream != INVALID_HANDLE_VALUE) {\n                                        NTSTATUS Status;\n                                        FILE_DISPOSITION_INFO fdi;\n                                        IO_STATUS_BLOCK iosb;\n\n                                        fdi.DeleteFile = true;\n\n                                        Status = NtSetInformationFile(stream, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);\n\n                                        CloseHandle(stream);\n\n                                        if (!NT_SUCCESS(Status))\n                                            throw ntstatus_error(Status);\n                                    }\n\n                                    if (dirh != INVALID_HANDLE_VALUE)\n                                        CloseHandle(dirh);\n\n                                    started = false;\n\n                                    SetDlgItemTextW(hwndDlg, IDCANCEL, closetext);\n\n                                    EnableWindow(GetDlgItem(hwnd, IDOK), true);\n                                    EnableWindow(GetDlgItem(hwnd, IDC_STREAM_DEST), true);\n                                    EnableWindow(GetDlgItem(hwnd, IDC_BROWSE), true);\n                                } else\n                                    EndDialog(hwndDlg, 1);\n                            return true;\n\n                            case IDC_BROWSE:\n                                Browse(hwndDlg);\n                            return true;\n\n                            case IDC_INCREMENTAL:\n                                incremental = IsDlgButtonChecked(hwndDlg, LOWORD(wParam));\n\n                                EnableWindow(GetDlgItem(hwnd, IDC_PARENT_SUBVOL), incremental);\n                                EnableWindow(GetDlgItem(hwnd, IDC_PARENT_BROWSE), incremental);\n                            return true;\n\n                            case IDC_PARENT_BROWSE:\n                                BrowseParent(hwndDlg);\n                            return true;\n\n                            case IDC_CLONE_ADD:\n                                AddClone(hwndDlg);\n                            return true;\n\n                            case IDC_CLONE_REMOVE:\n                                RemoveClone(hwndDlg);\n                            return true;\n                        }\n                    break;\n\n                    case LBN_SELCHANGE:\n                        switch (LOWORD(wParam)) {\n                            case IDC_CLONE_LIST:\n                                EnableWindow(GetDlgItem(hwnd, IDC_CLONE_REMOVE), true);\n                            return true;\n                        }\n                    break;\n                }\n            break;\n        }\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n\n    return false;\n}\n\nstatic INT_PTR CALLBACK stub_SendDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    BtrfsSend* bs;\n\n    if (uMsg == WM_INITDIALOG) {\n        SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);\n        bs = (BtrfsSend*)lParam;\n    } else\n        bs = (BtrfsSend*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n\n    if (bs)\n        return bs->SendDlgProc(hwndDlg, uMsg, wParam, lParam);\n    else\n        return false;\n}\n\nvoid BtrfsSend::Open(HWND hwnd, LPWSTR path) {\n    subvol = path;\n\n    if (DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_SEND_SUBVOL), hwnd, stub_SendDlgProc, (LPARAM)this) <= 0)\n        throw last_error(GetLastError());\n}\n\nextern \"C\" void CALLBACK SendSubvolGUIW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    try {\n        win_handle token;\n        TOKEN_PRIVILEGES tp;\n        LUID luid;\n\n        set_dpi_aware();\n\n        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n            throw last_error(GetLastError());\n\n        if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid))\n            throw last_error(GetLastError());\n\n        tp.PrivilegeCount = 1;\n        tp.Privileges[0].Luid = luid;\n        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n        if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))\n            throw last_error(GetLastError());\n\n        BtrfsSend bs;\n\n        bs.Open(hwnd, lpszCmdLine);\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n}\n\nstatic void send_subvol(const wstring& subvol, const wstring& file, const wstring& parent, const vector<wstring>& clones) {\n    char* buf;\n    win_handle dirh, stream;\n    ULONG i;\n    btrfs_send_subvol* bss;\n    IO_STATUS_BLOCK iosb;\n    NTSTATUS Status;\n    btrfs_send_header header;\n    btrfs_send_command end;\n\n    buf = (char*)malloc(SEND_BUFFER_LEN);\n\n    try {\n        dirh = CreateFileW(subvol.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n        if (dirh == INVALID_HANDLE_VALUE)\n            throw last_error(GetLastError());\n\n        stream = CreateFileW(file.c_str(), FILE_WRITE_DATA | DELETE, 0, nullptr, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr);\n        if (stream == INVALID_HANDLE_VALUE)\n            throw last_error(GetLastError());\n\n        try {\n            size_t bss_size = offsetof(btrfs_send_subvol, clones[0]) + (clones.size() * sizeof(HANDLE));\n            bss = (btrfs_send_subvol*)malloc(bss_size);\n            memset(bss, 0, bss_size);\n\n            if (parent != L\"\") {\n                HANDLE parenth;\n\n                parenth = CreateFileW(parent.c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n                if (parenth == INVALID_HANDLE_VALUE)\n                    throw last_error(GetLastError());\n\n                bss->parent = parenth;\n            } else\n                bss->parent = nullptr;\n\n            bss->num_clones = (ULONG)clones.size();\n\n            for (i = 0; i < bss->num_clones; i++) {\n                HANDLE h;\n\n                h = CreateFileW(clones[i].c_str(), FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, nullptr);\n                if (h == INVALID_HANDLE_VALUE) {\n                    auto le = GetLastError();\n                    ULONG j;\n\n                    for (j = 0; j < i; j++) {\n                        CloseHandle(bss->clones[j]);\n                    }\n\n                    if (bss->parent) CloseHandle(bss->parent);\n\n                    throw last_error(le);\n                }\n\n                bss->clones[i] = h;\n            }\n\n            Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_SEND_SUBVOL, bss, (ULONG)bss_size, nullptr, 0);\n\n            for (i = 0; i < bss->num_clones; i++) {\n                CloseHandle(bss->clones[i]);\n            }\n\n            if (bss->parent) CloseHandle(bss->parent);\n\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n\n            memcpy(header.magic, BTRFS_SEND_MAGIC, sizeof(header.magic));\n            header.version = 1;\n\n            if (!WriteFile(stream, &header, sizeof(header), nullptr, nullptr))\n                throw last_error(GetLastError());\n\n            do {\n                Status = NtFsControlFile(dirh, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_READ_SEND_BUFFER, nullptr, 0, buf, SEND_BUFFER_LEN);\n\n                if (NT_SUCCESS(Status))\n                    WriteFile(stream, buf, (DWORD)iosb.Information, nullptr, nullptr);\n            } while (NT_SUCCESS(Status));\n\n            if (Status != STATUS_END_OF_FILE)\n                throw ntstatus_error(Status);\n\n            end.length = 0;\n            end.cmd = BTRFS_SEND_CMD_END;\n            end.csum = 0x9dc96c50;\n\n            if (!WriteFile(stream, &end, sizeof(end), nullptr, nullptr))\n                throw last_error(GetLastError());\n\n            SetEndOfFile(stream);\n        } catch (...) {\n            FILE_DISPOSITION_INFO fdi;\n\n            fdi.DeleteFile = true;\n\n            Status = NtSetInformationFile(stream, &iosb, &fdi, sizeof(FILE_DISPOSITION_INFO), FileDispositionInformation);\n            if (!NT_SUCCESS(Status))\n                throw ntstatus_error(Status);\n\n            throw;\n        }\n    } catch (...) {\n        free(buf);\n        throw;\n    }\n\n    free(buf);\n}\n\nextern \"C\" void CALLBACK SendSubvolW(HWND, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    vector<wstring> args;\n    wstring subvol, parent, file;\n    vector<wstring> clones;\n\n    command_line_to_args(lpszCmdLine, args);\n\n    if (args.size() >= 2) {\n        TOKEN_PRIVILEGES tp;\n        LUID luid;\n\n        {\n            win_handle token;\n\n            if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n                return;\n\n            if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid))\n                return;\n\n            tp.PrivilegeCount = 1;\n            tp.Privileges[0].Luid = luid;\n            tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n            if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))\n                return;\n        }\n\n        for (unsigned int i = 0; i < args.size(); i++) {\n            if (args[i][0] == '-') {\n                if (args[i][2] == 0 && i < args.size() - 1) {\n                    if (args[i][1] == 'p') {\n                        parent = args[i+1];\n                        i++;\n                    } else if (args[i][1] == 'c') {\n                        clones.push_back(args[i+1]);\n                        i++;\n                    }\n                }\n            } else {\n                if (subvol == L\"\")\n                    subvol = args[i];\n                else if (file == L\"\")\n                    file = args[i];\n            }\n        }\n\n        if (subvol != L\"\" && file != L\"\") {\n            try {\n                send_subvol(subvol, file, parent, clones);\n            } catch (const exception& e) {\n                cerr << \"Error: \" << e.what() << endl;\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "src/shellext/send.h",
    "content": "/* Copyright (c) Mark Harmstone 2017\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\n#include \"../btrfs.h\"\n\nclass BtrfsSend {\npublic:\n    BtrfsSend() {\n        started = false;\n        file[0] = 0;\n        dirh = INVALID_HANDLE_VALUE;\n        stream = INVALID_HANDLE_VALUE;\n        subvol = L\"\";\n        buf = nullptr;\n        incremental = false;\n    }\n\n    ~BtrfsSend() {\n        if (buf)\n            free(buf);\n    }\n\n    void Open(HWND hwnd, WCHAR* path);\n    INT_PTR SendDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);\n    DWORD Thread();\n\nprivate:\n    void StartSend(HWND hwnd);\n    void Browse(HWND hwnd);\n    void BrowseParent(HWND hwnd);\n    void AddClone(HWND hwnd);\n    void RemoveClone(HWND hwnd);\n\n    bool started;\n    bool incremental;\n    WCHAR file[MAX_PATH], closetext[255];\n    HANDLE thread, dirh, stream;\n    HWND hwnd;\n    wstring subvol;\n    char* buf;\n    vector <wstring> clones;\n};\n"
  },
  {
    "path": "src/shellext/shellbtrfs.def",
    "content": "EXPORTS\r\n    DllCanUnloadNow     \tPRIVATE\r\n    DllGetClassObject   \tPRIVATE\r\n    DllRegisterServer   \tPRIVATE\r\n    DllUnregisterServer \tPRIVATE\r\n    DllInstall\t\t\t\tPRIVATE\r\n    AddDeviceW\t\t\tPRIVATE\r\n    RemoveDeviceW\t\tPRIVATE\r\n    StartBalanceW\t\tPRIVATE\r\n    PauseBalanceW\t\tPRIVATE\r\n    StopBalanceW\t\tPRIVATE\r\n    ShowScrubW\t\t\tPRIVATE\r\n    ResetStatsW\t\t\tPRIVATE\r\n    ShowPropSheetW\t\tPRIVATE\r\n    RecvSubvolGUIW\t\tPRIVATE\r\n    SendSubvolGUIW\t\tPRIVATE\r\n    CreateSubvolW\t\tPRIVATE\r\n    CreateSnapshotW\t\tPRIVATE\r\n    ReflinkCopyW\t\tPRIVATE\r\n    StartScrubW\t\t\tPRIVATE\r\n    StopScrubW\t\t\tPRIVATE\r\n    SendSubvolW\t\t\tPRIVATE\r\n    RecvSubvolW\t\t\tPRIVATE\r\n    ResizeDeviceW\t\tPRIVATE\r\n    ShowChangeDriveLetterW\tPRIVATE\r\n    MappingsTest\t\tPRIVATE\r\n"
  },
  {
    "path": "src/shellext/shellbtrfs.manifest",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\r\n<assembly xmlns=\"urn:schemas-microsoft-com:asm.v1\" manifestVersion=\"1.0\">\r\n    <assemblyIdentity version=\"1.0.0.0\" processorArchitecture=\"*\" name=\"WinBtrfs.shellext\" type=\"win32\" />\r\n    <description>WinBtrfs shell extension</description>\r\n    <dependency>\r\n        <dependentAssembly>\r\n            <assemblyIdentity type=\"win32\" name=\"Microsoft.Windows.Common-Controls\" version=\"6.0.0.0\" processorArchitecture=\"*\" publicKeyToken=\"6595b64144ccf1df\" language=\"*\" />\r\n        </dependentAssembly>\r\n    </dependency>\r\n</assembly>\r\n"
  },
  {
    "path": "src/shellext/shellbtrfs.rc.in",
    "content": "// Microsoft Visual C++ generated resource script.\n//\n#include \"@CMAKE_CURRENT_SOURCE_DIR@/src/shellext/resource.h\"\n\n#define APSTUDIO_READONLY_SYMBOLS\n/////////////////////////////////////////////////////////////////////////////\n//\n// Generated from the TEXTINCLUDE 2 resource.\n//\n#include <winresrc.h>\n\n#ifndef IDC_STATIC\n#define IDC_STATIC (-1)\n#endif\n\n/////////////////////////////////////////////////////////////////////////////\n#undef APSTUDIO_READONLY_SYMBOLS\n\n/////////////////////////////////////////////////////////////////////////////\n// English (United Kingdom) resources\n\n#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)\nLANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK\n#pragma code_page(1252)\n\n#ifdef APSTUDIO_INVOKED\n/////////////////////////////////////////////////////////////////////////////\n//\n// TEXTINCLUDE\n//\n\n1 TEXTINCLUDE\nBEGIN\n    \"resource.h\\0\"\nEND\n\n2 TEXTINCLUDE\nBEGIN\n    \"#include \"\"winres.h\"\"\\r\\n\"\n    \"\\0\"\nEND\n\n3 TEXTINCLUDE\nBEGIN\n    \"\\r\\n\"\n    \"\\0\"\nEND\n\n#endif    // APSTUDIO_INVOKED\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// Icon\n//\n\n// Icon with lowest ID value placed first to ensure application icon\n// remains consistent on all systems.\nIDI_ICON1               ICON                    \"@CMAKE_CURRENT_SOURCE_DIR@/src/shellext/subvol.ico\"\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// Version\n//\n\nVS_VERSION_INFO VERSIONINFO\n FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0\n PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0\n FILEFLAGSMASK 0x17L\n#ifdef _DEBUG\n FILEFLAGS 0x1L\n#else\n FILEFLAGS 0x0L\n#endif\n FILEOS 0x4L\n FILETYPE 0x1L\n FILESUBTYPE 0x0L\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"080904b0\"\n        BEGIN\n            VALUE \"FileDescription\", \"WinBtrfs shell extension\"\n            VALUE \"FileVersion\", \"@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@\"\n            VALUE \"InternalName\", \"btrfs\"\n            VALUE \"LegalCopyright\", \"Copyright (c) Mark Harmstone 2016-24\"\n            VALUE \"OriginalFilename\", \"shellbtrfs.dll\"\n            VALUE \"ProductName\", \"WinBtrfs\"\n            VALUE \"ProductVersion\", \"@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x809, 1200\n    END\nEND\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// Dialog\n//\n\nIDD_PROP_SHEET DIALOGEX 0, 0, 235, 271\nSTYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_DISABLED | WS_CAPTION\nCAPTION \"Inode property sheet\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x0\nBEGIN\n    LTEXT           \"Subvolume:\",IDC_STATIC,14,21,38,8\n    LTEXT           \"Inode:\",IDC_STATIC,14,35,21,8\n    GROUPBOX        \"Information\",IDC_GROUP_INFORMATION,7,7,221,99\n    LTEXT           \"Type:\",IDC_STATIC,14,49,18,8\n    GROUPBOX        \"POSIX permissions\",IDC_STATIC,7,110,221,102\n    LTEXT           \"User:\",IDC_STATIC,14,125,17,8\n    LTEXT           \"Group:\",IDC_STATIC,14,141,22,8\n    EDITTEXT        IDC_UID,94,123,40,14,ES_AUTOHSCROLL | ES_NUMBER\n    EDITTEXT        IDC_GID,94,139,40,14,ES_AUTOHSCROLL | ES_NUMBER\n    LTEXT           \"User\",IDC_STATIC,14,175,15,8\n    LTEXT           \"Group\",IDC_STATIC,14,186,20,8\n    LTEXT           \"Others\",IDC_STATIC,14,196,22,8\n    LTEXT           \"Read\",IDC_STATIC,50,162,17,8\n    LTEXT           \"Write\",IDC_STATIC,89,162,18,8\n    LTEXT           \"Execute\",IDC_STATIC,129,162,30,8\n    CONTROL         \"\",IDC_USERR,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,54,175,16,10\n    CONTROL         \"\",IDC_GROUPR,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,54,186,16,10\n    CONTROL         \"\",IDC_OTHERR,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,54,196,16,10\n    CONTROL         \"\",IDC_USERW,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,93,175,16,10\n    CONTROL         \"\",IDC_GROUPW,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,93,186,16,10\n    CONTROL         \"\",IDC_OTHERW,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,93,196,16,10\n    CONTROL         \"\",IDC_USERX,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,135,175,16,10\n    CONTROL         \"\",IDC_GROUPX,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,135,186,16,10\n    CONTROL         \"\",IDC_OTHERX,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,135,196,16,10\n    GROUPBOX        \"Flags\",IDC_STATIC,7,218,221,48\n    CONTROL         \"Disable Copy-on-Write\",IDC_NODATACOW,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,14,232,86,10\n    LTEXT           \"(blank)\",IDC_INODE,78,35,70,8\n    LTEXT           \"(blank)\",IDC_TYPE,78,49,116,8\n    CONTROL         \"Compress\",IDC_COMPRESS,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,14,246,46,10\n    LTEXT           \"Size on disk:\",IDC_STATIC,14,63,61,8\n    CONTROL         \"%s (<a>Details</a>)\",IDC_SIZE_ON_DISK,\"SysLink\",WS_TABSTOP,78,63,142,8\n    COMBOBOX        IDC_COMPRESS_TYPE,63,245,48,13,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP\n    CONTROL         \"Readonly subvolume\",IDC_SUBVOL_RO,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,124,232,80,10\n    LTEXT           \"(blank)\",IDC_SUBVOL,78,21,70,8\n    PUSHBUTTON      \"&Open as Admin\",IDC_OPEN_ADMIN,151,21,70,14\n    CONTROL         \"Set UID\",IDC_SETUID,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,177,175,40,10\n    CONTROL         \"Set GID\",IDC_SETGID,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,177,186,40,10\n    CONTROL         \"Sticky\",IDC_STICKY,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,177,196,34,10\n    LTEXT           \"Compression ratio:\",IDC_STATIC,14,77,61,8\n    LTEXT           \"%1.1f%%\",IDC_COMPRESSION_RATIO,78,77,116,8\n    LTEXT           \"Fragmentation:\",IDC_STATIC,14,91,61,8\n    LTEXT           \"%1.1f%%\",IDC_FRAGMENTATION,78,91,116,8\nEND\n\nIDD_SIZE_DETAILS DIALOGEX 0, 0, 212, 98\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Size details\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    DEFPUSHBUTTON   \"OK\",IDOK,81,77,50,14\n    LTEXT           \"Inline:\",IDC_STATIC,7,7,21,8\n    LTEXT           \"Uncompressed:\",IDC_STATIC,7,20,49,8\n    LTEXT           \"ZLIB:\",IDC_STATIC,7,33,18,8\n    LTEXT           \"LZO:\",IDC_STATIC,7,46,16,8\n    LTEXT           \"(blank)\",IDC_SIZE_INLINE,63,7,142,8\n    LTEXT           \"(blank)\",IDC_SIZE_UNCOMPRESSED,63,20,142,8\n    LTEXT           \"(blank)\",IDC_SIZE_ZLIB,63,33,142,8\n    LTEXT           \"(blank)\",IDC_SIZE_LZO,63,46,142,8\n    LTEXT           \"Zstd:\",IDC_STATIC,7,59,16,8\n    LTEXT           \"(blank)\",IDC_SIZE_ZSTD,63,59,142,8\nEND\n\nIDD_VOL_PROP_SHEET DIALOGEX 0, 0, 235, 273\nSTYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_DISABLED | WS_CAPTION\nCAPTION \"s\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x0\nBEGIN\n    LTEXT           \"UUID:\",IDC_STATIC,7,15,20,8\n    LTEXT           \"%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x\",IDC_UUID,32,15,294,8\n    PUSHBUTTON      \"Change drive &letter...\",IDC_VOL_CHANGE_DRIVE_LETTER,7,30,101,19\n    PUSHBUTTON      \"Show &usage...\",IDC_VOL_SHOW_USAGE,154,69,67,19\n    PUSHBUTTON      \"&Balance...\",IDC_VOL_BALANCE,154,127,67,19\n    PUSHBUTTON      \"&Devices...\",IDC_VOL_DEVICES,154,184,67,19\n    GROUPBOX        \"Usage\",IDC_STATIC,7,53,221,53\n    LTEXT           \"Show detailed information about internal filesystem usage. This is the equivalent to the command \"\"btrfs fi usage\"\" on Linux.\",IDC_STATIC,14,66,131,33\n    GROUPBOX        \"Balance\",IDC_STATIC,7,109,221,53\n    LTEXT           \"Balancing reads and rewrites data and metadata. It can be used to consolidate free space, as well as to convert between different RAID types.\",IDC_STATIC,15,120,131,39\n    GROUPBOX        \"Devices\",IDC_STATIC,7,168,221,45\n    LTEXT           \"Allows you to add disks or partitions to this filesystem, or remove those already present.\",IDC_STATIC,14,181,131,30\n    GROUPBOX        \"Scrub\",IDC_STATIC,7,221,221,45\n    LTEXT           \"Scrubbing verifies the data and metadata of a filesystem, and where possible will correct any errors.\",IDC_STATIC,15,234,131,27\n    PUSHBUTTON      \"&Scrub...\",IDC_VOL_SCRUB,154,237,67,19\nEND\n\nIDD_VOL_USAGE DIALOGEX 0, 0, 235, 242\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Volume usage\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    DEFPUSHBUTTON   \"OK\",IDOK,62,221,50,14\n    PUSHBUTTON      \"&Refresh\",IDC_USAGE_REFRESH,124,221,50,14\n    EDITTEXT        IDC_USAGE_BOX,7,7,221,208,ES_MULTILINE | ES_AUTOHSCROLL | ES_READONLY | WS_VSCROLL\nEND\n\nIDD_BALANCE_OPTIONS DIALOGEX 0, 0, 303, 138\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Balance options\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    DEFPUSHBUTTON   \"OK\",IDOK,91,117,50,14\n    PUSHBUTTON      \"Cancel\",IDCANCEL,161,117,50,14\n    CONTROL         \"&Profiles:\",IDC_PROFILES,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,7,6,41,10\n    CONTROL         \"Single\",IDC_PROFILES_SINGLE,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,12,19,35,10\n    CONTROL         \"DUP\",IDC_PROFILES_DUP,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,12,30,29,10\n    CONTROL         \"RAID0\",IDC_PROFILES_RAID0,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,12,41,36,10\n    CONTROL         \"RAID1\",IDC_PROFILES_RAID1,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,12,52,36,10\n    CONTROL         \"RAID10\",IDC_PROFILES_RAID10,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,12,63,39,10\n    CONTROL         \"RAID5\",IDC_PROFILES_RAID5,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,52,19,36,10\n    CONTROL         \"RAID6\",IDC_PROFILES_RAID6,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,52,30,36,10\n    CONTROL         \"RAID1C3\",IDC_PROFILES_RAID1C3,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,52,41,44,10\n    CONTROL         \"RAID1C4\",IDC_PROFILES_RAID1C4,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,52,52,44,10\n    CONTROL         \"&Usage:\",IDC_USAGE,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,7,80,37,10\n    EDITTEXT        IDC_USAGE_START,7,94,19,14,ES_AUTOHSCROLL | ES_NUMBER\n    CONTROL         \"\",IDC_USAGE_START_SPINNER,\"msctls_updown32\",UDS_SETBUDDYINT | UDS_AUTOBUDDY | UDS_ARROWKEYS,25,94,11,14\n    EDITTEXT        IDC_USAGE_END,58,94,19,14,ES_AUTOHSCROLL | ES_NUMBER\n    CONTROL         \"\",IDC_USAGE_END_SPINNER,\"msctls_updown32\",UDS_SETBUDDYINT | UDS_AUTOBUDDY | UDS_ARROWKEYS,77,94,11,14\n    LTEXT           \"% to\",IDC_STATIC,39,97,16,8\n    CONTROL         \"&Device:\",IDC_DEVID,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,104,6,34,10\n    COMBOBOX        IDC_DEVID_COMBO,141,6,155,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP\n    LTEXT           \"%\",IDC_STATIC,91,97,8,8\n    CONTROL         \"Device &range:\",IDC_DRANGE,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,104,19,59,10\n    EDITTEXT        IDC_DRANGE_END,159,32,40,14,ES_AUTOHSCROLL | ES_NUMBER\n    LTEXT           \"to\",IDC_STATIC,148,34,8,8\n    EDITTEXT        IDC_DRANGE_START,104,32,40,14,ES_AUTOHSCROLL | ES_NUMBER\n    CONTROL         \"&Virtual range:\",IDC_VRANGE,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,104,49,59,10\n    EDITTEXT        IDC_VRANGE_END,160,62,40,14,ES_AUTOHSCROLL | ES_NUMBER\n    LTEXT           \"to\",IDC_STATIC,148,64,8,8\n    EDITTEXT        IDC_VRANGE_START,104,62,40,14,ES_AUTOHSCROLL | ES_NUMBER\n    CONTROL         \"&Limit:\",IDC_LIMIT,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,104,80,33,10\n    EDITTEXT        IDC_LIMIT_START,104,94,19,14,ES_AUTOHSCROLL | ES_NUMBER\n    CONTROL         \"\",IDC_LIMIT_START_SPINNER,\"msctls_updown32\",UDS_SETBUDDYINT | UDS_AUTOBUDDY | UDS_ARROWKEYS,123,94,11,14\n    EDITTEXT        IDC_LIMIT_END,150,94,19,14,ES_AUTOHSCROLL | ES_NUMBER\n    CONTROL         \"\",IDC_LIMIT_END_SPINNER,\"msctls_updown32\",UDS_SETBUDDYINT | UDS_AUTOBUDDY | UDS_ARROWKEYS,169,94,11,14\n    LTEXT           \"to\",IDC_STATIC,139,97,8,8\n    CONTROL         \"&Stripes:\",IDC_STRIPES,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,210,19,40,10\n    EDITTEXT        IDC_STRIPES_START,210,32,19,14,ES_AUTOHSCROLL | ES_NUMBER\n    CONTROL         \"\",IDC_STRIPES_START_SPINNER,\"msctls_updown32\",UDS_SETBUDDYINT | UDS_AUTOBUDDY | UDS_ARROWKEYS,228,32,11,14\n    EDITTEXT        IDC_STRIPES_END,253,32,19,14,ES_AUTOHSCROLL | ES_NUMBER\n    CONTROL         \"\",IDC_STRIPES_END_SPINNER,\"msctls_updown32\",UDS_SETBUDDYINT | UDS_AUTOBUDDY | UDS_ARROWKEYS,272,32,11,14\n    LTEXT           \"to\",IDC_STATIC,242,35,8,8\n    CONTROL         \"&Convert:\",IDC_CONVERT,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,210,49,36,10\n    COMBOBOX        IDC_CONVERT_COMBO,248,49,48,30,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP\n    CONTROL         \"So&ft\",IDC_SOFT,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,210,64,28,10\nEND\n\nIDD_BALANCE DIALOGEX 0, 0, 254, 167\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Balance\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    DEFPUSHBUTTON   \"OK\",IDOK,102,146,50,14\n    CONTROL         \"\",IDC_BALANCE_PROGRESS,\"msctls_progress32\",WS_BORDER,7,95,240,14\n    CONTROL         \"&Data\",IDC_DATA,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,7,7,30,10\n    CONTROL         \"&Metadata\",IDC_METADATA,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,7,28,45,10\n    CONTROL         \"&System\",IDC_SYSTEM,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,7,51,38,10\n    PUSHBUTTON      \"Options...\",IDC_DATA_OPTIONS,70,6,50,14\n    PUSHBUTTON      \"Options...\",IDC_METADATA_OPTIONS,70,26,50,14\n    PUSHBUTTON      \"Options...\",IDC_SYSTEM_OPTIONS,70,47,50,14\n    LTEXT           \"Status\",IDC_BALANCE_STATUS,8,80,239,8\n    PUSHBUTTON      \"&Start balance\",IDC_START_BALANCE,13,117,69,14\n    PUSHBUTTON      \"&Pause / resume\",IDC_PAUSE_BALANCE,93,117,69,14\n    PUSHBUTTON      \"&Cancel balance\",IDC_CANCEL_BALANCE,173,117,69,14\nEND\n\nIDD_DEVICES DIALOGEX 0, 0, 318, 203\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Devices\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    DEFPUSHBUTTON   \"OK\",IDOK,207,182,50,14\n    PUSHBUTTON      \"Cancel\",IDCANCEL,261,182,50,14\n    CONTROL         \"\",IDC_DEVLIST,\"SysListView32\",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,7,7,304,144\n    PUSHBUTTON      \"&Add device...\",IDC_DEVICE_ADD,7,182,76,14\n    PUSHBUTTON      \"&Refresh\",IDC_DEVICE_REFRESH,7,159,76,14\n    PUSHBUTTON      \"Remove &device\",IDC_DEVICE_REMOVE,93,182,76,14\n    PUSHBUTTON      \"Show &stats...\",IDC_DEVICE_SHOW_STATS,235,159,76,14,WS_DISABLED\n    PUSHBUTTON      \"Re&size...\",IDC_DEVICE_RESIZE,149,159,76,14,WS_DISABLED\nEND\n\nIDD_DEVICE_ADD DIALOGEX 0, 0, 261, 185\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Add device\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    DEFPUSHBUTTON   \"OK\",IDOK,66,164,50,14\n    PUSHBUTTON      \"Cancel\",IDCANCEL,145,164,50,14\n    CONTROL         \"\",IDC_DEVICE_TREE,\"SysTreeView32\",TVS_HASBUTTONS | TVS_HASLINES | TVS_SHOWSELALWAYS | WS_BORDER | WS_HSCROLL | WS_TABSTOP,7,7,247,148\nEND\n\nIDD_SCRUB DIALOGEX 0, 0, 254, 162\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Scrub\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    DEFPUSHBUTTON   \"OK\",IDOK,102,141,50,14\n    CONTROL         \"\",IDC_SCRUB_PROGRESS,\"msctls_progress32\",WS_BORDER,7,95,240,14\n    LTEXT           \"Status\",IDC_SCRUB_STATUS,8,81,239,8\n    PUSHBUTTON      \"&Start scrub\",IDC_START_SCRUB,13,117,69,14\n    PUSHBUTTON      \"&Pause / resume\",IDC_PAUSE_SCRUB,93,117,69,14\n    PUSHBUTTON      \"&Cancel scrub\",IDC_CANCEL_SCRUB,173,117,69,14\n    EDITTEXT        IDC_SCRUB_INFO,7,7,240,69,ES_MULTILINE | ES_READONLY | WS_VSCROLL\nEND\n\nIDD_DEVICE_STATS DIALOGEX 0, 0, 159, 113\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Device stats\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    DEFPUSHBUTTON   \"OK\",IDOK,24,91,50,14\n    LTEXT           \"Device %llx:\",IDC_DEVICE_ID,7,7,40,8\n    LTEXT           \"Write errors:\",IDC_STATIC,7,21,79,8\n    LTEXT           \"Read errors:\",IDC_STATIC,7,34,79,8\n    LTEXT           \"Flush errors:\",IDC_STATIC,7,47,79,8\n    LTEXT           \"Corruption errors:\",IDC_STATIC,7,60,79,8\n    LTEXT           \"Generation errors:\",IDC_STATIC,7,73,79,8\n    RTEXT           \"%llu\",IDC_WRITE_ERRS,87,21,65,8\n    RTEXT           \"%llu\",IDC_READ_ERRS,87,34,65,8\n    RTEXT           \"%llu\",IDC_FLUSH_ERRS,87,47,65,8\n    RTEXT           \"%llu\",IDC_CORRUPTION_ERRS,87,60,65,8\n    RTEXT           \"%llu\",IDC_GENERATION_ERRS,87,73,65,8\n    PUSHBUTTON      \"&Reset\",IDC_RESET_STATS,85,91,50,14\nEND\n\nIDD_RECV_PROGRESS DIALOGEX 0, 0, 311, 83\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Receiving subvolume\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    PUSHBUTTON      \"Cancel\",IDCANCEL,130,62,50,14\n    CONTROL         \"\",IDC_RECV_PROGRESS,\"msctls_progress32\",WS_BORDER,7,33,297,24\n    LTEXT           \"Receiving subvolume...\",IDC_RECV_MSG,7,7,297,18\nEND\n\nIDD_SEND_SUBVOL DIALOGEX 0, 0, 288, 149\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Send subvolume\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    DEFPUSHBUTTON   \"&Write\",IDOK,83,128,50,14\n    PUSHBUTTON      \"&Close\",IDCANCEL,156,128,50,14\n    EDITTEXT        IDC_STREAM_DEST,57,7,166,14,ES_AUTOHSCROLL\n    LTEXT           \"Stream:\",IDC_STATIC,7,11,26,8\n    PUSHBUTTON      \"&Browse...\",IDC_BROWSE,231,7,50,14\n    LTEXT           \"Select a destination for the subvolume stream.\",IDC_SEND_STATUS,7,93,274,22\n    CONTROL         \"Incremental\",IDC_INCREMENTAL,\"Button\",BS_AUTOCHECKBOX | WS_TABSTOP,7,32,54,10\n    EDITTEXT        IDC_PARENT_SUBVOL,69,29,154,14,ES_AUTOHSCROLL | WS_DISABLED\n    PUSHBUTTON      \"&Browse...\",IDC_PARENT_BROWSE,231,29,50,14,WS_DISABLED\n    LTEXT           \"Clone sources:\",IDC_STATIC,7,52,46,8\n    LISTBOX         IDC_CLONE_LIST,69,50,154,36,LBS_NOINTEGRALHEIGHT | WS_VSCROLL | WS_TABSTOP\n    PUSHBUTTON      \"&Add...\",IDC_CLONE_ADD,231,50,50,14\n    PUSHBUTTON      \"&Remove\",IDC_CLONE_REMOVE,231,69,50,14,WS_DISABLED\nEND\n\nIDD_RESIZE DIALOGEX 0, 0, 279, 133\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Resize device\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    DEFPUSHBUTTON   \"OK\",IDOK,81,112,50,14\n    PUSHBUTTON      \"Cancel\",IDCANCEL,148,112,50,14\n    LTEXT           \"Device %llx:\",IDC_RESIZE_DEVICE_ID,18,21,238,8\n    LTEXT           \"Current size: %s\",IDC_RESIZE_CURSIZE,18,37,238,8\n    CONTROL         \"\",IDC_RESIZE_SLIDER,\"msctls_trackbar32\",TBS_BOTH | TBS_NOTICKS | WS_TABSTOP,7,74,265,24\n    LTEXT           \"New size: %s\",IDC_RESIZE_NEWSIZE,18,53,238,8\nEND\n\nIDD_DRIVE_LETTER DIALOGEX 0, 0, 131, 61\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Change drive letter\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    DEFPUSHBUTTON   \"OK\",IDOK,7,40,50,14\n    PUSHBUTTON      \"Cancel\",IDCANCEL,74,40,50,14\n    COMBOBOX        IDC_DRIVE_LETTER_COMBO,64,17,60,30,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP\n    LTEXT           \"Drive letter:\",IDC_STATIC,15,19,45,8\nEND\n\nIDD_MAPPINGS DIALOGEX 0, 0, 200, 163\nSTYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU\nCAPTION \"Mappings\"\nFONT 8, \"MS Shell Dlg\", 400, 0, 0x1\nBEGIN\n    DEFPUSHBUTTON   \"OK\",IDOK,75,145,50,14\n    CONTROL         \"\",IDC_MAPPINGS_TAB,\"SysTabControl32\",0x0,0,0,200,142\n    CONTROL         \"\",IDC_MAPPINGS_LIST,\"SysListView32\",LVS_REPORT | LVS_ALIGNLEFT | WS_BORDER | WS_TABSTOP,3,15,193,123\nEND\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// DESIGNINFO\n//\n\n#ifdef APSTUDIO_INVOKED\nGUIDELINES DESIGNINFO\nBEGIN\n    IDD_PROP_SHEET, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 228\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 266\n    END\n\n    IDD_SIZE_DETAILS, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 205\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 93\n    END\n\n    IDD_VOL_PROP_SHEET, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 228\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 266\n    END\n\n    IDD_VOL_USAGE, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 228\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 235\n    END\n\n    IDD_BALANCE_OPTIONS, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 296\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 131\n    END\n\n    IDD_BALANCE, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 247\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 160\n    END\n\n    IDD_DEVICES, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 311\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 196\n    END\n\n    IDD_DEVICE_ADD, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 254\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 178\n    END\n\n    IDD_SCRUB, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 247\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 155\n    END\n\n    IDD_DEVICE_STATS, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 152\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 105\n    END\n\n    IDD_RECV_PROGRESS, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 304\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 76\n    END\n\n    IDD_SEND_SUBVOL, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 281\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 142\n    END\n\n    IDD_RESIZE, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 272\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 126\n    END\n\n    IDD_DRIVE_LETTER, DIALOG\n    BEGIN\n        LEFTMARGIN, 7\n        RIGHTMARGIN, 124\n        TOPMARGIN, 7\n        BOTTOMMARGIN, 54\n    END\nEND\n#endif    // APSTUDIO_INVOKED\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// RT_MANIFEST\n//\n\n2                       RT_MANIFEST             \"@CMAKE_CURRENT_SOURCE_DIR@/src/shellext/shellbtrfs.manifest\"\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// AFX_DIALOG_LAYOUT\n//\n\nIDD_SIZE_DETAILS AFX_DIALOG_LAYOUT\nBEGIN\n    0\nEND\n\nIDD_PROP_SHEET AFX_DIALOG_LAYOUT\nBEGIN\n    0\nEND\n\nIDD_VOL_PROP_SHEET AFX_DIALOG_LAYOUT\nBEGIN\n    0\nEND\n\nIDD_DRIVE_LETTER AFX_DIALOG_LAYOUT\nBEGIN\n    0\nEND\n\nIDD_BALANCE_OPTIONS AFX_DIALOG_LAYOUT\nBEGIN\n    0\nEND\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// String Table\n//\n\nSTRINGTABLE\nBEGIN\n    IDS_NEW_SUBVOL_HELP_TEXT \"Creates a new Btrfs subvolume.\"\n    IDS_NEW_SUBVOL          \"&New subvolume\"\n    IDS_NEW_SUBVOL_FILENAME \"New subvolume\"\n    IDS_CREATE_SNAPSHOT     \"Create snapshot\"\n    IDS_CREATE_SNAPSHOT_HELP_TEXT \"Creates a snapshot of a Btrfs subvolume.\"\n    IDS_SNAPSHOT_FILENAME   \"Snapshot of %s (%04u-%02u-%02u)\"\n    IDS_PROP_SHEET_TITLE    \"Btrfs properties\"\n    IDS_INODE_FILE          \"File\"\n    IDS_INODE_DIR           \"Directory\"\n    IDS_INODE_CHAR          \"Character device (major %llu, minor %llu)\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_INODE_BLOCK         \"Block device (major %llu, minor %llu)\"\n    IDS_INODE_FIFO          \"FIFO\"\n    IDS_INODE_SOCKET        \"Socket\"\n    IDS_INODE_SYMLINK       \"Symbolic link\"\n    IDS_INODE_UNKNOWN       \"Unknown inode type %x\"\n    IDS_CANNOT_FIND_DEVICE  \"Cannot find device.\"\n    IDS_SIZE_BYTE           \"%s byte\"\n    IDS_SIZE_BYTES          \"%s bytes\"\n    IDS_SIZE_KB             \"%1.1f KB\"\n    IDS_SIZE_MB             \"%1.1f MB\"\n    IDS_SIZE_GB             \"%1.1f GB\"\n    IDS_SIZE_TB             \"%1.1f TB\"\n    IDS_SIZE_PB             \"%1.1f PB\"\n    IDS_SIZE_EB             \"%1.1f EB\"\n    IDS_VARIOUS             \"(various)\"\n    IDS_INODE_CHAR_SIMPLE   \"Character device\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_INODE_BLOCK_SIMPLE  \"Block device\"\n    IDS_VOL_PROP_SHEET_TITLE \"Btrfs\"\n    IDS_SIZE_LARGE          \"%s (%s)\"\n    IDS_SINGLE              \"single\"\n    IDS_DUP                 \"DUP\"\n    IDS_RAID0               \"RAID0\"\n    IDS_RAID1               \"RAID1\"\n    IDS_RAID10              \"RAID10\"\n    IDS_RAID5               \"RAID5\"\n    IDS_RAID6               \"RAID6\"\n    IDS_USAGE_DATA          \"Data, %s: size: %s, used: %s\"\n    IDS_USAGE_MIXED         \"Data / metadata, %s: size: %s, used: %s\"\n    IDS_USAGE_METADATA      \"Metadata, %s: size: %s, used: %s\"\n    IDS_USAGE_SYSTEM        \"System, %s: size: %s, used: %s\"\n    IDS_USAGE_UNALLOC       \"Unallocated:\"\n    IDS_UNKNOWN_DEVICE      \"(unknown device %llu)\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_USAGE_DEV_SIZE      \"Device size:\\t\\t%s\"\n    IDS_USAGE_DEV_ALLOC     \"Device allocated:\\t\\t%s\"\n    IDS_USAGE_DEV_UNALLOC   \"Device unallocated:\\t\\t%s\"\n    IDS_USAGE_DATA_RATIO    \"Data ratio:\\t\\t%1.2f\"\n    IDS_USAGE_METADATA_RATIO \"Metadata ratio:\\t\\t%1.2f\"\n    IDS_NO_BALANCE          \"No balance is currently running.\"\n    IDS_SINGLE2             \"Single\"\n    IDS_DEVID_LIST          \"%llu: %s\"\n    IDS_BALANCE_RUNNING     \"Balance is currently running (%llu out of %llu chunks processed, %1.1f%%)\"\n    IDS_DRANGE_END_BEFORE_START \"Device range end is before start.\"\n    IDS_VRANGE_END_BEFORE_START \"Virtual range end is before start.\"\n    IDS_LIMIT_END_BEFORE_START \"Limit end is before start.\"\n    IDS_STRIPES_END_BEFORE_START \"Stripes end is before start.\"\n    IDS_USAGE_END_BEFORE_START \"Usage end is before start.\"\n    IDS_ERROR               \"Error\"\n    IDS_BALANCE_COMPLETE    \"Balance completed successfully.\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_BALANCE_PAUSED      \"Balance is currently paused (%llu out of %llu chunks processed, %1.1f%%)\"\n    IDS_BALANCE_CANCELLED   \"Balance cancelled.\"\n    IDS_DEVLIST_ID          \"ID\"\n    IDS_DEVLIST_DESC        \"Description\"\n    IDS_DEVLIST_READONLY    \"Read-only\"\n    IDS_DEVLIST_SIZE        \"Size\"\n    IDS_DEVLIST_READONLY_YES \"Yes\"\n    IDS_DEVLIST_READONLY_NO \"No\"\n    IDS_DEVLIST_ALLOC       \"Allocated\"\n    IDS_DEVLIST_ALLOC_PC    \"%\"\n    IDS_BALANCE_RUNNING_REMOVAL\n                            \"Currently removing device %llu (%llu out of %llu chunks processed, %1.1f%%)\"\n    IDS_BALANCE_PAUSED_REMOVAL\n                            \"Removal of device %llu paused (%llu out of %llu chunks processed, %1.1f%%)\"\n    IDS_BALANCE_CANCELLED_REMOVAL \"Device removal cancelled.\"\n    IDS_BALANCE_COMPLETE_REMOVAL \"Device removal completed successfully.\"\n    IDS_PARTITION           \"Partition %u\"\n    IDS_WHOLE_DISK          \"Whole disk\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_CANNOT_REMOVE_RAID  \"The current RAID levels do not allow this device to be removed. You must do a conversion balance before you will be able to proceed.\"\n    IDS_REMOVE_DEVICE_CONFIRMATION\n                            \"Are you sure that you want to remove device %s, %s?\"\n    IDS_CONFIRMATION_TITLE  \"Confirmation\"\n    IDS_ADD_DEVICE_CONFIRMATION\n                            \"Are you sure that you want to add this device?\"\n    IDS_ADD_DEVICE_CONFIRMATION_FS\n                            \"Are you sure that you want to add this device? It already appears to contain a filesystem (%s).\"\n    IDS_BALANCE_FAILED      \"Balance failed (error %08x, %s)\"\n    IDS_BALANCE_FAILED_REMOVAL \"Device removal failed (error %08x, %s)\"\n    IDS_DISK_NUM            \"Disk %u\"\n    IDS_DISK_PART_NUM       \"Disk %u, partition %u\"\n    IDS_NO_SCRUB            \"Scrub not running.\"\n    IDS_SCRUB_RUNNING       \"Scrub currently running (%llu out of %llu chunks processed, %1.1f%%)\"\n    IDS_SCRUB_FINISHED      \"Scrub finished.\"\n    IDS_SCRUB_PAUSED        \"Scrub paused (%llu out of %llu chunks processed, %1.1f%%)\"\n    IDS_SCRUB_MSG_STARTED   \"Scrub started at %s %s.\"\n    IDS_SCRUB_MSG_RECOVERABLE_DATA\n                            \"Recovered from data checksum error at %llx on device %llx.\"\n    IDS_SCRUB_MSG_RECOVERABLE_METADATA\n                            \"Recovered from metadata checksum error at %llx on device %llx.\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_SCRUB_MSG_UNRECOVERABLE_DATA\n                            \"Unrecoverable data checksum error at %llx on device %llx (%.*s, offset %llx)\"\n    IDS_SCRUB_MSG_UNRECOVERABLE_DATA_SUBVOL\n                            \"Unrecoverable data checksum error at %llx on device %llx (subvol %llx, %.*s, offset %llx)\"\n    IDS_SCRUB_MSG_UNRECOVERABLE_METADATA\n                            \"Unrecoverable metadata checksum error at %llx on device %llx (root %llx, level %x)\"\n    IDS_SCRUB_MSG_UNRECOVERABLE_METADATA_FIRSTITEM\n                            \"Unrecoverable metadata checksum error at %llx on device %llx (root %llx, level %x, first item %llx,%x,%llx)\"\n    IDS_SCRUB_MSG_FINISHED  \"Scrub finished at %s %s.\"\n    IDS_SCRUB_MSG_SUMMARY   \"Scrubbed %s in %s (%s/s).\"\n    IDS_BALANCE_SCRUB_RUNNING \"Cannot start balance while scrub running.\"\n    IDS_SCRUB_BALANCE_RUNNING \"Cannot start scrub while balance running.\"\n    IDS_SCRUB_MSG_SUMMARY_ERRORS_RECOVERABLE \"Recovered from %llu error(s).\"\n    IDS_SCRUB_MSG_SUMMARY_ERRORS_UNRECOVERABLE\n                            \"%llu unrecoverable error(s) found.\"\n    IDS_SCRUB_FAILED        \"Scrub failed with error %08x.\"\n    IDS_LOCK_FAILED         \"Unable to lock volume: error %08x. Make sure that there are no files open, and that you have closed any Explorer windows.\"\n    IDS_SCRUB_MSG_RECOVERABLE_PARITY\n                            \"Recovered from parity error at %llx on device %llx.\"\n    IDS_COMPRESS_ANY        \"(any)\"\n    IDS_COMPRESS_ZLIB       \"Zlib\"\n    IDS_COMPRESS_LZO        \"LZO\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_STANDALONE_PROPSHEET_TITLE \"Inode property sheet\"\n    IDS_REFLINK_PASTE       \"Ref&link Paste\"\n    IDS_REFLINK_PASTE_HELP  \"Do a lightweight copy of files using reference counting.\"\n    IDS_RECV_SUBVOL         \"Re&ceive subvolume...\"\n    IDS_RECV_SUBVOL_HELP    \"Recreate a previously exported subvolume.\"\n    IDS_RECV_CANT_OPEN_FILE \"%S: Couldn't open %s (error %u, %s).\"\n    IDS_RECV_READFILE_FAILED \"ReadFile failed (error %u, %s).\"\n    IDS_OUT_OF_MEMORY       \"Out of memory.\"\n    IDS_RECV_UNKNOWN_COMMAND \"Unrecognized command %u encountered.\"\n    IDS_RECV_CANT_OPEN_PATH \"Couldn't open path %s (error %u, %s).\"\n    IDS_RAID1C3             \"RAID1C3\"\n    IDS_RECV_CREATE_SUBVOL_FAILED\n                            \"FSCTL_BTRFS_CREATE_SUBVOL returned %08x (%s).\"\n    IDS_RECV_MISSING_PARAM  \"%S: could not find %s parameter.\"\n    IDS_RECV_SHORT_PARAM    \"%S: length of parameter %s was %u, expected %u.\"\n    IDS_RECV_MKNOD_FAILED   \"FSCTL_BTRFS_MKNOD returned %08x (%s).\"\n    IDS_RECV_SET_REPARSE_POINT_FAILED\n                            \"FSCTL_SET_REPARSE_POINT returned %08x (%s).\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_RECV_MOVEFILE_FAILED \"MoveFile (%s -> %s) failed (error %u, %s).\"\n    IDS_RECV_SETFILEPOINTER_FAILED \"SetFilePointer failed (error %u, %s).\"\n    IDS_RECV_WRITEFILE_FAILED \"WriteFile failed (error %u, %s).\"\n    IDS_RECV_CREATEHARDLINK_FAILED\n                            \"CreateHardLink (%s -> %s) failed (error %u, %s).\"\n    IDS_RECV_SETENDOFFILE_FAILED \"SetEndOfFile failed (error %u, %s).\"\n    IDS_RECV_CANT_CREATE_FILE \"Couldn't create %s (error %u, %s).\"\n    IDS_RAID1C4             \"RAID1C4\"\n    IDS_RECV_SETINODEINFO_FAILED\n                            \"FSCTL_BTRFS_SET_INODE_INFO returned %08x (%s).\"\n    IDS_RECV_SUCCESS        \"Received 1 subvolume successfully.\"\n    IDS_RECV_BUTTON_OK      \"OK\"\n    IDS_RECV_SETFILEATTRIBUTES_FAILED\n                            \"SetFileAttributes failed (error %u, %s).\"\n    IDS_RECV_GETFILEATTRIBUTES_FAILED\n                            \"GetFileAttributes failed (error %u, %s).\"\n    IDS_RECV_CSUM_ERROR     \"Checksum error.\"\n    IDS_RECV_NOT_A_SEND_STREAM \"File was not a send stream.\"\n    IDS_RECV_UNSUPPORTED_VERSION \"Unsupported version %u.\"\n    IDS_RECV_SETEAFILE_FAILED \"NtSetEaFile returned %08x (%s).\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_RECV_RECEIVED_SUBVOL_FAILED\n                            \"FSCTL_BTRFS_RECEIVED_SUBVOL returned %08x (%s).\"\n    IDS_RECV_SETSECURITYOBJECT_FAILED\n                            \"NtSetSecurityObject returned %08x (%s).\"\n    IDS_RECV_SETXATTR_FAILED \"FSCTL_BTRFS_SET_XATTR returned %08x (%s).\"\n    IDS_RECV_CREATETHREAD_FAILED \"CreateThread failed (error %u, %s).\"\n    IDS_RECV_FILE_TRUNCATED \"File was truncated.\"\n    IDS_RECV_RESERVE_SUBVOL_FAILED\n                            \"FSCTL_BTRFS_RESERVE_SUBVOL returned %08x (%s).\"\n    IDS_RECV_CANCELLED      \"Receiving cancelled.\"\n    IDS_RECV_CANT_FIND_PARENT_SUBVOL \"Could not find parent subvolume.\"\n    IDS_RECV_FIND_SUBVOL_FAILED \"FSCTL_BTRFS_FIND_SUBVOL returned %08x (%s).\"\n    IDS_RECV_CREATE_SNAPSHOT_FAILED\n                            \"FSCTL_BTRFS_CREATE_SNAPSHOT returned %08x (%s).\"\n    IDS_RECV_GETVOLUMEPATHNAME_FAILED\n                            \"GetVolumePathName failed (error %u, %s).\"\n    IDS_RECV_DELETEFILE_FAILED \"DeleteFile failed for %s (error %u, %s).\"\n    IDS_RECV_REMOVEDIRECTORY_FAILED\n                            \"RemoveDirectory failed for %s (error %u, %s).\"\n    IDS_RECV_CANT_FIND_CLONE_SUBVOL \"Could not find clone subvolume.\"\n    IDS_RECV_GETFILESIZEEX_FAILED \"GetFileSizeEx failed (error %u, %s).\"\n    IDS_RECV_DUPLICATE_EXTENTS_FAILED\n                            \"FSCTL_DUPLICATE_EXTENTS_TO_FILE returned %08x (%s).\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_RECV_SUCCESS_PLURAL \"Received %u subvolumes successfully.\"\n    IDS_SEND_SUBVOL         \"&Send subvolume...\"\n    IDS_SEND_SUBVOL_HELP    \"Exports a subvolume so that it can be recreated on another volume.\"\n    IDS_SEND_CANT_OPEN_FILE \"Error opening file %s (error %u, %s).\"\n    IDS_SEND_CANT_OPEN_DIR  \"Error opening directory %s (error %u, %s).\"\n    IDS_SEND_FSCTL_BTRFS_SEND_SUBVOL_FAILED\n                            \"FSCTL_BTRFS_SEND_SUBVOL returned error %08x (%s).\"\n    IDS_SEND_FSCTL_BTRFS_READ_SEND_BUFFER_FAILED\n                            \"FSCTL_BTRFS_READ_SEND_BUFFER returned error %08x (%s).\"\n    IDS_SEND_SUCCESS        \"Stream written successfully.\"\n    IDS_SEND_WRITEFILE_FAILED \"Writing to file failed (error %u, %s).\"\n    IDS_SEND_GET_FILE_INFO_FAILED\n                            \"GetFileInformationByHandle failed (error %u, %s).\"\n    IDS_SEND_NOT_READONLY   \"Subvolume not readonly.\"\n    IDS_NOT_SUBVOL          \"Directory was not a subvolume.\"\n    IDS_GET_FILE_IDS_FAILED \"FSCTL_BTRFS_GET_FILE_IDS returned error %08x (%s).\"\n    IDS_SHPARSEDISPLAYNAME_FAILED \"SHParseDisplayName failed.\"\n    IDS_SHGETPATHFROMIDLIST_FAILED \"SHGetPathFromIDList failed.\"\n    IDS_SEND_PARENT_NOT_READONLY \"Parent subvolume not readonly.\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_SEND_CANCEL         \"&Cancel\"\n    IDS_SEND_WRITING        \"Writing...\"\n    IDS_MISSING             \"(missing)\"\n    IDS_RESIZE_SUCCESSFUL   \"Device %llx successfully resized to %s.\"\n    IDS_BALANCE_RUNNING_SHRINK\n                            \"Currently shrinking device %llu (%llu out of %llu chunks processed, %1.1f%%)\"\n    IDS_BALANCE_PAUSED_SHRINK\n                            \"Shrinking of device %llu paused (%llu out of %llu chunks processed, %1.1f%%)\"\n    IDS_BALANCE_CANCELLED_SHRINK \"Device shrinking cancelled.\"\n    IDS_BALANCE_COMPLETE_SHRINK \"Device successfully shrunk.\"\n    IDS_BALANCE_FAILED_SHRINK \"Device shrinking failed (error %08x, %s)\"\n    IDS_COMPRESS_ZSTD       \"Zstd\"\n    IDS_REGCREATEKEY_FAILED \"RegCreateKey returned %08x\"\n    IDS_REGSETVALUEEX_FAILED \"RegSetValueEx returned %08x\"\n    IDS_REGCLOSEKEY_FAILED  \"RegCloseKey returned %08x\"\n    IDS_CANT_REFLINK_DIFFERENT_FS\n                            \"Cannot create a reflink between two different filesystems.\"\nEND\n\nSTRINGTABLE\nBEGIN\n    IDS_INITCOMMONCONTROLSEX_FAILED \"InitCommonControlsEx failed.\"\n    IDS_CANT_OPEN_MOUNTMGR  \"Could not get a handle to mount manager.\"\n    IDS_TVM_INSERTITEM_FAILED \"TVM_INSERTITEM failed.\"\n    IDS_RECV_PATH_TOO_LONG  \"%S: path was too long.\"\n    IDS_MAPPINGS_UID_MAPPINGS  \"UID mappings\"\n    IDS_MAPPINGS_GID_MAPPINGS  \"GID mappings\"\n    IDS_MAPPINGS_PRINCIPAL  \"Principal\"\n    IDS_MAPPINGS_UID  \"UID\"\n    IDS_MAPPINGS_GID  \"GID\"\nEND\n\n#endif    // English (United Kingdom) resources\n/////////////////////////////////////////////////////////////////////////////\n\n\n\n#ifndef APSTUDIO_INVOKED\n/////////////////////////////////////////////////////////////////////////////\n//\n// Generated from the TEXTINCLUDE 3 resource.\n//\n\n\n/////////////////////////////////////////////////////////////////////////////\n#endif    // not APSTUDIO_INVOKED\n\n"
  },
  {
    "path": "src/shellext/shellext.h",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\n#define ISOLATION_AWARE_ENABLED 1\n#define STRSAFE_NO_DEPRECATE\n\n#define WINVER 0x0A00 // Windows 10\n#define _WIN32_WINNT 0x0A00\n\n#include <windows.h>\n#include <winternl.h>\n#include <string>\n#include <vector>\n#include <stdint.h>\n#include \"../btrfs.h\"\n#include \"../btrfsioctl.h\"\n\nusing namespace std;\n\n#define STATUS_SUCCESS                  (NTSTATUS)0x00000000\n#define STATUS_BUFFER_OVERFLOW          (NTSTATUS)0x80000005\n#define STATUS_END_OF_FILE              (NTSTATUS)0xc0000011\n#define STATUS_MORE_PROCESSING_REQUIRED (NTSTATUS)0xc0000016\n#define STATUS_BUFFER_TOO_SMALL         (NTSTATUS)0xc0000023\n#define STATUS_DEVICE_NOT_READY         (NTSTATUS)0xc00000a3\n#define STATUS_CANNOT_DELETE            (NTSTATUS)0xc0000121\n#define STATUS_NOT_FOUND                (NTSTATUS)0xc0000225\n\n#define BLOCK_FLAG_DATA         0x001\n#define BLOCK_FLAG_SYSTEM       0x002\n#define BLOCK_FLAG_METADATA     0x004\n#define BLOCK_FLAG_RAID0        0x008\n#define BLOCK_FLAG_RAID1        0x010\n#define BLOCK_FLAG_DUPLICATE    0x020\n#define BLOCK_FLAG_RAID10       0x040\n#define BLOCK_FLAG_RAID5        0x080\n#define BLOCK_FLAG_RAID6        0x100\n\n#define BTRFS_TYPE_FILE      1\n#define BTRFS_TYPE_DIRECTORY 2\n#define BTRFS_TYPE_CHARDEV   3\n#define BTRFS_TYPE_BLOCKDEV  4\n#define BTRFS_TYPE_FIFO      5\n#define BTRFS_TYPE_SOCKET    6\n#define BTRFS_TYPE_SYMLINK   7\n\n#ifdef _MSC_VER\n#define funcname __FUNCTION__\n#else\n#define funcname __func__\n#endif\n\n#ifdef _MSC_VER\n#pragma warning(disable: 4800)\n#endif\n\nextern \"C\" {\nNTSTATUS NTAPI NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer,\n                          ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key);\n\nNTSTATUS WINAPI NtSetEaFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer, ULONG Length);\n\nNTSTATUS WINAPI NtSetSecurityObject(HANDLE Handle, SECURITY_INFORMATION SecurityInformation, PSECURITY_DESCRIPTOR SecurityDescriptor);\n\n#ifdef _MSC_VER\nNTSYSCALLAPI NTSTATUS NTAPI NtFsControlFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext,\n                                            PIO_STATUS_BLOCK IoStatusBlock, ULONG FsControlCode, PVOID InputBuffer, ULONG InputBufferLength,\n                                            PVOID OutputBuffer, ULONG OutputBufferLength);\nNTSTATUS NTAPI NtQueryInformationFile(HANDLE hFile, PIO_STATUS_BLOCK io, PVOID ptr, ULONG len, FILE_INFORMATION_CLASS FileInformationClass);\n\nNTSTATUS NTAPI NtSetInformationFile(HANDLE hFile, PIO_STATUS_BLOCK io, PVOID ptr, ULONG len, FILE_INFORMATION_CLASS FileInformationClass);\n\n#define FileBasicInformation (FILE_INFORMATION_CLASS)4\n#define FileStandardInformation (FILE_INFORMATION_CLASS)5\n#define FileDispositionInformation (FILE_INFORMATION_CLASS)13\n#define FileEndOfFileInformation (FILE_INFORMATION_CLASS)20\n#define FileStreamInformation (FILE_INFORMATION_CLASS)22\n\ntypedef enum _FSINFOCLASS {\n    FileFsVolumeInformation = 1,\n    FileFsLabelInformation,\n    FileFsSizeInformation,\n    FileFsDeviceInformation,\n    FileFsAttributeInformation,\n    FileFsControlInformation,\n    FileFsFullSizeInformation,\n    FileFsObjectIdInformation,\n    FileFsDriverPathInformation,\n    FileFsVolumeFlagsInformation,\n    FileFsSectorSizeInformation,\n    FileFsDataCopyInformation,\n    FileFsMetadataSizeInformation,\n    FileFsFullSizeInformationEx,\n    FileFsMaximumInformation\n} FS_INFORMATION_CLASS, *PFS_INFORMATION_CLASS;\n\ntypedef struct _FILE_STREAM_INFORMATION {\n    ULONG NextEntryOffset;\n    ULONG StreamNameLength;\n    LARGE_INTEGER StreamSize;\n    LARGE_INTEGER StreamAllocationSize;\n    WCHAR StreamName[1];\n} FILE_STREAM_INFORMATION, *PFILE_STREAM_INFORMATION;\n#endif\n\nNTSTATUS NTAPI NtQueryVolumeInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID FsInformation, ULONG Length,\n                                            FS_INFORMATION_CLASS FsInformationClass);\n}\n\ntypedef struct _REPARSE_DATA_BUFFER {\n    ULONG ReparseTag;\n    USHORT ReparseDataLength;\n    USHORT Reserved;\n\n    union {\n        struct {\n            USHORT SubstituteNameOffset;\n            USHORT SubstituteNameLength;\n            USHORT PrintNameOffset;\n            USHORT PrintNameLength;\n            ULONG Flags;\n            WCHAR PathBuffer[1];\n        } SymbolicLinkReparseBuffer;\n\n        struct {\n            USHORT SubstituteNameOffset;\n            USHORT SubstituteNameLength;\n            USHORT PrintNameOffset;\n            USHORT PrintNameLength;\n            WCHAR PathBuffer[1];\n        } MountPointReparseBuffer;\n\n        struct {\n            UCHAR DataBuffer[1];\n        } GenericReparseBuffer;\n    };\n} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;\n\n#define SYMLINK_FLAG_RELATIVE 1\n\n#ifndef _MSC_VER\n\ntypedef struct _DUPLICATE_EXTENTS_DATA {\n    HANDLE FileHandle;\n    LARGE_INTEGER SourceFileOffset;\n    LARGE_INTEGER TargetFileOffset;\n    LARGE_INTEGER ByteCount;\n} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA;\n\ntypedef struct _FSCTL_GET_INTEGRITY_INFORMATION_BUFFER {\n    WORD ChecksumAlgorithm;\n    WORD Reserved;\n    DWORD Flags;\n    DWORD ChecksumChunkSizeInBytes;\n    DWORD ClusterSizeInBytes;\n} FSCTL_GET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_GET_INTEGRITY_INFORMATION_BUFFER;\n\ntypedef struct _FSCTL_SET_INTEGRITY_INFORMATION_BUFFER {\n    WORD ChecksumAlgorithm;\n    WORD Reserved;\n    DWORD Flags;\n} FSCTL_SET_INTEGRITY_INFORMATION_BUFFER, *PFSCTL_SET_INTEGRITY_INFORMATION_BUFFER;\n\n#endif\n\nclass win_handle {\npublic:\n    win_handle() {\n    }\n\n    win_handle(HANDLE nh) {\n        h = nh;\n    }\n\n    ~win_handle() {\n        if (h != INVALID_HANDLE_VALUE)\n            CloseHandle(h);\n    }\n\n    operator HANDLE() const {\n        return h;\n    }\n\n    win_handle& operator=(const HANDLE nh) {\n        if (h != INVALID_HANDLE_VALUE)\n            CloseHandle(h);\n\n        h = nh;\n\n        return *this;\n    }\n\n    HANDLE* operator&() {\n        return &h;\n    }\n\nprivate:\n    HANDLE h = INVALID_HANDLE_VALUE;\n};\n\nclass fff_handle {\npublic:\n    fff_handle() {\n    }\n\n    fff_handle(HANDLE nh) {\n        h = nh;\n    }\n\n    ~fff_handle() {\n        if (h != INVALID_HANDLE_VALUE)\n            FindClose(h);\n    }\n\n    operator HANDLE() const {\n        return h;\n    }\n\n    fff_handle& operator=(const HANDLE nh) {\n        if (h != INVALID_HANDLE_VALUE)\n            FindClose(h);\n\n        h = nh;\n\n        return *this;\n    }\n\n    HANDLE* operator&() {\n        return &h;\n    }\n\nprivate:\n    HANDLE h = INVALID_HANDLE_VALUE;\n};\n\nclass nt_handle {\npublic:\n    nt_handle() {\n    }\n\n    nt_handle(HANDLE nh) {\n        h = nh;\n    }\n\n    ~nt_handle() {\n        if (h != INVALID_HANDLE_VALUE)\n            NtClose(h);\n    }\n\n    operator HANDLE() const {\n        return h;\n    }\n\n    nt_handle& operator=(const HANDLE nh) {\n        if (h != INVALID_HANDLE_VALUE)\n            NtClose(h);\n\n        h = nh;\n\n        return *this;\n    }\n\n    HANDLE* operator&() {\n        return &h;\n    }\n\nprivate:\n    HANDLE h = INVALID_HANDLE_VALUE;\n};\n\nclass string_error : public exception {\npublic:\n    string_error(int resno, ...);\n\n    const char* what() const noexcept {\n        return msg.c_str();\n    }\n\nprivate:\n    string msg;\n};\n\n\nclass last_error : public exception {\npublic:\n    last_error(DWORD errnum);\n\n    const char* what() const noexcept {\n        return msg.c_str();\n    }\n\nprivate:\n    string msg;\n};\n\nclass ntstatus_error : public exception {\npublic:\n    ntstatus_error(NTSTATUS Status);\n\n    const char* what() const noexcept {\n        return msg.c_str();\n    }\n\n    NTSTATUS Status;\n\nprivate:\n    string msg;\n};\n\nclass global_lock {\npublic:\n    global_lock(HGLOBAL mem) : mem(mem) {\n        ptr = GlobalLock(mem);\n    }\n\n    ~global_lock() {\n        if (ptr)\n            GlobalUnlock(mem);\n    }\n\n    void* ptr;\n\nprivate:\n    HGLOBAL mem;\n};\n\nextern HMODULE module;\nvoid format_size(uint64_t size, wstring& s, bool show_bytes);\nvoid set_dpi_aware();\nwstring format_message(ULONG last_error);\nwstring format_ntstatus(NTSTATUS Status);\nbool load_string(HMODULE module, UINT id, wstring& s);\nvoid wstring_sprintf(wstring& s, wstring fmt, ...);\nvoid command_line_to_args(LPWSTR cmdline, vector<wstring>& args);\nwstring utf8_to_utf16(string_view utf8);\nvoid error_message(HWND hwnd, const char* msg);\n"
  },
  {
    "path": "src/shellext/volpropsheet.cpp",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#define ISOLATION_AWARE_ENABLED 1\n#define STRSAFE_NO_DEPRECATE\n\n#include \"shellext.h\"\n#include <windows.h>\n#include <strsafe.h>\n#include <winternl.h>\n\n#define NO_SHLWAPI_STRFCNS\n#include <shlwapi.h>\n#include <uxtheme.h>\n\n#include \"volpropsheet.h\"\n#include \"resource.h\"\n#include \"mountmgr.h\"\n\nstatic const NTSTATUS STATUS_OBJECT_NAME_NOT_FOUND = 0xC0000034;\n\nHRESULT __stdcall BtrfsVolPropSheet::QueryInterface(REFIID riid, void **ppObj) {\n    if (riid == IID_IUnknown || riid == IID_IShellPropSheetExt) {\n        *ppObj = static_cast<IShellPropSheetExt*>(this);\n        AddRef();\n        return S_OK;\n    } else if (riid == IID_IShellExtInit) {\n        *ppObj = static_cast<IShellExtInit*>(this);\n        AddRef();\n        return S_OK;\n    }\n\n    *ppObj = nullptr;\n    return E_NOINTERFACE;\n}\n\nHRESULT __stdcall BtrfsVolPropSheet::Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY) {\n    ULONG num_files;\n    FORMATETC format = { CF_HDROP, nullptr, DVASPECT_CONTENT, -1, TYMED_HGLOBAL };\n    WCHAR fnbuf[MAX_PATH];\n\n    if (pidlFolder)\n        return E_FAIL;\n\n    if (!pdtobj)\n        return E_FAIL;\n\n    stgm.tymed = TYMED_HGLOBAL;\n\n    if (FAILED(pdtobj->GetData(&format, &stgm)))\n        return E_INVALIDARG;\n\n    stgm_set = true;\n\n    global_lock gl(stgm.hGlobal);\n\n    if (!gl.ptr) {\n        ReleaseStgMedium(&stgm);\n        stgm_set = false;\n        return E_INVALIDARG;\n    }\n\n    num_files = DragQueryFileW((HDROP)stgm.hGlobal, 0xFFFFFFFF, nullptr, 0);\n\n    if (num_files > 1)\n        return E_FAIL;\n\n    if (DragQueryFileW((HDROP)stgm.hGlobal, 0, fnbuf, sizeof(fnbuf) / sizeof(WCHAR))) {\n        fn = fnbuf;\n\n        win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                   OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n        if (h != INVALID_HANDLE_VALUE) {\n            NTSTATUS Status;\n            IO_STATUS_BLOCK iosb;\n            ULONG devsize, i;\n\n            i = 0;\n            devsize = 1024;\n\n            devices = (btrfs_device*)malloc(devsize);\n\n            while (true) {\n                Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_DEVICES, nullptr, 0, devices, devsize);\n                if (Status == STATUS_BUFFER_OVERFLOW) {\n                    if (i < 8) {\n                        devsize += 1024;\n\n                        free(devices);\n                        devices = (btrfs_device*)malloc(devsize);\n\n                        i++;\n                    } else\n                        return E_FAIL;\n                } else\n                    break;\n            }\n\n            if (!NT_SUCCESS(Status))\n                return E_FAIL;\n\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_UUID, nullptr, 0, &uuid, sizeof(BTRFS_UUID));\n            uuid_set = NT_SUCCESS(Status);\n\n            ignore = false;\n            balance = new BtrfsBalance(fn);\n        } else\n            return E_FAIL;\n    } else\n        return E_FAIL;\n\n    return S_OK;\n}\n\ntypedef struct {\n    uint64_t dev_id;\n    wstring name;\n    uint64_t alloc;\n    uint64_t size;\n} dev;\n\nvoid BtrfsVolPropSheet::FormatUsage(HWND, wstring& s, btrfs_usage* usage) {\n    uint8_t i, j;\n    uint64_t num_devs, dev_size, dev_alloc, data_size, data_alloc, metadata_size, metadata_alloc;\n    btrfs_device* bd;\n    vector<dev> devs;\n    btrfs_usage* bue;\n    wstring t, u, v;\n\n    static const uint64_t types[] = { BLOCK_FLAG_DATA, BLOCK_FLAG_DATA | BLOCK_FLAG_METADATA, BLOCK_FLAG_METADATA, BLOCK_FLAG_SYSTEM };\n    static const ULONG typestrings[] = { IDS_USAGE_DATA, IDS_USAGE_MIXED, IDS_USAGE_METADATA, IDS_USAGE_SYSTEM };\n    static const uint64_t duptypes[] = { 0, BLOCK_FLAG_DUPLICATE, BLOCK_FLAG_RAID0, BLOCK_FLAG_RAID1, BLOCK_FLAG_RAID10, BLOCK_FLAG_RAID5,\n                                         BLOCK_FLAG_RAID6, BLOCK_FLAG_RAID1C3, BLOCK_FLAG_RAID1C4 };\n    static const ULONG dupstrings[] = { IDS_SINGLE, IDS_DUP, IDS_RAID0, IDS_RAID1, IDS_RAID10, IDS_RAID5, IDS_RAID6, IDS_RAID1C3, IDS_RAID1C4 };\n\n    static const uint64_t raid_types = BLOCK_FLAG_DUPLICATE | BLOCK_FLAG_RAID0 | BLOCK_FLAG_RAID1 | BLOCK_FLAG_RAID10 | BLOCK_FLAG_RAID5 |\n                                       BLOCK_FLAG_RAID6 | BLOCK_FLAG_RAID1C3 | BLOCK_FLAG_RAID1C4;\n\n    s = L\"\";\n\n    num_devs = 0;\n    bd = devices;\n\n    while (true) {\n        num_devs++;\n\n        if (bd->next_entry > 0)\n            bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry);\n        else\n            break;\n    }\n\n    bd = devices;\n\n    dev_size = 0;\n\n    while (true) {\n        dev d;\n\n        if (bd->missing) {\n            if (!load_string(module, IDS_MISSING, d.name))\n                throw last_error(GetLastError());\n        } else if (bd->device_number == 0xffffffff)\n            d.name = wstring(bd->name, bd->namelen / sizeof(WCHAR));\n        else if (bd->partition_number == 0) {\n            if (!load_string(module, IDS_DISK_NUM, u))\n                throw last_error(GetLastError());\n\n            wstring_sprintf(d.name, u, bd->device_number);\n        } else {\n            if (!load_string(module, IDS_DISK_PART_NUM, u))\n                throw last_error(GetLastError());\n\n            wstring_sprintf(d.name, u, bd->device_number, bd->partition_number);\n        }\n\n        d.dev_id = bd->dev_id;\n        d.alloc = 0;\n        d.size = bd->size;\n\n        devs.push_back(d);\n\n        dev_size += bd->size;\n\n        if (bd->next_entry > 0)\n            bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry);\n        else\n            break;\n    }\n\n    dev_alloc = 0;\n    data_size = data_alloc = 0;\n    metadata_size = metadata_alloc = 0;\n\n    bue = usage;\n    while (true) {\n        for (uint64_t k = 0; k < bue->num_devices; k++) {\n            dev_alloc += bue->devices[k].alloc;\n\n            if (bue->type & BLOCK_FLAG_DATA) {\n                data_alloc += bue->devices[k].alloc;\n            }\n\n            if (bue->type & BLOCK_FLAG_METADATA) {\n                metadata_alloc += bue->devices[k].alloc;\n            }\n        }\n\n        if (bue->type & BLOCK_FLAG_DATA)\n            data_size += bue->size;\n\n        if (bue->type & BLOCK_FLAG_METADATA)\n            metadata_size += bue->size;\n\n        if (bue->next_entry > 0)\n            bue = (btrfs_usage*)((uint8_t*)bue + bue->next_entry);\n        else\n            break;\n    }\n\n    // device size\n\n    if (!load_string(module, IDS_USAGE_DEV_SIZE, u))\n        throw last_error(GetLastError());\n\n    format_size(dev_size, v, false);\n\n    wstring_sprintf(t, u, v.c_str());\n\n    s += t + L\"\\r\\n\";\n\n    // device allocated\n\n    if (!load_string(module, IDS_USAGE_DEV_ALLOC, u))\n        throw last_error(GetLastError());\n\n    format_size(dev_alloc, v, false);\n\n    wstring_sprintf(t, u, v.c_str());\n\n    s += t + L\"\\r\\n\"s;\n\n    // device unallocated\n\n    if (!load_string(module, IDS_USAGE_DEV_UNALLOC, u))\n        throw last_error(GetLastError());\n\n    format_size(dev_size - dev_alloc, v, false);\n\n    wstring_sprintf(t, u, v.c_str());\n\n    s += t + L\"\\r\\n\"s;\n\n    // data ratio\n\n    if (data_alloc > 0) {\n        if (!load_string(module, IDS_USAGE_DATA_RATIO, u))\n            throw last_error(GetLastError());\n\n        wstring_sprintf(t, u, (float)data_alloc / (float)data_size);\n\n        s += t + L\"\\r\\n\"s;\n    }\n\n    // metadata ratio\n\n    if (!load_string(module, IDS_USAGE_METADATA_RATIO, u))\n        throw last_error(GetLastError());\n\n    wstring_sprintf(t, u, (float)metadata_alloc / (float)metadata_size);\n\n    s += t + L\"\\r\\n\\r\\n\";\n\n    for (i = 0; i < sizeof(types) / sizeof(types[0]); i++) {\n        for (j = 0; j < sizeof(duptypes) / sizeof(duptypes[0]); j++) {\n            bue = usage;\n\n            while (true) {\n                if ((bue->type & types[i]) == types[i] && ((duptypes[j] == 0 && (bue->type & raid_types) == 0) || bue->type & duptypes[j])) {\n                    wstring sizestring, usedstring, typestring, dupstring;\n\n                    if (bue->type & BLOCK_FLAG_DATA && bue->type & BLOCK_FLAG_METADATA && (types[i] == BLOCK_FLAG_DATA || types[i] == BLOCK_FLAG_METADATA))\n                        break;\n\n                    if (!load_string(module, typestrings[i], typestring))\n                        throw last_error(GetLastError());\n\n                    if (!load_string(module, dupstrings[j], dupstring))\n                        throw last_error(GetLastError());\n\n                    format_size(bue->size, sizestring, false);\n                    format_size(bue->used, usedstring, false);\n\n                    wstring_sprintf(t, typestring, dupstring.c_str(), sizestring.c_str(), usedstring.c_str());\n\n                    s += t + L\"\\r\\n\";\n\n                    for (uint64_t k = 0; k < bue->num_devices; k++) {\n                        bool found = false;\n\n                        format_size(bue->devices[k].alloc, sizestring, false);\n\n                        for (size_t l = 0; l < min((uint64_t)SIZE_MAX, num_devs); l++) {\n                            if (devs[l].dev_id == bue->devices[k].dev_id) {\n                                s += devs[l].name + L\"\\t\" + sizestring + L\"\\r\\n\";\n\n                                devs[l].alloc += bue->devices[k].alloc;\n\n                                found = true;\n                                break;\n                            }\n                        }\n\n                        if (!found) {\n                            if (!load_string(module, IDS_UNKNOWN_DEVICE, typestring))\n                                throw last_error(GetLastError());\n\n                            wstring_sprintf(t, typestring, bue->devices[k].dev_id);\n\n                            s += t + L\"\\t\"s + sizestring + L\"\\r\\n\"s;\n                        }\n                    }\n\n                    s += L\"\\r\\n\";\n\n                    break;\n                }\n\n                if (bue->next_entry > 0)\n                    bue = (btrfs_usage*)((uint8_t*)bue + bue->next_entry);\n                else\n                    break;\n            }\n        }\n    }\n\n    if (!load_string(module, IDS_USAGE_UNALLOC, t))\n        throw last_error(GetLastError());\n\n    s += t + L\"\\r\\n\"s;\n\n    for (size_t k = 0; k < min((uint64_t)SIZE_MAX, num_devs); k++) {\n        wstring sizestring;\n\n        format_size(devs[k].size - devs[k].alloc, sizestring, false);\n\n        s += devs[k].name + L\"\\t\" + sizestring + L\"\\r\\n\";\n    }\n}\n\nvoid BtrfsVolPropSheet::RefreshUsage(HWND hwndDlg) {\n    wstring s;\n    btrfs_usage* usage;\n\n    win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                               OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n    if (h != INVALID_HANDLE_VALUE) {\n        NTSTATUS Status;\n        IO_STATUS_BLOCK iosb;\n        ULONG devsize, usagesize, i;\n\n        i = 0;\n        devsize = 1024;\n\n        devices = (btrfs_device*)malloc(devsize);\n\n        while (true) {\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_DEVICES, nullptr, 0, devices, devsize);\n            if (Status == STATUS_BUFFER_OVERFLOW) {\n                if (i < 8) {\n                    devsize += 1024;\n\n                    free(devices);\n                    devices = (btrfs_device*)malloc(devsize);\n\n                    i++;\n                } else\n                    return;\n            } else\n                break;\n        }\n\n        if (!NT_SUCCESS(Status))\n            return;\n\n        i = 0;\n        usagesize = 1024;\n\n        usage = (btrfs_usage*)malloc(usagesize);\n\n        while (true) {\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_USAGE, nullptr, 0, usage, usagesize);\n            if (Status == STATUS_BUFFER_OVERFLOW) {\n                if (i < 8) {\n                    usagesize += 1024;\n\n                    free(usage);\n                    usage = (btrfs_usage*)malloc(usagesize);\n\n                    i++;\n                } else\n                    return;\n            } else\n                break;\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            free(usage);\n            return;\n        }\n\n        ignore = false;\n    } else\n        return;\n\n    FormatUsage(hwndDlg, s, usage);\n\n    SetDlgItemTextW(hwndDlg, IDC_USAGE_BOX, s.c_str());\n\n    free(usage);\n}\n\nINT_PTR CALLBACK BtrfsVolPropSheet::UsageDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n            {\n                wstring s;\n                int i;\n                ULONG usagesize;\n                NTSTATUS Status;\n                IO_STATUS_BLOCK iosb;\n\n                EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB);\n\n                win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                        OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n                if (h != INVALID_HANDLE_VALUE) {\n                    btrfs_usage* usage;\n\n                    i = 0;\n                    usagesize = 1024;\n\n                    usage = (btrfs_usage*)malloc(usagesize);\n\n                    while (true) {\n                        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_USAGE, nullptr, 0, usage, usagesize);\n                        if (Status == STATUS_BUFFER_OVERFLOW) {\n                            if (i < 8) {\n                                usagesize += 1024;\n\n                                free(usage);\n                                usage = (btrfs_usage*)malloc(usagesize);\n\n                                i++;\n                            } else\n                                break;\n                        } else\n                            break;\n                    }\n\n                    if (!NT_SUCCESS(Status)) {\n                        free(usage);\n                        break;\n                    }\n\n                    FormatUsage(hwndDlg, s, usage);\n\n                    SetDlgItemTextW(hwndDlg, IDC_USAGE_BOX, s.c_str());\n\n                    free(usage);\n                }\n\n                break;\n            }\n\n            case WM_COMMAND:\n                switch (HIWORD(wParam)) {\n                    case BN_CLICKED:\n                        switch (LOWORD(wParam)) {\n                            case IDOK:\n                            case IDCANCEL:\n                                EndDialog(hwndDlg, 0);\n                            return true;\n\n                            case IDC_USAGE_REFRESH:\n                                RefreshUsage(hwndDlg);\n                            return true;\n                        }\n                    break;\n                }\n            break;\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nstatic INT_PTR CALLBACK stub_UsageDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    BtrfsVolPropSheet* bvps;\n\n    if (uMsg == WM_INITDIALOG) {\n        SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);\n        bvps = (BtrfsVolPropSheet*)lParam;\n    } else {\n        bvps = (BtrfsVolPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n    }\n\n    if (bvps)\n        return bvps->UsageDlgProc(hwndDlg, uMsg, wParam, lParam);\n    else\n        return false;\n}\n\nvoid BtrfsVolPropSheet::ShowUsage(HWND hwndDlg) {\n   DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_VOL_USAGE), hwndDlg, stub_UsageDlgProc, (LPARAM)this);\n}\n\nstatic void add_lv_column(HWND list, int string, int cx) {\n    LVCOLUMNW lvc;\n    wstring s;\n\n    if (!load_string(module, string, s))\n        throw last_error(GetLastError());\n\n    lvc.mask = LVCF_TEXT|LVCF_WIDTH;\n    lvc.pszText = (WCHAR*)s.c_str();\n    lvc.cx = cx;\n    SendMessageW(list, LVM_INSERTCOLUMNW, 0, (LPARAM)&lvc);\n}\n\nstatic int CALLBACK lv_sort(LPARAM lParam1, LPARAM lParam2, LPARAM) {\n    if (lParam1 < lParam2)\n        return -1;\n    else if (lParam1 > lParam2)\n        return 1;\n    else\n        return 0;\n}\n\nstatic uint64_t find_dev_alloc(uint64_t dev_id, btrfs_usage* usage) {\n    btrfs_usage* bue;\n    uint64_t alloc;\n\n    alloc = 0;\n\n    bue = usage;\n    while (true) {\n        uint64_t k;\n\n        for (k = 0; k < bue->num_devices; k++) {\n            if (bue->devices[k].dev_id == dev_id)\n                alloc += bue->devices[k].alloc;\n        }\n\n        if (bue->next_entry > 0)\n            bue = (btrfs_usage*)((uint8_t*)bue + bue->next_entry);\n        else\n            break;\n    }\n\n    return alloc;\n}\n\nvoid BtrfsVolPropSheet::RefreshDevList(HWND devlist) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    ULONG usagesize, devsize;\n    btrfs_usage* usage;\n    btrfs_device* bd;\n    int i;\n    uint64_t num_rw_devices;\n    {\n        win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                   OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n        if (h == INVALID_HANDLE_VALUE)\n            throw last_error(GetLastError());\n\n        i = 0;\n        devsize = 1024;\n\n        if (devices)\n            free(devices);\n\n        devices = (btrfs_device*)malloc(devsize);\n\n        while (true) {\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_DEVICES, nullptr, 0, devices, devsize);\n            if (Status == STATUS_BUFFER_OVERFLOW) {\n                if (i < 8) {\n                    devsize += 1024;\n\n                    free(devices);\n                    devices = (btrfs_device*)malloc(devsize);\n\n                    i++;\n                } else\n                    return;\n            } else\n                break;\n        }\n\n        if (!NT_SUCCESS(Status))\n            return;\n\n        bd = devices;\n\n        i = 0;\n        usagesize = 1024;\n\n        usage = (btrfs_usage*)malloc(usagesize);\n\n        while (true) {\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_USAGE, nullptr, 0, usage, usagesize);\n            if (Status == STATUS_BUFFER_OVERFLOW) {\n                if (i < 8) {\n                    usagesize += 1024;\n\n                    free(usage);\n                    usage = (btrfs_usage*)malloc(usagesize);\n\n                    i++;\n                } else {\n                    free(usage);\n                    return;\n                }\n            } else\n                break;\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            free(usage);\n            return;\n        }\n    }\n\n    SendMessageW(devlist, LVM_DELETEALLITEMS, 0, 0);\n\n    num_rw_devices = 0;\n\n    i = 0;\n    while (true) {\n        LVITEMW lvi;\n        wstring s, u;\n        uint64_t alloc;\n\n        // ID\n\n        RtlZeroMemory(&lvi, sizeof(LVITEMW));\n        lvi.mask = LVIF_TEXT | LVIF_PARAM;\n        lvi.iItem = (int)SendMessageW(devlist, LVM_GETITEMCOUNT, 0, 0);\n        lvi.lParam = (LPARAM)bd->dev_id;\n\n        s = to_wstring(bd->dev_id);\n        lvi.pszText = (LPWSTR)s.c_str();\n\n        SendMessageW(devlist, LVM_INSERTITEMW, 0, (LPARAM)&lvi);\n\n        // description\n\n        lvi.mask = LVIF_TEXT;\n        lvi.iSubItem = 1;\n\n        if (bd->missing) {\n            if (!load_string(module, IDS_MISSING, s))\n                throw last_error(GetLastError());\n        } else if (bd->device_number == 0xffffffff)\n            s = wstring(bd->name, bd->namelen / sizeof(WCHAR));\n        else if (bd->partition_number == 0) {\n            if (!load_string(module, IDS_DISK_NUM, u))\n                throw last_error(GetLastError());\n\n            wstring_sprintf(s, u, bd->device_number);\n        } else {\n            if (!load_string(module, IDS_DISK_PART_NUM, u))\n                throw last_error(GetLastError());\n\n            wstring_sprintf(s, u, bd->device_number, bd->partition_number);\n        }\n\n        lvi.pszText = (LPWSTR)s.c_str();\n\n        SendMessageW(devlist, LVM_SETITEMW, 0, (LPARAM)&lvi);\n\n        // readonly\n\n        lvi.iSubItem = 2;\n        load_string(module, bd->readonly ? IDS_DEVLIST_READONLY_YES : IDS_DEVLIST_READONLY_NO, s);\n        lvi.pszText = (LPWSTR)s.c_str();\n        SendMessageW(devlist, LVM_SETITEMW, 0, (LPARAM)&lvi);\n\n        if (!bd->readonly)\n            num_rw_devices++;\n\n        // size\n\n        lvi.iSubItem = 3;\n        format_size(bd->size, s, false);\n        lvi.pszText = (LPWSTR)s.c_str();\n        SendMessageW(devlist, LVM_SETITEMW, 0, (LPARAM)&lvi);\n\n        // alloc\n\n        alloc = find_dev_alloc(bd->dev_id, usage);\n\n        lvi.iSubItem = 4;\n        format_size(alloc, s, false);\n        lvi.pszText = (LPWSTR)s.c_str();\n        SendMessageW(devlist, LVM_SETITEMW, 0, (LPARAM)&lvi);\n\n        // alloc %\n\n        wstring_sprintf(s, L\"%1.1f%%\", (float)alloc * 100.0f / (float)bd->size);\n        lvi.iSubItem = 5;\n        lvi.pszText = (LPWSTR)s.c_str();\n        SendMessageW(devlist, LVM_SETITEMW, 0, (LPARAM)&lvi);\n\n        i++;\n\n        if (bd->next_entry > 0)\n            bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry);\n        else\n            break;\n    }\n\n    free(usage);\n\n    SendMessageW(devlist, LVM_SORTITEMS, 0, (LPARAM)lv_sort);\n\n    EnableWindow(GetDlgItem(GetParent(devlist), IDC_DEVICE_ADD), num_rw_devices > 0);\n    EnableWindow(GetDlgItem(GetParent(devlist), IDC_DEVICE_REMOVE), num_rw_devices > 1);\n}\n\nvoid BtrfsVolPropSheet::ResetStats(HWND hwndDlg) {\n    wstring t, sel;\n    WCHAR modfn[MAX_PATH];\n    SHELLEXECUTEINFOW sei;\n\n    sel = to_wstring(stats_dev);\n\n    GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR));\n\n    t = L\"\\\"\"s + modfn + L\"\\\",ResetStats \" + fn + L\"|\" + sel;\n\n    RtlZeroMemory(&sei, sizeof(sei));\n\n    sei.cbSize = sizeof(sei);\n    sei.hwnd = hwndDlg;\n    sei.lpVerb = L\"runas\";\n    sei.lpFile = L\"rundll32.exe\";\n    sei.lpParameters = t.c_str();\n    sei.nShow = SW_SHOW;\n    sei.fMask = SEE_MASK_NOCLOSEPROCESS;\n\n    if (!ShellExecuteExW(&sei))\n        throw last_error(GetLastError());\n\n    WaitForSingleObject(sei.hProcess, INFINITE);\n    CloseHandle(sei.hProcess);\n\n    win_handle h = CreateFileW(fn.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                               OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n    if (h != INVALID_HANDLE_VALUE) {\n        NTSTATUS Status;\n        IO_STATUS_BLOCK iosb;\n        ULONG devsize, i;\n\n        i = 0;\n        devsize = 1024;\n\n        free(devices);\n        devices = (btrfs_device*)malloc(devsize);\n\n        while (true) {\n            Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_GET_DEVICES, nullptr, 0, devices, devsize);\n            if (Status == STATUS_BUFFER_OVERFLOW) {\n                if (i < 8) {\n                    devsize += 1024;\n\n                    free(devices);\n                    devices = (btrfs_device*)malloc(devsize);\n\n                    i++;\n                } else\n                    break;\n            } else\n                break;\n        }\n    }\n\n    EndDialog(hwndDlg, 0);\n}\n\nINT_PTR CALLBACK BtrfsVolPropSheet::StatsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n            {\n                WCHAR s[255];\n                wstring t;\n                btrfs_device *bd, *dev = nullptr;\n                int i;\n\n                static int stat_ids[] = { IDC_WRITE_ERRS, IDC_READ_ERRS, IDC_FLUSH_ERRS, IDC_CORRUPTION_ERRS, IDC_GENERATION_ERRS };\n\n                bd = devices;\n\n                while (true) {\n                    if (bd->dev_id == stats_dev) {\n                        dev = bd;\n                        break;\n                    }\n\n                    if (bd->next_entry > 0)\n                        bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry);\n                    else\n                        break;\n                }\n\n                if (!dev) {\n                    EndDialog(hwndDlg, 0);\n                    throw string_error(IDS_CANNOT_FIND_DEVICE);\n                }\n\n                GetDlgItemTextW(hwndDlg, IDC_DEVICE_ID, s, sizeof(s) / sizeof(WCHAR));\n\n                wstring_sprintf(t, s, dev->dev_id);\n\n                SetDlgItemTextW(hwndDlg, IDC_DEVICE_ID, t.c_str());\n\n                for (i = 0; i < 5; i++) {\n                    GetDlgItemTextW(hwndDlg, stat_ids[i], s, sizeof(s) / sizeof(WCHAR));\n\n                    wstring_sprintf(t, s, dev->stats[i]);\n\n                    SetDlgItemTextW(hwndDlg, stat_ids[i], t.c_str());\n                }\n\n                SendMessageW(GetDlgItem(hwndDlg, IDC_RESET_STATS), BCM_SETSHIELD, 0, true);\n                EnableWindow(GetDlgItem(hwndDlg, IDC_RESET_STATS), !readonly);\n\n                break;\n            }\n\n            case WM_COMMAND:\n                switch (HIWORD(wParam)) {\n                    case BN_CLICKED:\n                        switch (LOWORD(wParam)) {\n                            case IDOK:\n                            case IDCANCEL:\n                                EndDialog(hwndDlg, 0);\n                            return true;\n\n                            case IDC_RESET_STATS:\n                                ResetStats(hwndDlg);\n                            return true;\n                        }\n                    break;\n                }\n            break;\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nstatic INT_PTR CALLBACK stub_StatsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    BtrfsVolPropSheet* bvps;\n\n    if (uMsg == WM_INITDIALOG) {\n        SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);\n        bvps = (BtrfsVolPropSheet*)lParam;\n    } else {\n        bvps = (BtrfsVolPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n    }\n\n    if (bvps)\n        return bvps->StatsDlgProc(hwndDlg, uMsg, wParam, lParam);\n    else\n        return false;\n}\n\nvoid BtrfsVolPropSheet::ShowStats(HWND hwndDlg, uint64_t devid) {\n    stats_dev = devid;\n\n    DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_DEVICE_STATS), hwndDlg, stub_StatsDlgProc, (LPARAM)this);\n}\n\nINT_PTR CALLBACK BtrfsVolPropSheet::DeviceDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n            {\n                HWND devlist;\n                RECT rect;\n                ULONG w;\n\n                EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB);\n\n                devlist = GetDlgItem(hwndDlg, IDC_DEVLIST);\n\n                ListView_SetExtendedListViewStyleEx(devlist, ListView_GetExtendedListViewStyle(devlist), LVS_EX_FULLROWSELECT);\n\n                GetClientRect(devlist, &rect);\n                w = rect.right - rect.left;\n\n                add_lv_column(devlist, IDS_DEVLIST_ALLOC_PC, w * 5 / 44);\n                add_lv_column(devlist, IDS_DEVLIST_ALLOC, w * 6 / 44);\n                add_lv_column(devlist, IDS_DEVLIST_SIZE, w * 6 / 44);\n                add_lv_column(devlist, IDS_DEVLIST_READONLY, w * 7 / 44);\n                add_lv_column(devlist, IDS_DEVLIST_DESC, w * 16 / 44);\n                add_lv_column(devlist, IDS_DEVLIST_ID, w * 4 / 44);\n\n                SendMessageW(GetDlgItem(hwndDlg, IDC_DEVICE_ADD), BCM_SETSHIELD, 0, true);\n                SendMessageW(GetDlgItem(hwndDlg, IDC_DEVICE_REMOVE), BCM_SETSHIELD, 0, true);\n                SendMessageW(GetDlgItem(hwndDlg, IDC_DEVICE_RESIZE), BCM_SETSHIELD, 0, true);\n\n                RefreshDevList(devlist);\n\n                break;\n            }\n\n            case WM_COMMAND:\n                switch (HIWORD(wParam)) {\n                    case BN_CLICKED:\n                        switch (LOWORD(wParam)) {\n                            case IDOK:\n                            case IDCANCEL:\n                                KillTimer(hwndDlg, 1);\n                                EndDialog(hwndDlg, 0);\n                            return true;\n\n                            case IDC_DEVICE_ADD:\n                            {\n                                wstring t;\n                                WCHAR modfn[MAX_PATH];\n                                SHELLEXECUTEINFOW sei;\n\n                                GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR));\n\n                                t = L\"\\\"\"s + modfn + L\"\\\",AddDevice \"s + fn;\n\n                                RtlZeroMemory(&sei, sizeof(sei));\n\n                                sei.cbSize = sizeof(sei);\n                                sei.hwnd = hwndDlg;\n                                sei.lpVerb = L\"runas\";\n                                sei.lpFile = L\"rundll32.exe\";\n                                sei.lpParameters = t.c_str();\n                                sei.nShow = SW_SHOW;\n                                sei.fMask = SEE_MASK_NOCLOSEPROCESS;\n\n                                if (!ShellExecuteExW(&sei))\n                                    throw last_error(GetLastError());\n\n                                WaitForSingleObject(sei.hProcess, INFINITE);\n                                CloseHandle(sei.hProcess);\n\n                                RefreshDevList(GetDlgItem(hwndDlg, IDC_DEVLIST));\n\n                                return true;\n                            }\n\n                            case IDC_DEVICE_REFRESH:\n                                RefreshDevList(GetDlgItem(hwndDlg, IDC_DEVLIST));\n                                return true;\n\n                            case IDC_DEVICE_SHOW_STATS:\n                            {\n                                WCHAR sel[MAX_PATH];\n                                HWND devlist;\n                                LVITEMW lvi;\n\n                                devlist = GetDlgItem(hwndDlg, IDC_DEVLIST);\n\n                                auto index = SendMessageW(devlist, LVM_GETNEXTITEM, -1, LVNI_SELECTED);\n\n                                if (index == -1)\n                                    return true;\n\n                                RtlZeroMemory(&lvi, sizeof(LVITEMW));\n                                lvi.mask = LVIF_TEXT;\n                                lvi.iItem = (int)index;\n                                lvi.iSubItem = 0;\n                                lvi.pszText = sel;\n                                lvi.cchTextMax = sizeof(sel) / sizeof(WCHAR);\n                                SendMessageW(devlist, LVM_GETITEMW, 0, (LPARAM)&lvi);\n\n                                ShowStats(hwndDlg, _wtoi(sel));\n                                return true;\n                            }\n\n                            case IDC_DEVICE_REMOVE:\n                            {\n                                wstring t, mess, mess2, title;\n                                WCHAR modfn[MAX_PATH], sel[MAX_PATH], sel2[MAX_PATH];\n                                HWND devlist;\n                                SHELLEXECUTEINFOW sei;\n                                LVITEMW lvi;\n\n                                devlist = GetDlgItem(hwndDlg, IDC_DEVLIST);\n\n                                auto index = SendMessageW(devlist, LVM_GETNEXTITEM, -1, LVNI_SELECTED);\n\n                                if (index == -1)\n                                    return true;\n\n                                RtlZeroMemory(&lvi, sizeof(LVITEMW));\n                                lvi.mask = LVIF_TEXT;\n                                lvi.iItem = (int)index;\n                                lvi.iSubItem = 0;\n                                lvi.pszText = sel;\n                                lvi.cchTextMax = sizeof(sel) / sizeof(WCHAR);\n                                SendMessageW(devlist, LVM_GETITEMW, 0, (LPARAM)&lvi);\n\n                                lvi.iSubItem = 1;\n                                lvi.pszText = sel2;\n                                lvi.cchTextMax = sizeof(sel2) / sizeof(WCHAR);\n                                SendMessageW(devlist, LVM_GETITEMW, 0, (LPARAM)&lvi);\n\n                                if (!load_string(module, IDS_REMOVE_DEVICE_CONFIRMATION, mess))\n                                    throw last_error(GetLastError());\n\n                                wstring_sprintf(mess2, mess, sel, sel2);\n\n                                if (!load_string(module, IDS_CONFIRMATION_TITLE, title))\n                                    throw last_error(GetLastError());\n\n                                if (MessageBoxW(hwndDlg, mess2.c_str(), title.c_str(), MB_YESNO) != IDYES)\n                                    return true;\n\n                                GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR));\n\n                                t = L\"\\\"\"s + modfn + L\"\\\",RemoveDevice \"s + fn + L\"|\"s + sel;\n\n                                RtlZeroMemory(&sei, sizeof(sei));\n\n                                sei.cbSize = sizeof(sei);\n                                sei.hwnd = hwndDlg;\n                                sei.lpVerb = L\"runas\";\n                                sei.lpFile = L\"rundll32.exe\";\n                                sei.lpParameters = t.c_str();\n                                sei.nShow = SW_SHOW;\n                                sei.fMask = SEE_MASK_NOCLOSEPROCESS;\n\n                                if (!ShellExecuteExW(&sei))\n                                    throw last_error(GetLastError());\n\n                                WaitForSingleObject(sei.hProcess, INFINITE);\n                                CloseHandle(sei.hProcess);\n\n                                RefreshDevList(GetDlgItem(hwndDlg, IDC_DEVLIST));\n\n                                return true;\n                            }\n\n                            case IDC_DEVICE_RESIZE:\n                            {\n                                HWND devlist;\n                                LVITEMW lvi;\n                                wstring t;\n                                WCHAR modfn[MAX_PATH], sel[100];\n                                SHELLEXECUTEINFOW sei;\n\n                                devlist = GetDlgItem(hwndDlg, IDC_DEVLIST);\n\n                                auto index = SendMessageW(devlist, LVM_GETNEXTITEM, -1, LVNI_SELECTED);\n\n                                if (index == -1)\n                                    return true;\n\n                                RtlZeroMemory(&lvi, sizeof(LVITEMW));\n                                lvi.mask = LVIF_TEXT;\n                                lvi.iItem = (int)index;\n                                lvi.iSubItem = 0;\n                                lvi.pszText = sel;\n                                lvi.cchTextMax = sizeof(sel) / sizeof(WCHAR);\n                                SendMessageW(devlist, LVM_GETITEMW, 0, (LPARAM)&lvi);\n\n                                GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR));\n\n                                t = L\"\\\"\"s + modfn + L\"\\\",ResizeDevice \"s + fn + L\"|\"s + sel;\n\n                                RtlZeroMemory(&sei, sizeof(sei));\n\n                                sei.cbSize = sizeof(sei);\n                                sei.hwnd = hwndDlg;\n                                sei.lpVerb = L\"runas\";\n                                sei.lpFile = L\"rundll32.exe\";\n                                sei.lpParameters = t.c_str();\n                                sei.nShow = SW_SHOW;\n                                sei.fMask = SEE_MASK_NOCLOSEPROCESS;\n\n                                if (!ShellExecuteExW(&sei))\n                                    throw last_error(GetLastError());\n\n                                WaitForSingleObject(sei.hProcess, INFINITE);\n                                CloseHandle(sei.hProcess);\n\n                                RefreshDevList(GetDlgItem(hwndDlg, IDC_DEVLIST));\n                            }\n                        }\n                    break;\n                }\n            break;\n\n            case WM_NOTIFY:\n                switch (((LPNMHDR)lParam)->code) {\n                    case LVN_ITEMCHANGED:\n                    {\n                        NMLISTVIEW* nmv = (NMLISTVIEW*)lParam;\n\n                        EnableWindow(GetDlgItem(hwndDlg, IDC_DEVICE_SHOW_STATS), nmv->uNewState & LVIS_SELECTED);\n\n                        if (nmv->uNewState & LVIS_SELECTED && !readonly) {\n                            HWND devlist;\n                            btrfs_device* bd;\n                            bool device_readonly = false;\n                            LVITEMW lvi;\n                            WCHAR sel[MAX_PATH];\n                            uint64_t devid;\n\n                            devlist = GetDlgItem(hwndDlg, IDC_DEVLIST);\n\n                            RtlZeroMemory(&lvi, sizeof(LVITEMW));\n                            lvi.mask = LVIF_TEXT;\n                            lvi.iItem = nmv->iItem;\n                            lvi.iSubItem = 0;\n                            lvi.pszText = sel;\n                            lvi.cchTextMax = sizeof(sel) / sizeof(WCHAR);\n                            SendMessageW(devlist, LVM_GETITEMW, 0, (LPARAM)&lvi);\n                            devid = _wtoi(sel);\n\n                            bd = devices;\n\n                            while (true) {\n                                if (bd->dev_id == devid) {\n                                    device_readonly = bd->readonly;\n                                    break;\n                                }\n\n                                if (bd->next_entry > 0)\n                                    bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry);\n                                else\n                                    break;\n                            }\n\n                            EnableWindow(GetDlgItem(hwndDlg, IDC_DEVICE_RESIZE), !device_readonly);\n                        } else\n                            EnableWindow(GetDlgItem(hwndDlg, IDC_DEVICE_RESIZE), false);\n\n                        break;\n                    }\n                }\n            break;\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nstatic INT_PTR CALLBACK stub_DeviceDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    BtrfsVolPropSheet* bvps;\n\n    if (uMsg == WM_INITDIALOG) {\n        SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);\n        bvps = (BtrfsVolPropSheet*)lParam;\n    } else {\n        bvps = (BtrfsVolPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n    }\n\n    if (bvps)\n        return bvps->DeviceDlgProc(hwndDlg, uMsg, wParam, lParam);\n    else\n        return false;\n}\n\nvoid BtrfsVolPropSheet::ShowDevices(HWND hwndDlg) {\n   DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_DEVICES), hwndDlg, stub_DeviceDlgProc, (LPARAM)this);\n}\n\nvoid BtrfsVolPropSheet::ShowScrub(HWND hwndDlg) {\n    wstring t;\n    WCHAR modfn[MAX_PATH];\n    SHELLEXECUTEINFOW sei;\n\n    GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR));\n\n    t = L\"\\\"\"s + modfn + L\"\\\",ShowScrub \"s + fn;\n\n    RtlZeroMemory(&sei, sizeof(sei));\n\n    sei.cbSize = sizeof(sei);\n    sei.hwnd = hwndDlg;\n    sei.lpVerb = L\"runas\";\n    sei.lpFile = L\"rundll32.exe\";\n    sei.lpParameters = t.c_str();\n    sei.nShow = SW_SHOW;\n    sei.fMask = SEE_MASK_NOCLOSEPROCESS;\n\n    if (!ShellExecuteExW(&sei))\n        throw last_error(GetLastError());\n\n    WaitForSingleObject(sei.hProcess, INFINITE);\n    CloseHandle(sei.hProcess);\n}\n\nvoid BtrfsVolPropSheet::ShowChangeDriveLetter(HWND hwndDlg) {\n    wstring t;\n    WCHAR modfn[MAX_PATH];\n    SHELLEXECUTEINFOW sei;\n\n    GetModuleFileNameW(module, modfn, sizeof(modfn) / sizeof(WCHAR));\n\n    t = L\"\\\"\"s + modfn + L\"\\\",ShowChangeDriveLetter \"s + fn;\n\n    RtlZeroMemory(&sei, sizeof(sei));\n\n    sei.cbSize = sizeof(sei);\n    sei.hwnd = hwndDlg;\n    sei.lpVerb = L\"runas\";\n    sei.lpFile = L\"rundll32.exe\";\n    sei.lpParameters = t.c_str();\n    sei.nShow = SW_SHOW;\n    sei.fMask = SEE_MASK_NOCLOSEPROCESS;\n\n    if (!ShellExecuteExW(&sei))\n        throw last_error(GetLastError());\n\n    WaitForSingleObject(sei.hProcess, INFINITE);\n    CloseHandle(sei.hProcess);\n}\n\nstatic INT_PTR CALLBACK PropSheetDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n            {\n                PROPSHEETPAGEW* psp = (PROPSHEETPAGEW*)lParam;\n                BtrfsVolPropSheet* bps = (BtrfsVolPropSheet*)psp->lParam;\n                btrfs_device* bd;\n\n                EnableThemeDialogTexture(hwndDlg, ETDT_ENABLETAB);\n\n                SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)bps);\n\n                bps->readonly = true;\n                bd = bps->devices;\n\n                while (true) {\n                    if (!bd->readonly) {\n                        bps->readonly = false;\n                        break;\n                    }\n\n                    if (bd->next_entry > 0)\n                        bd = (btrfs_device*)((uint8_t*)bd + bd->next_entry);\n                    else\n                        break;\n                }\n\n                if (bps->uuid_set) {\n                    WCHAR s[255];\n                    wstring t;\n\n                    GetDlgItemTextW(hwndDlg, IDC_UUID, s, sizeof(s) / sizeof(WCHAR));\n\n                    wstring_sprintf(t, s, bps->uuid.uuid[0], bps->uuid.uuid[1], bps->uuid.uuid[2], bps->uuid.uuid[3], bps->uuid.uuid[4], bps->uuid.uuid[5],\n                                    bps->uuid.uuid[6], bps->uuid.uuid[7], bps->uuid.uuid[8], bps->uuid.uuid[9], bps->uuid.uuid[10], bps->uuid.uuid[11],\n                                    bps->uuid.uuid[12], bps->uuid.uuid[13], bps->uuid.uuid[14], bps->uuid.uuid[15]);\n\n                    SetDlgItemTextW(hwndDlg, IDC_UUID, t.c_str());\n                } else\n                    SetDlgItemTextW(hwndDlg, IDC_UUID, L\"\");\n\n                SendMessageW(GetDlgItem(hwndDlg, IDC_VOL_SCRUB), BCM_SETSHIELD, 0, true);\n                SendMessageW(GetDlgItem(hwndDlg, IDC_VOL_CHANGE_DRIVE_LETTER), BCM_SETSHIELD, 0, true);\n\n                return false;\n            }\n\n            case WM_NOTIFY:\n            {\n                switch (((LPNMHDR)lParam)->code) {\n                    case PSN_KILLACTIVE:\n                        SetWindowLongPtrW(hwndDlg, DWLP_MSGRESULT, false);\n                    break;\n                }\n                break;\n            }\n\n            case WM_COMMAND:\n            {\n                BtrfsVolPropSheet* bps = (BtrfsVolPropSheet*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n\n                if (bps) {\n                    switch (HIWORD(wParam)) {\n                        case BN_CLICKED: {\n                            switch (LOWORD(wParam)) {\n                                case IDC_VOL_SHOW_USAGE:\n                                    bps->ShowUsage(hwndDlg);\n                                break;\n\n                                case IDC_VOL_BALANCE:\n                                    bps->balance->ShowBalance(hwndDlg);\n                                break;\n\n                                case IDC_VOL_DEVICES:\n                                    bps->ShowDevices(hwndDlg);\n                                break;\n\n                                case IDC_VOL_SCRUB:\n                                    bps->ShowScrub(hwndDlg);\n                                break;\n\n                                case IDC_VOL_CHANGE_DRIVE_LETTER:\n                                    bps->ShowChangeDriveLetter(hwndDlg);\n                                break;\n                            }\n                        }\n                    }\n                }\n\n                break;\n            }\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nHRESULT __stdcall BtrfsVolPropSheet::AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) {\n    try {\n        PROPSHEETPAGEW psp;\n        HPROPSHEETPAGE hPage;\n        INITCOMMONCONTROLSEX icex;\n\n        if (ignore)\n            return S_OK;\n\n        icex.dwSize = sizeof(icex);\n        icex.dwICC = ICC_LINK_CLASS;\n\n        if (!InitCommonControlsEx(&icex))\n            throw string_error(IDS_INITCOMMONCONTROLSEX_FAILED);\n\n        psp.dwSize = sizeof(psp);\n        psp.dwFlags = PSP_USEREFPARENT | PSP_USETITLE;\n        psp.hInstance = module;\n        psp.pszTemplate = MAKEINTRESOURCEW(IDD_VOL_PROP_SHEET);\n        psp.hIcon = 0;\n        psp.pszTitle = MAKEINTRESOURCEW(IDS_VOL_PROP_SHEET_TITLE);\n        psp.pfnDlgProc = (DLGPROC)PropSheetDlgProc;\n        psp.pcRefParent = (UINT*)&objs_loaded;\n        psp.pfnCallback = nullptr;\n        psp.lParam = (LPARAM)this;\n\n        hPage = CreatePropertySheetPageW(&psp);\n\n        if (hPage) {\n            if (pfnAddPage(hPage, lParam)) {\n                this->AddRef();\n                return S_OK;\n            } else\n                DestroyPropertySheetPage(hPage);\n        } else\n            return E_OUTOFMEMORY;\n    } catch (const exception& e) {\n        error_message(nullptr, e.what());\n    }\n\n    return E_FAIL;\n}\n\nHRESULT __stdcall BtrfsVolPropSheet::ReplacePage(UINT, LPFNADDPROPSHEETPAGE, LPARAM) {\n    return S_OK;\n}\n\nvoid BtrfsChangeDriveLetter::do_change(HWND hwndDlg) {\n    unsigned int sel = (unsigned int)SendDlgItemMessageW(hwndDlg, IDC_DRIVE_LETTER_COMBO, CB_GETCURSEL, 0, 0);\n\n    if (sel < letters.size()) {\n        wstring dd;\n\n        if (fn.length() == 3 && fn[1] == L':' && fn[2] == L'\\\\') {\n            dd = L\"\\\\DosDevices\\\\?:\";\n\n            dd[12] = fn[0];\n        } else\n            throw runtime_error(\"Volume path was not root of drive.\");\n\n        mountmgr mm;\n        wstring dev_name;\n\n        {\n            auto v = mm.query_points(dd);\n\n            if (v.empty())\n                throw runtime_error(\"Error finding device name.\");\n\n            dev_name = v[0].device_name;\n        }\n\n        wstring new_dd = L\"\\\\DosDevices\\\\?:\";\n        new_dd[12] = letters[sel];\n\n        mm.delete_points(dd);\n\n        try {\n            mm.create_point(new_dd, dev_name);\n        } catch (...) {\n            // if fails, try to recreate old symlink, so we're not left with no drive letter at all\n            mm.create_point(dd, dev_name);\n            throw;\n        }\n    }\n\n    EndDialog(hwndDlg, 1);\n}\n\nINT_PTR BtrfsChangeDriveLetter::DlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM) {\n    try {\n        switch (uMsg) {\n            case WM_INITDIALOG:\n            {\n                HWND cb = GetDlgItem(hwndDlg, IDC_DRIVE_LETTER_COMBO);\n\n                SendMessageW(cb, CB_RESETCONTENT, 0, 0);\n\n                mountmgr mm;\n                wstring drv;\n\n                drv = L\"\\\\DosDevices\\\\?:\";\n\n                for (wchar_t l = 'A'; l <= 'Z'; l++) {\n                    bool found = true;\n\n                    drv[12] = l;\n\n                    try {\n                        auto v = mm.query_points(drv);\n\n                        if (v.empty())\n                            found = false;\n                    } catch (const ntstatus_error& ntstatus) {\n                        if (ntstatus.Status == STATUS_OBJECT_NAME_NOT_FOUND)\n                            found = false;\n                        else\n                            throw;\n                    }\n\n                    if (!found) {\n                        wstring str = L\"?:\";\n\n                        str[0] = l;\n                        letters.push_back(l);\n\n                        SendMessageW(cb, CB_ADDSTRING, 0, reinterpret_cast<LPARAM>(str.c_str()));\n                    }\n                }\n\n                break;\n            }\n\n            case WM_COMMAND:\n                switch (HIWORD(wParam)) {\n                    case BN_CLICKED:\n                        switch (LOWORD(wParam)) {\n                            case IDOK:\n                                do_change(hwndDlg);\n                                return true;\n\n                            case IDCANCEL:\n                                EndDialog(hwndDlg, 0);\n                                return true;\n                        }\n                        break;\n                }\n            break;\n        }\n    } catch (const exception& e) {\n        error_message(hwndDlg, e.what());\n    }\n\n    return false;\n}\n\nstatic INT_PTR __stdcall dlg_proc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {\n    BtrfsChangeDriveLetter* bcdl;\n\n    if (uMsg == WM_INITDIALOG) {\n        SetWindowLongPtrW(hwndDlg, GWLP_USERDATA, (LONG_PTR)lParam);\n        bcdl = (BtrfsChangeDriveLetter*)lParam;\n    } else\n        bcdl = (BtrfsChangeDriveLetter*)GetWindowLongPtrW(hwndDlg, GWLP_USERDATA);\n\n    return bcdl->DlgProc(hwndDlg, uMsg, wParam, lParam);\n}\n\nvoid BtrfsChangeDriveLetter::show() {\n    DialogBoxParamW(module, MAKEINTRESOURCEW(IDD_DRIVE_LETTER), hwnd, dlg_proc, (LPARAM)this);\n}\n\nextern \"C\" {\n\nvoid CALLBACK ResetStatsW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    try {\n        win_handle token;\n        NTSTATUS Status;\n        TOKEN_PRIVILEGES tp;\n        LUID luid;\n        uint64_t devid;\n        wstring cmdline, vol, dev;\n        size_t pipe;\n        IO_STATUS_BLOCK iosb;\n\n        set_dpi_aware();\n\n        cmdline = lpszCmdLine;\n\n        if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n            throw last_error(GetLastError());\n\n        if (!LookupPrivilegeValueW(nullptr, L\"SeManageVolumePrivilege\", &luid))\n            throw last_error(GetLastError());\n\n        tp.PrivilegeCount = 1;\n        tp.Privileges[0].Luid = luid;\n        tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n        if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), nullptr, nullptr))\n            throw last_error(GetLastError());\n\n        pipe = cmdline.find(L\"|\");\n\n        if (pipe == string::npos)\n            return;\n\n        vol = cmdline.substr(0, pipe);\n        dev = cmdline.substr(pipe + 1);\n\n        devid = _wtoi(dev.c_str());\n        if (devid == 0)\n            return;\n\n        win_handle h = CreateFileW(vol.c_str(), FILE_TRAVERSE | FILE_READ_ATTRIBUTES, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr,\n                                OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT, nullptr);\n\n        if (h == INVALID_HANDLE_VALUE)\n            throw last_error(GetLastError());\n\n        Status = NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb, FSCTL_BTRFS_RESET_STATS, &devid, sizeof(uint64_t), nullptr, 0);\n        if (!NT_SUCCESS(Status))\n            throw ntstatus_error(Status);\n    } catch (const exception& e) {\n        error_message(hwnd, e.what());\n    }\n}\n\nvoid CALLBACK ShowChangeDriveLetterW(HWND hwnd, HINSTANCE, LPWSTR lpszCmdLine, int) {\n    BtrfsChangeDriveLetter bcdl(hwnd, lpszCmdLine);\n\n    bcdl.show();\n}\n\n}\n"
  },
  {
    "path": "src/shellext/volpropsheet.h",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\n#include <shlobj.h>\n#include \"../btrfsioctl.h\"\n#include \"../btrfs.h\"\n#include \"balance.h\"\n#include \"scrub.h\"\n\nextern LONG objs_loaded;\n\nclass BtrfsVolPropSheet : public IShellExtInit, IShellPropSheetExt {\npublic:\n    BtrfsVolPropSheet() {\n        refcount = 0;\n        ignore = true;\n        stgm_set = false;\n        devices = nullptr;\n\n        InterlockedIncrement(&objs_loaded);\n\n        balance = nullptr;\n    }\n\n    virtual ~BtrfsVolPropSheet() {\n        if (stgm_set)\n            ReleaseStgMedium(&stgm);\n\n        if (devices)\n            free(devices);\n\n        InterlockedDecrement(&objs_loaded);\n\n        if (balance)\n            delete balance;\n    }\n\n    // IUnknown\n\n    HRESULT __stdcall QueryInterface(REFIID riid, void **ppObj);\n\n    ULONG __stdcall AddRef() {\n        return InterlockedIncrement(&refcount);\n    }\n\n    ULONG __stdcall Release() {\n        LONG rc = InterlockedDecrement(&refcount);\n\n        if (rc == 0)\n            delete this;\n\n        return rc;\n    }\n\n    // IShellExtInit\n\n    virtual HRESULT __stdcall Initialize(PCIDLIST_ABSOLUTE pidlFolder, IDataObject* pdtobj, HKEY hkeyProgID) override;\n\n    // IShellPropSheetExt\n\n    virtual HRESULT __stdcall AddPages(LPFNADDPROPSHEETPAGE pfnAddPage, LPARAM lParam) override;\n    virtual HRESULT __stdcall ReplacePage(UINT uPageID, LPFNADDPROPSHEETPAGE pfnReplacePage, LPARAM lParam) override;\n\n    void FormatUsage(HWND hwndDlg, wstring& s, btrfs_usage* usage);\n    void RefreshUsage(HWND hwndDlg);\n    void ShowUsage(HWND hwndDlg);\n    INT_PTR CALLBACK UsageDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);\n    void RefreshDevList(HWND devlist);\n    INT_PTR CALLBACK DeviceDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);\n    void ShowDevices(HWND hwndDlg);\n    void ShowScrub(HWND hwndDlg);\n    void ShowChangeDriveLetter(HWND hwndDlg);\n    INT_PTR CALLBACK StatsDlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);\n    void ShowStats(HWND hwndDlg, uint64_t devid);\n    void ResetStats(HWND hwndDlg);\n\n    btrfs_device* devices;\n    bool readonly;\n    BtrfsBalance* balance;\n    BTRFS_UUID uuid;\n    bool uuid_set;\n\nprivate:\n    LONG refcount;\n    bool ignore;\n    STGMEDIUM stgm;\n    bool stgm_set;\n    wstring fn;\n    uint64_t stats_dev;\n};\n\nclass BtrfsChangeDriveLetter {\npublic:\n    BtrfsChangeDriveLetter(HWND hwnd, wstring_view fn) : hwnd(hwnd), fn(fn) {\n    }\n\n    void show();\n    INT_PTR DlgProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);\n\nprivate:\n    void do_change(HWND hwndDlg);\n\n    HWND hwnd;\n    wstring fn;\n    vector<wchar_t> letters;\n};\n"
  },
  {
    "path": "src/tests/create.cpp",
    "content": "#include \"test.h\"\n#include <array>\n\n#define FSCTL_CREATE_OR_GET_OBJECT_ID CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 48, METHOD_BUFFERED, FILE_ANY_ACCESS)\n\nusing namespace std;\n\nstatic OBJECT_BASIC_INFORMATION query_object_basic_information(HANDLE h) {\n    NTSTATUS Status;\n    OBJECT_BASIC_INFORMATION obi;\n    ULONG len;\n\n    Status = NtQueryObject(h, ObjectBasicInformation, &obi, sizeof(obi), &len);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (len != sizeof(obi))\n        throw formatted_error(\"returned length was {}, expected {}\", len, sizeof(obi));\n\n    return obi;\n}\n\ntemplate<typename T>\nconcept has_CreationTime = requires { T::CreationTime; };\n\ntemplate<typename T>\nconcept has_LastAccessTime = requires { T::LastAccessTime; };\n\ntemplate<typename T>\nconcept has_LastWriteTime = requires { T::LastWriteTime; };\n\ntemplate<typename T>\nconcept has_ChangeTime = requires { T::ChangeTime; };\n\ntemplate<typename T>\nconcept has_EndOfFile = requires { T::EndOfFile; };\n\ntemplate<typename T>\nconcept has_AllocationSize = requires { T::AllocationSize; };\n\ntemplate<typename T>\nconcept has_FileAttributes = requires { T::FileAttributes; };\n\ntemplate<typename T>\nconcept has_FileNameLength = requires { T::FileNameLength; };\n\ntemplate<typename T>\nconcept has_FileId = requires { T::FileId; };\n\ntemplate<typename T>\nstatic void check_dir_entry(const u16string& dir, u16string_view name,\n                            const FILE_BASIC_INFORMATION& fbi, const FILE_STANDARD_INFORMATION& fsi,\n                            int64_t file_id, const FILE_ID_128& file_id_128) {\n    auto items = query_dir<T>(dir, name);\n\n    if (items.size() != 1)\n        throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n    auto& fdi = *static_cast<const T*>(items.front());\n\n    if constexpr (has_CreationTime<T>) {\n        if (fdi.CreationTime.QuadPart != fbi.CreationTime.QuadPart)\n            throw formatted_error(\"CreationTime was {}, expected {}.\", fdi.CreationTime.QuadPart, fbi.CreationTime.QuadPart);\n    }\n\n    if constexpr (has_LastAccessTime<T>) {\n        if (fdi.LastAccessTime.QuadPart != fbi.LastAccessTime.QuadPart)\n            throw formatted_error(\"LastAccessTime was {}, expected {}.\", fdi.LastAccessTime.QuadPart, fbi.LastAccessTime.QuadPart);\n    }\n\n    if constexpr (has_LastWriteTime<T>) {\n        if (fdi.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart)\n            throw formatted_error(\"LastWriteTime was {}, expected {}.\", fdi.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart);\n    }\n\n    if constexpr (has_ChangeTime<T>) {\n        if (fdi.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart)\n            throw formatted_error(\"ChangeTime was {}, expected {}.\", fdi.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart);\n    }\n\n    if constexpr (has_EndOfFile<T>) {\n        if (fdi.EndOfFile.QuadPart != fsi.EndOfFile.QuadPart)\n            throw formatted_error(\"EndOfFile was {}, expected {}.\", fdi.EndOfFile.QuadPart, fsi.EndOfFile.QuadPart);\n    }\n\n    if constexpr (has_AllocationSize<T>) {\n        if (fdi.AllocationSize.QuadPart != fsi.AllocationSize.QuadPart)\n            throw formatted_error(\"AllocationSize was {}, expected {}.\", fdi.AllocationSize.QuadPart, fsi.AllocationSize.QuadPart);\n    }\n\n    if constexpr (has_FileAttributes<T>) {\n        if (fdi.FileAttributes != fbi.FileAttributes)\n            throw formatted_error(\"FileAttributes was {}, expected {}.\", fdi.FileAttributes, fbi.FileAttributes);\n    }\n\n    if constexpr (has_FileNameLength<T>) {\n        if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n            throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n        if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n            throw runtime_error(\"FileName did not match.\");\n    }\n\n    if constexpr (has_FileId<T>) {\n        if constexpr (sizeof(T::FileId) == 8) {\n            if (fdi.FileId.QuadPart != file_id)\n                throw formatted_error(\"FileId was {:x}, expected {:x}.\", fdi.FileId.QuadPart, file_id);\n        } else {\n            if (memcmp(&fdi.FileId, &file_id_128, sizeof(FILE_ID_128)))\n                throw runtime_error(\"FileId was not as expected.\");\n        }\n    }\n\n    // FIXME - EaSize\n    // FIXME - ShortNameLength / ShortName\n    // FIXME - ReparsePointTag\n}\n\nvoid test_create(HANDLE token, const u16string& dir) {\n    unique_handle h;\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\file\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create duplicate file\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\file\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        test(\"Create file differing in case\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\FILE\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        FILE_BASIC_INFORMATION fbi;\n\n        test(\"Check attributes\", [&]() {\n            fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        FILE_STANDARD_INFORMATION fsi;\n\n        test(\"Check standard information\", [&]() {\n            fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.AllocationSize.QuadPart != 0)\n                throw formatted_error(\"AllocationSize was {}, expected 0\", fsi.AllocationSize.QuadPart);\n\n            if (fsi.EndOfFile.QuadPart != 0)\n                throw formatted_error(\"EndOfFile was {}, expected 0\", fsi.EndOfFile.QuadPart);\n\n            if (fsi.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi.NumberOfLinks);\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsi.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\file\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\file\\\".\");\n        });\n\n        test(\"Check access information\", [&]() {\n            auto fai = query_information<FILE_ACCESS_INFORMATION>(h.get());\n\n            ACCESS_MASK exp = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE |\n                              FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD |\n                              FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA |\n                              FILE_WRITE_DATA | FILE_READ_DATA;\n\n            if (fai.AccessFlags != exp)\n                throw formatted_error(\"AccessFlags was {:x}, expected {:x}\", fai.AccessFlags, exp);\n        });\n\n        test(\"Check mode information\", [&]() {\n            auto fmi = query_information<FILE_MODE_INFORMATION>(h.get());\n\n            if (fmi.Mode != 0)\n                throw formatted_error(\"Mode was {:x}, expected 0\", fmi.Mode);\n        });\n\n        test(\"Check alignment information\", [&]() {\n            auto fai = query_information<FILE_ALIGNMENT_INFORMATION>(h.get());\n\n            if (fai.AlignmentRequirement != FILE_WORD_ALIGNMENT)\n                throw formatted_error(\"AlignmentRequirement was {:x}, expected FILE_WORD_ALIGNMENT\", fai.AlignmentRequirement);\n        });\n\n        test(\"Check position information\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if (fpi.CurrentByteOffset.QuadPart != 0)\n                throw formatted_error(\"CurrentByteOffset was {:x}, expected 0\", fpi.CurrentByteOffset.QuadPart);\n        });\n\n        test(\"Check attribute tag information\", [&]() {\n            auto fati = query_information<FILE_ATTRIBUTE_TAG_INFORMATION>(h.get());\n\n            if (fati.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE\",\n                                      fati.FileAttributes);\n            }\n\n            if (fati.ReparseTag != 0)\n                throw formatted_error(\"ReparseTag was {:08x}, expected 0\", fati.ReparseTag);\n        });\n\n        test(\"Check compression information\", [&]() {\n            auto fci = query_information<FILE_COMPRESSION_INFORMATION>(h.get());\n\n            if (fci.CompressedFileSize.QuadPart != 0)\n                throw formatted_error(\"CompressedFileSize was {}, expected 0\", fci.CompressedFileSize.QuadPart);\n\n            if (fci.CompressionFormat != 0)\n                throw formatted_error(\"CompressionFormat was {}, expected 0\", fci.CompressionFormat);\n\n            if (fci.CompressionUnitShift != 0)\n                throw formatted_error(\"CompressionUnitShift was {}, expected 0\", fci.CompressionUnitShift);\n\n            if (fci.ChunkShift != 0)\n                throw formatted_error(\"ChunkShift was {}, expected 0\", fci.ChunkShift);\n\n            if (fci.ClusterShift != 0)\n                throw formatted_error(\"ClusterShift was {}, expected 0\", fci.ClusterShift);\n        });\n\n        test(\"Check EA information\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != 0)\n                throw formatted_error(\"EaSize was {}, expected 0\", feai.EaSize);\n        });\n\n        int64_t file_id = 0;\n\n        test(\"Check internal information\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file_id = fii.IndexNumber.QuadPart;\n        });\n\n        test(\"Check network open information\", [&]() {\n            auto fnoi = query_information<FILE_NETWORK_OPEN_INFORMATION>(h.get());\n\n            if (fnoi.CreationTime.QuadPart != fbi.CreationTime.QuadPart)\n                throw formatted_error(\"CreationTime was {}, expected {}\", fnoi.CreationTime.QuadPart, fbi.CreationTime.QuadPart);\n\n            if (fnoi.LastAccessTime.QuadPart != fbi.LastAccessTime.QuadPart)\n                throw formatted_error(\"LastAccessTime was {}, expected {}\", fnoi.LastAccessTime.QuadPart, fbi.LastAccessTime.QuadPart);\n\n            if (fnoi.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart)\n                throw formatted_error(\"LastWriteTime was {}, expected {}\", fnoi.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart);\n\n            if (fnoi.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart)\n                throw formatted_error(\"ChangeTime was {}, expected {}\", fnoi.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart);\n\n            if (fnoi.AllocationSize.QuadPart != fsi.AllocationSize.QuadPart)\n                throw formatted_error(\"AllocationSize was {}, expected {}\", fnoi.AllocationSize.QuadPart, fsi.AllocationSize.QuadPart);\n\n            if (fnoi.EndOfFile.QuadPart != fsi.EndOfFile.QuadPart)\n                throw formatted_error(\"EndOfFile was {}, expected {}\", fnoi.EndOfFile.QuadPart, fsi.EndOfFile.QuadPart);\n\n            if (fnoi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE\",\n                                      fnoi.FileAttributes);\n            }\n        });\n\n        test(\"Try to check normalized name\", [&]() { // needs traverse privilege\n            exp_status([&]() {\n                query_file_name_information(h.get(), true);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Check FileStatInformation\", [&]() {\n            auto fsi2 = query_information<FILE_STAT_INFORMATION>(h.get());\n\n            if (fsi2.FileId.QuadPart != file_id)\n                throw formatted_error(\"FileId was {}, expected {}\", fsi2.FileId.QuadPart, file_id);\n\n            if (fsi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart)\n                throw formatted_error(\"CreationTime was {}, expected {}\", fsi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart);\n\n            if (fsi2.LastAccessTime.QuadPart != fbi.LastAccessTime.QuadPart)\n                throw formatted_error(\"LastAccessTime was {}, expected {}\", fsi2.LastAccessTime.QuadPart, fbi.LastAccessTime.QuadPart);\n\n            if (fsi2.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart)\n                throw formatted_error(\"LastWriteTime was {}, expected {}\", fsi2.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart);\n\n            if (fsi2.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart)\n                throw formatted_error(\"ChangeTime was {}, expected {}\", fsi2.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart);\n\n            if (fsi2.AllocationSize.QuadPart != fsi.AllocationSize.QuadPart)\n                throw formatted_error(\"AllocationSize was {}, expected {}\", fsi2.AllocationSize.QuadPart, fsi.AllocationSize.QuadPart);\n\n            if (fsi2.EndOfFile.QuadPart != fsi.EndOfFile.QuadPart)\n                throw formatted_error(\"EndOfFile was {}, expected {}\", fsi2.EndOfFile.QuadPart, fsi.EndOfFile.QuadPart);\n\n            if (fsi2.FileAttributes != FILE_ATTRIBUTE_ARCHIVE)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_ARCHIVE\", fsi2.FileAttributes);\n\n            if (fsi2.ReparseTag != 0)\n                throw formatted_error(\"ReparseTag was {:08x}, expected 0\", fsi2.ReparseTag);\n\n            if (fsi2.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi2.NumberOfLinks);\n\n            ACCESS_MASK exp = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE |\n                              FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD |\n                              FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA |\n                              FILE_WRITE_DATA | FILE_READ_DATA;\n\n            if (fsi2.EffectiveAccess != exp)\n                throw formatted_error(\"EffectiveAccess was {:x}, expected {:x}\", fsi2.EffectiveAccess, exp);\n        });\n\n        test(\"Check FileStatLxInformation\", [&]() {\n            auto fsli = query_information<FILE_STAT_LX_INFORMATION>(h.get());\n\n            if (fsli.FileId.QuadPart != file_id)\n                throw formatted_error(\"FileId was {}, expected {}\", fsli.FileId.QuadPart, file_id);\n\n            if (fsli.CreationTime.QuadPart != fbi.CreationTime.QuadPart)\n                throw formatted_error(\"CreationTime was {}, expected {}\", fsli.CreationTime.QuadPart, fbi.CreationTime.QuadPart);\n\n            if (fsli.LastAccessTime.QuadPart != fbi.LastAccessTime.QuadPart)\n                throw formatted_error(\"LastAccessTime was {}, expected {}\", fsli.LastAccessTime.QuadPart, fbi.LastAccessTime.QuadPart);\n\n            if (fsli.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart)\n                throw formatted_error(\"LastWriteTime was {}, expected {}\", fsli.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart);\n\n            if (fsli.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart)\n                throw formatted_error(\"ChangeTime was {}, expected {}\", fsli.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart);\n\n            if (fsli.AllocationSize.QuadPart != fsi.AllocationSize.QuadPart)\n                throw formatted_error(\"AllocationSize was {}, expected {}\", fsli.AllocationSize.QuadPart, fsi.AllocationSize.QuadPart);\n\n            if (fsli.EndOfFile.QuadPart != fsi.EndOfFile.QuadPart)\n                throw formatted_error(\"EndOfFile was {}, expected {}\", fsli.EndOfFile.QuadPart, fsi.EndOfFile.QuadPart);\n\n            if (fsli.FileAttributes != FILE_ATTRIBUTE_ARCHIVE)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_ARCHIVE\", fsli.FileAttributes);\n\n            if (fsli.ReparseTag != 0)\n                throw formatted_error(\"ReparseTag was {:08x}, expected 0\", fsli.ReparseTag);\n\n            if (fsli.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsli.NumberOfLinks);\n\n            ACCESS_MASK exp = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE |\n                              FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD |\n                              FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA |\n                              FILE_WRITE_DATA | FILE_READ_DATA;\n\n            if (fsli.EffectiveAccess != exp)\n                throw formatted_error(\"EffectiveAccess was {:x}, expected {:x}\", fsli.EffectiveAccess, exp);\n        });\n\n        FILE_ID_128 file_id_128 = {};\n\n        test(\"Check FileIdInformation\", [&]() {\n            auto fidi = query_information<FILE_ID_INFORMATION>(h.get());\n\n            file_id_128 = fidi.FileId;\n        });\n\n        test(\"Check FileAllInformation\", [&]() {\n            auto buf = query_all_information(h.get());\n\n            auto& fai = *static_cast<FILE_ALL_INFORMATION*>(buf);\n\n            if (fai.BasicInformation.CreationTime.QuadPart != fbi.CreationTime.QuadPart)\n                throw formatted_error(\"BasicInformation.CreationTime was {}, expected {}\", fai.BasicInformation.CreationTime.QuadPart, fbi.CreationTime.QuadPart);\n\n            if (fai.BasicInformation.LastAccessTime.QuadPart != fbi.LastAccessTime.QuadPart)\n                throw formatted_error(\"BasicInformation.LastAccessTime was {}, expected {}\", fai.BasicInformation.LastAccessTime.QuadPart, fbi.LastAccessTime.QuadPart);\n\n            if (fai.BasicInformation.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart)\n                throw formatted_error(\"BasicInformation.LastWriteTime was {}, expected {}\", fai.BasicInformation.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart);\n\n            if (fai.BasicInformation.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart)\n                throw formatted_error(\"BasicInformation.ChangeTime was {}, expected {}\", fai.BasicInformation.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart);\n\n            if (fai.BasicInformation.FileAttributes != fbi.FileAttributes)\n                throw formatted_error(\"BasicInformation.FileAttributes was {:x}, expected {:x}\", fai.BasicInformation.FileAttributes, fbi.FileAttributes);\n\n            if (fai.StandardInformation.AllocationSize.QuadPart != fsi.AllocationSize.QuadPart)\n                throw formatted_error(\"StandardInformation.AllocationSize was {}, expected {}\", fai.StandardInformation.AllocationSize.QuadPart, fsi.AllocationSize.QuadPart);\n\n            if (fai.StandardInformation.EndOfFile.QuadPart != fsi.EndOfFile.QuadPart)\n                throw formatted_error(\"StandardInformation.EndOfFile was {}, expected {}\", fai.StandardInformation.EndOfFile.QuadPart, fsi.EndOfFile.QuadPart);\n\n            if (fai.StandardInformation.NumberOfLinks != fsi.NumberOfLinks)\n                throw formatted_error(\"StandardInformation.NumberOfLinks was {}, expected {}\", fai.StandardInformation.NumberOfLinks, fsi.NumberOfLinks);\n\n            if (!!fai.StandardInformation.DeletePending != !!fsi.DeletePending)\n                throw formatted_error(\"StandardInformation.DeletePending was {}, expected {}\", fai.StandardInformation.DeletePending, fsi.DeletePending);\n\n            if (!!fai.StandardInformation.Directory != !!fsi.Directory)\n                throw formatted_error(\"StandardInformation.Directory was {}, expected {}\", fai.StandardInformation.Directory, fsi.Directory);\n\n            if (fai.InternalInformation.IndexNumber.QuadPart != file_id)\n                throw formatted_error(\"InternalInformation.IndexNumber was {:x}, expected {:x}\", fai.InternalInformation.IndexNumber.QuadPart, file_id);\n\n            if (fai.EaInformation.EaSize != 0)\n                throw formatted_error(\"EaInformation.EaSize was {:x}, expected 0\", fai.EaInformation.EaSize);\n\n            ACCESS_MASK exp_access = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE |\n                                     FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD |\n                                     FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA |\n                                     FILE_WRITE_DATA | FILE_READ_DATA;\n\n            if (fai.AccessInformation.AccessFlags != exp_access)\n                throw formatted_error(\"AccessInformation.AccessFlags was {:x}, expected {:x}\", fai.AccessInformation.AccessFlags, exp_access);\n\n            if (fai.PositionInformation.CurrentByteOffset.QuadPart != 0)\n                throw formatted_error(\"PositionInformation.CurrentByteOffset was {:x}, expected 0\", fai.PositionInformation.CurrentByteOffset.QuadPart);\n\n            if (fai.ModeInformation.Mode != 0)\n                throw formatted_error(\"ModeInformation.Mode was {:x}, expected 0\", fai.ModeInformation.Mode);\n\n            if (fai.AlignmentInformation.AlignmentRequirement != FILE_WORD_ALIGNMENT)\n                throw formatted_error(\"AlignmentInformation.AlignmentRequirement was {:x}, expected FILE_WORD_ALIGNMENT\", fai.AlignmentInformation.AlignmentRequirement);\n\n            auto fn = u16string_view((char16_t*)fai.NameInformation.FileName, fai.NameInformation.FileNameLength / sizeof(char16_t));\n\n            static const u16string_view ends_with = u\"\\\\file\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\file\\\".\");\n        });\n\n        test(\"Check FILE_STANDARD_INFORMATION_EX\", [&]() {\n            auto fsix = query_information<FILE_STANDARD_INFORMATION_EX>(h.get());\n\n            if (fsix.AllocationSize.QuadPart != 0)\n                throw formatted_error(\"AllocationSize was {}, expected 0\", fsix.AllocationSize.QuadPart);\n\n            if (fsix.EndOfFile.QuadPart != 0)\n                throw formatted_error(\"EndOfFile was {}, expected 0\", fsix.EndOfFile.QuadPart);\n\n            if (fsix.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsix.NumberOfLinks);\n\n            if (fsix.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsix.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n\n            if (fsix.AlternateStream)\n                throw runtime_error(\"AlternateStream was true, expected false\");\n\n            if (fsix.MetadataAttribute)\n                throw runtime_error(\"MetadataAttribute was true, expected false\");\n        });\n\n        // FIXME - FileHardLinkFullIdInformation (does this work? Undocumented, and seems to always return STATUS_INVALID_PARAMETER on NTFS for 21H2)\n        // FIXME - FileAlternateNameInformation\n        // FIXME - FileSfioReserveInformation\n        // FIXME - FileDesiredStorageClassInformation\n        // FIXME - FileStorageReserveIdInformation\n        // FIXME - FileKnownFolderInformation\n\n        static const u16string_view name = u\"file\";\n\n        test(\"Check directory entry (FILE_DIRECTORY_INFORMATION)\", [&]() {\n            check_dir_entry<FILE_DIRECTORY_INFORMATION>(dir, name, fbi, fsi, file_id, file_id_128);\n        });\n\n        test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n            check_dir_entry<FILE_BOTH_DIR_INFORMATION>(dir, name, fbi, fsi, file_id, file_id_128);\n        });\n\n        test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n            check_dir_entry<FILE_FULL_DIR_INFORMATION>(dir, name, fbi, fsi, file_id, file_id_128);\n        });\n\n        test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n            check_dir_entry<FILE_ID_BOTH_DIR_INFORMATION>(dir, name, fbi, fsi, file_id, file_id_128);\n        });\n\n        test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n            check_dir_entry<FILE_ID_FULL_DIR_INFORMATION>(dir, name, fbi, fsi, file_id, file_id_128);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n            check_dir_entry<FILE_ID_EXTD_DIR_INFORMATION>(dir, name, fbi, fsi, file_id, file_id_128);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n            check_dir_entry<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, name, fbi, fsi, file_id, file_id_128);\n        });\n\n        test(\"Check directory entry (FILE_NAMES_INFORMATION)\", [&]() {\n            check_dir_entry<FILE_NAMES_INFORMATION>(dir, name, fbi, fsi, file_id, file_id_128);\n        });\n\n        test(\"Check granted access\", [&]() {\n            auto obi = query_object_basic_information(h.get());\n\n            ACCESS_MASK exp = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE |\n                              FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD |\n                              FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA |\n                              FILE_WRITE_DATA | FILE_READ_DATA;\n\n            if (obi.GrantedAccess != exp)\n                throw formatted_error(\"granted access was {:x}, expected {:x}\", obi.GrantedAccess, exp);\n        });\n\n        h.reset();\n    }\n\n    // traverse privilege needed for FileNormalizedNameInformation\n    test(\"Add SeChangeNotifyPrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\file\", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Check normalized name\", [&]() {\n            auto fn = query_file_name_information(h.get(), true);\n\n            static const u16string_view ends_with = u\"\\\\file\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\file\\\".\");\n        });\n    }\n\n    disable_token_privileges(token);\n\n    test(\"Create file (FILE_NON_DIRECTORY_FILE)\", [&]() {\n        h = create_file(dir + u\"\\\\file2\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.AllocationSize.QuadPart != 0)\n                throw formatted_error(\"AllocationSize was {}, expected 0\", fsi.AllocationSize.QuadPart);\n\n            if (fsi.EndOfFile.QuadPart != 0)\n                throw formatted_error(\"EndOfFile was {}, expected 0\", fsi.EndOfFile.QuadPart);\n\n            if (fsi.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi.NumberOfLinks);\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsi.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file (FILE_NON_DIRECTORY_FILE, FILE_ATTRIBUTE_DIRECTORY)\", [&]() {\n        h = create_file(dir + u\"\\\\file3\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_DIRECTORY, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.AllocationSize.QuadPart != 0)\n                throw formatted_error(\"AllocationSize was {}, expected 0\", fsi.AllocationSize.QuadPart);\n\n            if (fsi.EndOfFile.QuadPart != 0)\n                throw formatted_error(\"EndOfFile was {}, expected 0\", fsi.EndOfFile.QuadPart);\n\n            if (fsi.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi.NumberOfLinks);\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsi.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory (FILE_DIRECTORY_FILE)\", [&]() {\n        h = create_file(dir + u\"\\\\dir\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_DIRECTORY\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.AllocationSize.QuadPart != 0)\n                throw formatted_error(\"AllocationSize was {}, expected 0\", fsi.AllocationSize.QuadPart);\n\n            if (fsi.EndOfFile.QuadPart != 0)\n                throw formatted_error(\"EndOfFile was {}, expected 0\", fsi.EndOfFile.QuadPart);\n\n            if (fsi.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi.NumberOfLinks);\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (!fsi.Directory)\n                throw runtime_error(\"Directory was false, expected true\");\n        });\n\n        test(\"Check granted access\", [&]() {\n            auto obi = query_object_basic_information(h.get());\n\n            ACCESS_MASK exp = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE |\n                              FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD |\n                              FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA |\n                              FILE_WRITE_DATA | FILE_READ_DATA;\n\n            if (obi.GrantedAccess != exp)\n                throw formatted_error(\"granted access was {:x}, expected {:x}\", obi.GrantedAccess, exp);\n        });\n\n        h.reset();\n\n        test(\"Open directory\", [&]() {\n            create_file(dir + u\"\\\\dir\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n        });\n    }\n\n    test(\"Create file (FILE_ATTRIBUTE_DIRECTORY)\", [&]() {\n        h = create_file(dir + u\"\\\\file4\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_DIRECTORY, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.AllocationSize.QuadPart != 0)\n                throw formatted_error(\"AllocationSize was {}, expected 0\", fsi.AllocationSize.QuadPart);\n\n            if (fsi.EndOfFile.QuadPart != 0)\n                throw formatted_error(\"EndOfFile was {}, expected 0\", fsi.EndOfFile.QuadPart);\n\n            if (fsi.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi.NumberOfLinks);\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsi.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file (FILE_ATTRIBUTE_HIDDEN)\", [&]() {\n        h = create_file(dir + u\"\\\\filehidden\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_HIDDEN)) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_HIDDEN\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file (FILE_ATTRIBUTE_READONLY)\", [&]() {\n        h = create_file(dir + u\"\\\\filero\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_READONLY)) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_READONLY\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file (FILE_ATTRIBUTE_SYSTEM)\", [&]() {\n        h = create_file(dir + u\"\\\\filesys\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_SYSTEM, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_SYSTEM)) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_SYSTEM\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file (FILE_ATTRIBUTE_NORMAL)\", [&]() {\n        h = create_file(dir + u\"\\\\filenormal\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory (FILE_ATTRIBUTE_HIDDEN)\", [&]() {\n        h = create_file(dir + u\"\\\\dirhidden\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN)) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory (FILE_ATTRIBUTE_READONLY)\", [&]() {\n        h = create_file(dir + u\"\\\\dirro\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY)) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory (FILE_ATTRIBUTE_SYSTEM)\", [&]() {\n        h = create_file(dir + u\"\\\\dirsys\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_SYSTEM, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM)) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_SYSTEM\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory (FILE_ATTRIBUTE_NORMAL)\", [&]() {\n        h = create_file(dir + u\"\\\\dirnormal\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_NORMAL, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_DIRECTORY\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file (FILE_SHARE_READ)\", [&]() {\n        h = create_file(dir + u\"\\\\fileshareread\", FILE_READ_DATA, 0, FILE_SHARE_READ, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Open for read\", [&]() {\n            create_file(dir + u\"\\\\fileshareread\", FILE_READ_DATA, 0, FILE_SHARE_READ, FILE_OPEN,\n                        0, FILE_OPENED);\n        });\n\n        test(\"Open for write\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\fileshareread\", FILE_WRITE_DATA, 0, FILE_SHARE_READ, FILE_OPEN,\n                            0, FILE_OPENED);\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        test(\"Open for delete\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\fileshareread\", DELETE, 0, FILE_SHARE_READ, FILE_OPEN,\n                            0, FILE_OPENED);\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file (FILE_SHARE_WRITE)\", [&]() {\n        h = create_file(dir + u\"\\\\filesharewrite\", FILE_WRITE_DATA, 0, FILE_SHARE_WRITE, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Open for read\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\filesharewrite\", FILE_READ_DATA, 0, FILE_SHARE_WRITE, FILE_OPEN,\n                            0, FILE_OPENED);\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        test(\"Open for write\", [&]() {\n            create_file(dir + u\"\\\\filesharewrite\", FILE_WRITE_DATA, 0, FILE_SHARE_WRITE, FILE_OPEN,\n                        0, FILE_OPENED);\n        });\n\n        test(\"Open for delete\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\filesharewrite\", DELETE, 0, FILE_SHARE_WRITE, FILE_OPEN,\n                            0, FILE_OPENED);\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file (FILE_SHARE_DELETE)\", [&]() {\n        h = create_file(dir + u\"\\\\filesharedelete\", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Open for read\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\filesharedelete\", FILE_READ_DATA, 0, FILE_SHARE_DELETE, FILE_OPEN,\n                            0, FILE_OPENED);\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        test(\"Open for write\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\filesharedelete\", FILE_WRITE_DATA, 0, FILE_SHARE_DELETE, FILE_OPEN,\n                            0, FILE_OPENED);\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        test(\"Open for delete\", [&]() {\n            create_file(dir + u\"\\\\filesharedelete\", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN,\n                        0, FILE_OPENED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file in invalid path\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\nosuchdir\\\\file\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE,\n                        FILE_CREATED);\n        }, STATUS_OBJECT_PATH_NOT_FOUND);\n    });\n\n    test(\"Create directory in invalid path\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\nosuchdir\\\\file\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE,\n                        FILE_CREATED);\n        }, STATUS_OBJECT_PATH_NOT_FOUND);\n    });\n\n    test(\"Create file with FILE_OPEN_IF\", [&]() {\n        h = create_file(dir + u\"\\\\openif\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN_IF, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        h.reset();\n\n        test(\"Open file with FILE_OPEN_IF\", [&]() {\n            create_file(dir + u\"\\\\openif\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN_IF, 0, FILE_OPENED);\n        });\n    }\n\n    test(\"Create file with long name\", [&]() {\n        u16string longname(256, u'x');\n\n        exp_status([&]() {\n            create_file(dir + u\"\\\\\" + longname, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        }, STATUS_OBJECT_NAME_INVALID);\n    });\n\n    test(\"Create file with emoji\", [&]() {\n        create_file(dir + u\"\\\\\\U0001f525\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        create_file(dir + u\"\\\\notadir\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Try to create file within other file\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\notadir\\\\file\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        }, STATUS_OBJECT_PATH_NOT_FOUND);\n    });\n\n    test(\"Try to open file within other file\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\notadir\\\\file\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n        }, STATUS_OBJECT_PATH_NOT_FOUND);\n    });\n\n    /* The limits for Btrfs are more stringent than NTFS, to make sure we don't\n     * create a filename that will confuse Linux. */\n    bool is_ntfs = fstype == fs_type::ntfs;\n\n    test(\"Create file with more than 255 UTF-8 characters\", [&]() {\n        auto fn = dir + u\"\\\\\";\n\n        for (unsigned int i = 0; i < 64; i++) {\n            fn += u\"\\U0001f525\";\n        }\n\n        exp_status([&]() {\n            create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n    });\n\n    test(\"Create file with WTF-16 (1)\", [&]() {\n        auto fn = dir + u\"\\\\\";\n\n        fn += (char16_t)0xd83d;\n\n        exp_status([&]() {\n            create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n    });\n\n    test(\"Create file with WTF-16 (2)\", [&]() {\n        auto fn = dir + u\"\\\\\";\n\n        fn += (char16_t)0xdd25;\n\n        exp_status([&]() {\n            create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n    });\n\n    test(\"Create file with WTF-16 (3)\", [&]() {\n        auto fn = dir + u\"\\\\\";\n\n        fn += (char16_t)0xdd25;\n        fn += (char16_t)0xd83d;\n\n        exp_status([&]() {\n            create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n    });\n\n    struct {\n        u16string name;\n        string desc;\n    } invalid_names[] = {\n        { u\"/\", \"slash\" },\n        { u\":\", \"colon\" },\n        { u\"<\", \"less than\" },\n        { u\">\", \"greater than\" },\n        { u\"\\\"\", \"quote\" },\n        { u\"|\", \"pipe\" },\n        { u\"?\", \"question mark\" },\n        { u\"*\", \"asterisk\" }\n    };\n\n    for (const auto& n : invalid_names) {\n        test(\"Create file with invalid name (\" + n.desc + \")\", [&]() {\n            auto fn = dir + u\"\\\\\" + n.name;\n\n            exp_status([&]() {\n                create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n            }, STATUS_OBJECT_NAME_INVALID);\n        });\n    }\n\n    test(\"Create file called CON\", [&]() { // allowed by NT API, not allowed by Win32 API\n        create_file(dir + u\"\\\\CON\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    // FIXME - if we try to open file with invalid name, do we get NOT_FOUND or INVALID?\n\n    // FIXME - test all the variations of NtQueryInformationFile\n    // FIXME - test NtOpenFile\n\n    // FIXME - permissions needed to create file or subdirectory\n    // FIXME - permissions needed when overwriting\n    // FIXME - what exactly does FILE_DELETE_CHILD do?\n    // FIXME - overwriting mapped file\n}\n\ntemplate<typename T>\nrequires (is_same_v<T, uint64_t> || is_same_v<T, array<uint8_t, 16>>)\nstatic unique_handle open_by_id(HANDLE dir, const T& id, ACCESS_MASK access, ULONG atts, ULONG share,\n                                ULONG dispo, ULONG options, ULONG_PTR exp_info, optional<uint64_t> allocation = nullopt) {\n    NTSTATUS Status;\n    HANDLE h;\n    IO_STATUS_BLOCK iosb;\n    UNICODE_STRING us;\n    OBJECT_ATTRIBUTES oa;\n    LARGE_INTEGER alloc_size;\n\n    oa.Length = sizeof(oa);\n    oa.RootDirectory = dir;\n\n    if constexpr (is_same_v<T, array<uint8_t, 16>>) {\n        us.Length = us.MaximumLength = id.size();\n        us.Buffer = (WCHAR*)id.data();\n    } else {\n        us.Length = us.MaximumLength = sizeof(id);\n        us.Buffer = (WCHAR*)&id;\n    }\n\n    oa.ObjectName = &us;\n\n    oa.Attributes = 0;\n    oa.SecurityDescriptor = nullptr;\n    oa.SecurityQualityOfService = nullptr;\n\n    if (allocation)\n        alloc_size.QuadPart = allocation.value();\n\n    iosb.Information = 0xdeadbeef;\n\n    Status = NtCreateFile(&h, access, &oa, &iosb, allocation ? &alloc_size : nullptr,\n                          atts, share, dispo, options | FILE_OPEN_BY_FILE_ID, nullptr, 0);\n\n    if (Status != STATUS_SUCCESS) {\n        if (NT_SUCCESS(Status)) // STATUS_OPLOCK_BREAK_IN_PROGRESS etc.\n            NtClose(h);\n\n        throw ntstatus_error(Status);\n    }\n\n    if (iosb.Information != exp_info)\n        throw formatted_error(\"iosb.Information was {}, expected {}\", iosb.Information, exp_info);\n\n    return unique_handle(h);\n}\n\nstatic array<uint8_t, 16> create_or_get_object_id(HANDLE h) {\n    NTSTATUS Status;\n    FILE_OBJECTID_BUFFER foib;\n    IO_STATUS_BLOCK iosb;\n\n    auto ev = create_event();\n\n    Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb,\n                             FSCTL_CREATE_OR_GET_OBJECT_ID, nullptr, 0,\n                             &foib, sizeof(foib));\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(ev.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    array<uint8_t, 16> ret;\n\n    memcpy(ret.data(), foib.ObjectId, 16);\n\n    return ret;\n}\n\nvoid test_open_id(HANDLE token, const u16string& dir) {\n    unique_handle h, dirh;\n    uint64_t file_id = 0;\n    auto random = random_data(4096);\n\n    // traverse privilege needed to query filename and hard links\n    test(\"Add SeChangeNotifyPrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\id1\", SYNCHRONIZE | FILE_WRITE_DATA, 0, 0, FILE_CREATE,\n                        FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), random, 0);\n        });\n\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file_id = fii.IndexNumber.QuadPart;\n        });\n\n        h.reset();\n    }\n\n    test(\"Check directory entry\", [&]() {\n        u16string_view name = u\"id1\";\n\n        auto items = query_dir<FILE_ID_FULL_DIR_INFORMATION>(dir, name);\n\n        if (items.size() != 1)\n            throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n        auto& fdi = *static_cast<const FILE_ID_FULL_DIR_INFORMATION*>(items.front());\n\n        if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n            throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n        if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n            throw runtime_error(\"FileName did not match.\");\n\n        if ((uint64_t)fdi.FileId.QuadPart != file_id)\n            throw runtime_error(\"File IDs did not match.\");\n    });\n\n    test(\"Try opening by ID without RootDirectory value\", [&]() {\n        exp_status([&]() {\n            open_by_id(nullptr, file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                       FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPENED);\n        }, STATUS_INVALID_PARAMETER);\n    });\n\n    test(\"Open directory\", [&]() {\n        dirh = create_file(dir, MAXIMUM_ALLOWED, 0,\n                           FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                           FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    test(\"Try to open by ID with FILE_DIRECTORY_FILE\", [&]() {\n        exp_status([&]() {\n            open_by_id(dirh.get(), file_id, SYNCHRONIZE | MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                       FILE_SYNCHRONOUS_IO_NONALERT | FILE_DIRECTORY_FILE, FILE_OPENED);\n        }, STATUS_NOT_A_DIRECTORY);\n    });\n\n    test(\"Open by ID\", [&]() {\n        h = open_by_id(dirh.get(), file_id, SYNCHRONIZE | MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                       FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPENED);\n    });\n\n    dirh.reset();\n\n    if (h) {\n        test(\"Read file\", [&]() {\n            auto data = read_file(h.get(), random.size(), 0);\n\n            if (data.size() != random.size())\n                throw formatted_error(\"Read {} bytes, {} expected.\", data.size(), random.size());\n\n            if (memcmp(data.data(), random.data(), random.size()))\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        test(\"Check filename\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\id1\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\id1\\\".\");\n        });\n\n        test(\"Check links\", [&]() {\n            auto items = query_links(h.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& item = items.front();\n\n            if (item.second != u\"id1\")\n                throw formatted_error(\"Link was called {}, expected id1\", u16string_to_string(item.second));\n        });\n\n        test(\"Create hardlink\", [&]() {\n            set_link_information(h.get(), false, nullptr, dir + u\"\\\\id1a\");\n        });\n\n        test(\"Try renaming\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\id1b\");\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Try deleting\", [&]() {\n            exp_status([&]() {\n                set_disposition_information(h.get(), true);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open directory\", [&]() {\n        dirh = create_file(dir, MAXIMUM_ALLOWED, 0,\n                           FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                           FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    test(\"Open by ID with FILE_DELETE_ON_CLOSE\", [&]() {\n        h = open_by_id(dirh.get(), file_id, SYNCHRONIZE | DELETE, 0, 0, FILE_OPEN,\n                       FILE_SYNCHRONOUS_IO_NONALERT | FILE_NON_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE,\n                       FILE_OPENED);\n    });\n\n    dirh.reset();\n\n    if (h) {\n        h.reset();\n\n        test(\"Check directory entry 1 still there\", [&]() {\n            u16string_view name = u\"id1\";\n\n            auto items = query_dir<FILE_ID_FULL_DIR_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_ID_FULL_DIR_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n\n            if ((uint64_t)fdi.FileId.QuadPart != file_id)\n                throw runtime_error(\"File IDs did not match.\");\n        });\n\n        test(\"Check directory entry 2 still there\", [&]() {\n            u16string_view name = u\"id1a\";\n\n            auto items = query_dir<FILE_ID_FULL_DIR_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_ID_FULL_DIR_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n\n            if ((uint64_t)fdi.FileId.QuadPart != file_id)\n                throw runtime_error(\"File IDs did not match.\");\n        });\n    }\n\n    test(\"Open directory\", [&]() {\n        dirh = create_file(dir, MAXIMUM_ALLOWED, 0,\n                           FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                           FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    test(\"Open by ID with FILE_OPEN_IF\", [&]() {\n        open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN_IF,\n                   0, FILE_OPENED);\n    });\n\n    test(\"Open by ID with FILE_OVERWRITE_IF\", [&]() {\n        open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE_IF,\n                   0, FILE_OVERWRITTEN);\n    });\n\n    test(\"Open by ID with FILE_SUPERSEDE\", [&]() {\n        open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE,\n                   0, FILE_SUPERSEDED);\n    });\n\n    test(\"Try open by ID with FILE_CREATE\", [&]() {\n        exp_status([&]() {\n            open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                       0, FILE_CREATED);\n        }, STATUS_OBJECT_NAME_COLLISION);\n    });\n\n    test(\"Try open by ID with FILE_OVERWRITE\", [&]() {\n        open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE,\n                    0, FILE_OVERWRITTEN);\n    });\n\n    dirh.reset();\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\id2\", FILE_READ_ATTRIBUTES | DELETE, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file_id = fii.IndexNumber.QuadPart;\n        });\n\n        test(\"Delete file\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open directory\", [&]() {\n        dirh = create_file(dir, MAXIMUM_ALLOWED, 0,\n                           FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                           FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    test(\"Try to open invalid ID with FILE_OPEN\", [&]() {\n        exp_status([&]() {\n            open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                       0, FILE_OPENED);\n        }, STATUS_INVALID_PARAMETER);\n    });\n\n    test(\"Try to open invalid ID with FILE_OPEN_IF\", [&]() {\n        exp_status([&]() {\n            open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN_IF,\n                       0, FILE_OPENED);\n        }, STATUS_INVALID_PARAMETER);\n    });\n\n    test(\"Try to open invalid ID with FILE_CREATE\", [&]() {\n        exp_status([&]() {\n            open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                       0, FILE_CREATED);\n        }, STATUS_INVALID_PARAMETER);\n    });\n\n    test(\"Try to open invalid ID with FILE_OVERWRITE\", [&]() {\n        exp_status([&]() {\n            open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE,\n                       0, FILE_OVERWRITTEN);\n        }, STATUS_INVALID_PARAMETER);\n    });\n\n    test(\"Try to open invalid ID with FILE_OVERWRITE_IF\", [&]() {\n        exp_status([&]() {\n            open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE_IF,\n                       0, FILE_OVERWRITTEN);\n        }, STATUS_INVALID_PARAMETER);\n    });\n\n    test(\"Try to open invalid ID with FILE_SUPERSEDE\", [&]() {\n        exp_status([&]() {\n            open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE,\n                       0, FILE_SUPERSEDED);\n        }, STATUS_INVALID_PARAMETER);\n    });\n\n    dirh.reset();\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\id3\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file_id = fii.IndexNumber.QuadPart;\n        });\n\n        h.reset();\n\n        test(\"Open directory\", [&]() {\n            dirh = create_file(dir, MAXIMUM_ALLOWED, 0,\n                               FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                               FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED);\n        });\n\n        test(\"Try to open subdirectory by ID with FILE_NON_DIRECTORY_FILE\", [&]() {\n            exp_status([&]() {\n                open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                           FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n            }, STATUS_FILE_IS_A_DIRECTORY);\n        });\n\n        test(\"Open subdirectory by ID\", [&]() {\n            open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                       0, FILE_OPENED);\n        });\n\n        dirh.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\id4\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle h2;\n\n        test(\"Open second handle to file\", [&]() {\n            h2 = create_file(dir + u\"\\\\id4\", DELETE, 0, 0, FILE_OPEN,\n                             0, FILE_OPENED);\n        });\n\n        test(\"Do POSIX deletion\", [&]() {\n            set_disposition_information_ex(h2.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS);\n        });\n\n        h2.reset();\n\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file_id = fii.IndexNumber.QuadPart;\n        });\n\n        test(\"Open directory\", [&]() {\n            dirh = create_file(dir, MAXIMUM_ALLOWED, 0,\n                               FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                               FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED);\n        });\n\n        test(\"Try to open orphaned inode by file ID\", [&]() {\n            exp_status([&]() {\n                open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                           0, FILE_OPENED);\n            }, STATUS_DELETE_PENDING);\n        });\n\n        dirh.reset();\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\id5\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        array<uint8_t, 16> obj_id;\n\n        test(\"Get object ID\", [&]() {\n            obj_id = create_or_get_object_id(h.get());\n        });\n\n        test(\"Open directory\", [&]() {\n            dirh = create_file(dir, MAXIMUM_ALLOWED, 0,\n                               FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                               FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED);\n        });\n\n        test(\"Open by object ID\", [&]() {\n            open_by_id(dirh.get(), obj_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                       0, FILE_OPENED);\n        });\n\n        dirh.reset();\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\id6\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle h2;\n\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file_id = fii.IndexNumber.QuadPart;\n        });\n\n        h.reset();\n\n        test(\"Open directory\", [&]() {\n            dirh = create_file(dir, MAXIMUM_ALLOWED, 0,\n                               FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                               FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED);\n        });\n\n        test(\"Open file by ID\", [&]() {\n            h2 = open_by_id(dirh.get(), file_id, MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                            0, FILE_OPENED);\n        });\n\n        test(\"Try to query filename without traverse privilege\", [&]() {\n            exp_status([&]() {\n                query_file_name_information(h2.get());\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        dirh.reset();\n    }\n}\n"
  },
  {
    "path": "src/tests/cs.cpp",
    "content": "#include \"test.h\"\n\nusing namespace std;\n\nstatic void set_case_sensitive(HANDLE h, bool case_sensitive) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    FILE_CASE_SENSITIVE_INFORMATION fcsi;\n\n    fcsi.Flags = case_sensitive ? FILE_CS_FLAG_CASE_SENSITIVE_DIR : 0;\n\n    Status = NtSetInformationFile(h, &iosb, &fcsi, sizeof(fcsi), FileCaseSensitiveInformation);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != 0)\n        throw formatted_error(\"iosb.Information was {}, expected 0\", iosb.Information);\n}\n\nstatic unique_handle create_file_cs(u16string_view path, ACCESS_MASK access, ULONG atts,\n                                    ULONG share, ULONG dispo, ULONG options, ULONG_PTR exp_info,\n                                    optional<uint64_t> allocation = nullopt) {\n    NTSTATUS Status;\n    HANDLE h;\n    IO_STATUS_BLOCK iosb;\n    UNICODE_STRING us;\n    OBJECT_ATTRIBUTES oa;\n    LARGE_INTEGER alloc_size;\n\n    oa.Length = sizeof(oa);\n    oa.RootDirectory = nullptr; // FIXME - test\n\n    us.Length = us.MaximumLength = path.length() * sizeof(char16_t);\n    us.Buffer = (WCHAR*)path.data();\n    oa.ObjectName = &us;\n\n    oa.Attributes = 0; // not OBJ_CASE_INSENSITIVE\n    oa.SecurityDescriptor = nullptr; // FIXME - test\n    oa.SecurityQualityOfService = nullptr; // FIXME - test(?)\n\n    if (allocation)\n        alloc_size.QuadPart = allocation.value();\n\n    // FIXME - EaBuffer and EaLength\n\n    iosb.Information = 0xdeadbeef;\n\n    Status = NtCreateFile(&h, access, &oa, &iosb, allocation ? &alloc_size : nullptr,\n                          atts, share, dispo, options, nullptr, 0);\n\n    if (Status != STATUS_SUCCESS) {\n        if (NT_SUCCESS(Status)) // STATUS_OPLOCK_BREAK_IN_PROGRESS etc.\n            NtClose(h);\n\n        throw ntstatus_error(Status);\n    }\n\n    if (iosb.Information != exp_info)\n        throw formatted_error(\"iosb.Information was {}, expected {}\", iosb.Information, exp_info);\n\n    return unique_handle(h);\n}\n\nvoid test_cs(const u16string& dir) {\n    unique_handle h;\n    int64_t lc_id = 0, uc_id = 0;\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\csdir\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        // returns STATUS_NOT_SUPPORTED unless HKLM\\SYSTEM\\CurrentControlSet\\Control\\FileSystem\\NtfsEnableDirCaseSensitivity is set to 1\n\n        test(\"Set case-sensitive flag\", [&]() {\n            set_case_sensitive(h.get(), true);\n        });\n\n        test(\"Query case-sensitive flag\", [&]() {\n            auto fcsi = query_information<FILE_CASE_SENSITIVE_INFORMATION>(h.get());\n\n            if (fcsi.Flags != FILE_CS_FLAG_CASE_SENSITIVE_DIR)\n                throw formatted_error(\"Flags was {:x}, expected FILE_CS_FLAG_CASE_SENSITIVE_DIR\", fcsi.Flags);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\csdir\\\\cs1\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            lc_id = fii.IndexNumber.QuadPart;\n        });\n\n        h.reset();\n    }\n\n    test(\"Check directory entry\", [&]() {\n        u16string_view name = u\"cs1\";\n\n        auto items = query_dir<FILE_ID_FULL_DIR_INFORMATION>(dir + u\"\\\\csdir\", name);\n\n        if (items.size() != 1)\n            throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n        auto& fdi = *static_cast<const FILE_ID_FULL_DIR_INFORMATION*>(items.front());\n\n        if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n            throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n        if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n            throw runtime_error(\"FileName did not match.\");\n    });\n\n    test(\"Check directory entry with wrong case\", [&]() {\n        u16string_view name = u\"CS1\";\n\n        exp_status([&]() {\n            query_dir<FILE_ID_FULL_DIR_INFORMATION>(dir + u\"\\\\csdir\", name);\n        }, STATUS_NO_SUCH_FILE);\n    });\n\n    test(\"Try opening file with wrong case (FILE_OPEN)\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\csdir\\\\CS1\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                        FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n        }, STATUS_OBJECT_NAME_NOT_FOUND);\n    });\n\n    test(\"Try opening file with wrong case (FILE_OVERWRITE)\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\csdir\\\\CS1\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE,\n                        FILE_NON_DIRECTORY_FILE, FILE_OVERWRITTEN);\n        }, STATUS_OBJECT_NAME_NOT_FOUND);\n    });\n\n    test(\"Create file with different case (FILE_CREATE)\", [&]() {\n        h = create_file(dir + u\"\\\\csdir\\\\CS1\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            uc_id = fii.IndexNumber.QuadPart;\n        });\n\n        h.reset();\n    }\n\n    test(\"Open uppercase file (FILE_OPEN_IF)\", [&]() {\n        h = create_file(dir + u\"\\\\csdir\\\\CS1\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN_IF,\n                        FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Check file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            if (fii.IndexNumber.QuadPart != uc_id)\n                throw runtime_error(\"Wrong file ID\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Open uppercase file (FILE_OVERWRITE_IF)\", [&]() {\n        h = create_file(dir + u\"\\\\csdir\\\\CS1\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE_IF,\n                        FILE_NON_DIRECTORY_FILE, FILE_OVERWRITTEN);\n    });\n\n    if (h) {\n        test(\"Check file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            if (fii.IndexNumber.QuadPart != uc_id)\n                throw runtime_error(\"Wrong file ID\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Open uppercase file (FILE_SUPERSEDE)\", [&]() {\n        h = create_file(dir + u\"\\\\csdir\\\\CS1\", MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE,\n                        FILE_NON_DIRECTORY_FILE, FILE_SUPERSEDED);\n    });\n\n    if (h) {\n        test(\"Check file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            if (fii.IndexNumber.QuadPart != uc_id)\n                throw runtime_error(\"Wrong file ID\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create subdir\", [&]() {\n        h = create_file(dir + u\"\\\\csdir\\\\cs2\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check case-sensitive flag inherited\", [&]() {\n            auto fcsi = query_information<FILE_CASE_SENSITIVE_INFORMATION>(h.get());\n\n            if (fcsi.Flags != FILE_CS_FLAG_CASE_SENSITIVE_DIR)\n                throw formatted_error(\"Flags was {:x}, expected FILE_CS_FLAG_CASE_SENSITIVE_DIR\", fcsi.Flags);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\csdir\\\\cs3\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to set case-sensitive flag on file\", [&]() {\n            exp_status([&]() {\n                set_case_sensitive(h.get(), true);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Query case-sensitive flag on file\", [&]() {\n            auto fcsi = query_information<FILE_CASE_SENSITIVE_INFORMATION>(h.get());\n\n            if (fcsi.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", fcsi.Flags);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create subdir\", [&]() {\n        h = create_file(dir + u\"\\\\csdir\\\\cs5\", FILE_WRITE_ATTRIBUTES | WRITE_DAC, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set DACL to FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD\", [&]() {\n            set_dacl(h.get(), FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD);\n        });\n\n        test(\"Try to set case-sensitive flag\", [&]() {\n            exp_status([&]() {\n                set_case_sensitive(h.get(), true);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Set DACL to FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY\", [&]() {\n            set_dacl(h.get(), FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY);\n        });\n\n        test(\"Try to set case-sensitive flag\", [&]() {\n            exp_status([&]() {\n                set_case_sensitive(h.get(), true);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Set DACL to FILE_ADD_FILE | FILE_DELETE_CHILD\", [&]() {\n            set_dacl(h.get(), FILE_ADD_FILE | FILE_DELETE_CHILD);\n        });\n\n        test(\"Try to set case-sensitive flag\", [&]() {\n            exp_status([&]() {\n                set_case_sensitive(h.get(), true);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Set DACL to FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD\", [&]() {\n            set_dacl(h.get(), FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | FILE_DELETE_CHILD);\n        });\n\n        test(\"Set case-sensitive flag\", [&]() {\n            set_case_sensitive(h.get(), true);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create subdir\", [&]() {\n        create_file(dir + u\"\\\\csdir\\\\cs6\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        create_file(dir + u\"\\\\csdir\\\\cs6\\\\file\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Open subdir\", [&]() {\n        h = create_file(dir + u\"\\\\csdir\\\\cs6\", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN,\n                        FILE_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Set case-sensitive flag on non-empty directory\", [&]() {\n            set_case_sensitive(h.get(), true);\n        });\n\n        test(\"Clear case-sensitive flag on non-empty directory\", [&]() {\n            set_case_sensitive(h.get(), false);\n        });\n\n        test(\"Set case-sensitive flag on non-empty directory again\", [&]() {\n            set_case_sensitive(h.get(), true);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file differing by case\", [&]() {\n        create_file(dir + u\"\\\\csdir\\\\cs6\\\\FILE\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Open subdir\", [&]() {\n        h = create_file(dir + u\"\\\\csdir\\\\cs6\", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN,\n                        FILE_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Try to clear case-sensitive flag on directory with files differing in case\", [&]() {\n            exp_status([&]() {\n                set_case_sensitive(h.get(), false);\n            }, STATUS_CASE_DIFFERING_NAMES_IN_DIR);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file in normal dir without OBJ_CASE_INSENSITIVE\", [&]() {\n        create_file_cs(dir + u\"\\\\cs7\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATED,\n                       FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Open file in normal dir without OBJ_CASE_INSENSITIVE\", [&]() {\n        create_file_cs(dir + u\"\\\\cs7\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                       FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    // succeeds unless HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\kernel\\ObCaseInsensitive set to 0\n\n    test(\"Open file in normal dir without OBJ_CASE_INSENSITIVE with wrong case\", [&]() {\n        exp_status([&]() {\n            create_file_cs(dir + u\"\\\\CS7\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                           FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n        }, STATUS_OBJECT_NAME_NOT_FOUND);\n    });\n\n    test(\"Create stream in case-sensitive directory\", [&]() {\n        create_file(dir + u\"\\\\csdir\\\\cs8:stream\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Open stream in case-sensitive directory with wrong case\", [&]() { // succeeds!\n        create_file(dir + u\"\\\\csdir\\\\cs8:STREAM\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                    0, FILE_OPENED);\n    });\n}\n"
  },
  {
    "path": "src/tests/delete.cpp",
    "content": "#include \"test.h\"\n\nusing namespace std;\n\nvoid set_disposition_information(HANDLE h, bool delete_file) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    FILE_DISPOSITION_INFORMATION fdi;\n\n    fdi.DoDeleteFile = delete_file;\n\n    iosb.Information = 0xdeadbeef;\n\n    Status = NtSetInformationFile(h, &iosb, &fdi, sizeof(fdi), FileDispositionInformation);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != 0)\n        throw formatted_error(\"iosb.Information was {}, expected 0\", iosb.Information);\n}\n\nvoid set_disposition_information_ex(HANDLE h, uint32_t flags) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    FILE_DISPOSITION_INFORMATION_EX fdie;\n\n    fdie.Flags = flags;\n\n    iosb.Information = 0xdeadbeef;\n\n    Status = NtSetInformationFile(h, &iosb, &fdie, sizeof(fdie), FileDispositionInformationEx);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != 0)\n        throw formatted_error(\"iosb.Information was {}, expected 0\", iosb.Information);\n}\n\nvoid test_delete(const u16string& dir) {\n    unique_handle h, h2;\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\deletefile1\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"deletefile1\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Set disposition\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        test(\"Check directory entry still there\", [&]() {\n            u16string_view name = u\"deletefile1\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check standard information again\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h.reset();\n\n        test(\"Check directory entry gone after close\", [&]() {\n            u16string_view name = u\"deletefile1\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\deletedir2\", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"deletedir2\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (!fsli.Directory)\n                throw runtime_error(\"Directory was false, expected true\");\n        });\n\n        test(\"Set disposition\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        test(\"Check directory entry still there\", [&]() {\n            u16string_view name = u\"deletedir2\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check standard information again\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information again\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (!fsli.Directory)\n                throw runtime_error(\"Directory was false, expected true\");\n        });\n\n        h.reset();\n\n        test(\"Check directory entry gone after close\", [&]() {\n            u16string_view name = u\"deletedir2\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\deletedir3\", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        h2 = create_file(dir + u\"\\\\deletedir3\\\\file\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Try to set disposition on directory\", [&]() {\n            exp_status([&]() {\n                set_disposition_information(h.get(), true);\n            }, STATUS_DIRECTORY_NOT_EMPTY);\n        });\n\n        test(\"Set disposition on file\", [&]() {\n            set_disposition_information(h2.get(), true);\n        });\n\n        test(\"Try to set disposition on directory again\", [&]() {\n            exp_status([&]() {\n                set_disposition_information(h.get(), true);\n            }, STATUS_DIRECTORY_NOT_EMPTY);\n        });\n\n        h2.reset();\n\n        test(\"Set disposition on directory now empty\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry gone after close\", [&]() {\n            u16string_view name = u\"deletedir3\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\deletefile4\", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set deletion flag on file\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        test(\"Try to reopen file marked for deletion\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\deletefile4\", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED);\n            }, STATUS_DELETE_PENDING);\n        });\n\n        test(\"Clear deletion flag on file\", [&]() {\n            set_disposition_information(h.get(), false);\n        });\n\n        test(\"Check standard information again\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information again\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Reopen file now no longer marked for deletion\", [&]() {\n            h2 = create_file(dir + u\"\\\\deletefile4\", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        test(\"Set deletion flag again\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry after first handle closed\", [&]() {\n            u16string_view name = u\"deletefile4\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        h2.reset();\n\n        test(\"Check directory entry gone after second handle closed\", [&]() {\n            u16string_view name = u\"deletefile4\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\deletefile5\", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set deletion flag on file\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        test(\"Clear deletion flag on file\", [&]() {\n            set_disposition_information(h.get(), false);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"deletefile5\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n    }\n\n    test(\"Create file without DELETE flag\", [&]() {\n        h = create_file(dir + u\"\\\\deletefile6\", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to set deletion flag on file\", [&]() {\n            exp_status([&]() {\n                set_disposition_information(h.get(), true);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\deletefile7\", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create stream on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\deletefile7:ads\", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Set deletion flag on file\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        test(\"Check standard information on file\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information on file\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Check standard information on stream\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h2.get());\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information on stream\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h2.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Clear deletion flag on stream\", [&]() {\n            set_disposition_information(h2.get(), false); // gets ignored\n        });\n\n        test(\"Check standard information on file\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information on file\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Check standard information on stream\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h2.get());\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information on stream\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h2.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h.reset();\n\n        test(\"Check directory entry still there after file handle closed\", [&]() {\n            u16string_view name = u\"deletefile7\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Clear deletion flag on stream\", [&]() {\n            set_disposition_information(h2.get(), false); // still ignored\n        });\n\n        test(\"Check standard information on stream\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h2.get());\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information on stream\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h2.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h2.reset();\n\n        test(\"Check directory entry gone after stream handle closed\", [&]() {\n            u16string_view name = u\"deletefile7\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n    }\n\n    test(\"Create file with FILE_DELETE_ON_CLOSE\", [&]() {\n        h = create_file(dir + u\"\\\\deletefile8\", DELETE, 0, 0, FILE_CREATE,\n                        FILE_DELETE_ON_CLOSE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check standard information on file\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information on file\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h.reset();\n\n        test(\"Check directory entry gone after file closed\", [&]() {\n            u16string_view name = u\"deletefile8\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n    }\n\n    test(\"Create file with FILE_DELETE_ON_CLOSE\", [&]() {\n        h = create_file(dir + u\"\\\\deletefile9\", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE,\n                        FILE_DELETE_ON_CLOSE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to open second handle to file without FILE_SHARE_DELETE\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\deletefile9\", FILE_READ_DATA, 0, 0, FILE_OPEN,\n                            0, FILE_OPENED);\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        test(\"Open second handle to file with FILE_SHARE_DELETE\", [&]() {\n            h2 = create_file(dir + u\"\\\\deletefile9\", DELETE, 0, FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        test(\"Check standard information on second handle\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h2.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information on second handle\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h2.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h.reset();\n\n        test(\"Check standard information after first handle closed\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h2.get());\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information after first handle closed\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h2.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Clear deletion flag\", [&]() {\n            set_disposition_information(h2.get(), false); // ignored\n        });\n\n        test(\"Check standard information again\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h2.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information again\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h2.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Try to open third handle to file\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\deletefile9\", FILE_READ_DATA, 0, 0, FILE_OPEN,\n                            FILE_SHARE_DELETE, FILE_OPENED);\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        h2.reset();\n\n        test(\"Check directory entry still there after both handles closed\", [&]() {\n            u16string_view name = u\"deletefile9\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n    }\n\n    test(\"Create readonly file\", [&]() {\n        create_file(dir + u\"\\\\deletefile10\", FILE_READ_DATA, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE,\n                    0, FILE_CREATED);\n    });\n\n    test(\"Try to open readonly file with FILE_DELETE_ON_CLOSE\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\deletefile10\", DELETE, 0, 0, FILE_OPEN,\n                        FILE_DELETE_ON_CLOSE, FILE_OPENED);\n        }, STATUS_CANNOT_DELETE);\n    });\n\n    test(\"Create readonly file\", [&]() {\n        h = create_file(dir + u\"\\\\deletefile11\", DELETE, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to set deletion flag on readonly file\", [&]() {\n            exp_status([&]() {\n                set_disposition_information(h.get(), true);\n            }, STATUS_CANNOT_DELETE);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory with FILE_DELETE_ON_CLOSE\", [&]() {\n        h = create_file(dir + u\"\\\\deletedir12\", DELETE, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (!fsli.Directory)\n                throw runtime_error(\"Directory was false, expected true\");\n        });\n\n        h.reset();\n\n        test(\"Check directory entry gone after handle closed\", [&]() {\n            u16string_view name = u\"deletedir12\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n    }\n\n    test(\"Create directory with FILE_DELETE_ON_CLOSE\", [&]() {\n        h = create_file(dir + u\"\\\\deletedir13\", DELETE, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE | FILE_DELETE_ON_CLOSE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create file in directory\", [&]() {\n            create_file(dir + u\"\\\\deletedir13\\\\file\", FILE_READ_DATA, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry still there after handle closed\", [&]() {\n            u16string_view name = u\"deletedir13\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n    }\n}\n\nvoid test_delete_ex(HANDLE token, const u16string& dir) {\n    unique_handle h, h2;\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\deletefileex1\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"deletefileex1\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Set disposition\", [&]() {\n            set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE);\n        });\n\n        test(\"Check directory entry still there\", [&]() {\n            u16string_view name = u\"deletefileex1\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check standard information again\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information again\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h.reset();\n\n        test(\"Check directory entry gone after close\", [&]() {\n            u16string_view name = u\"deletefileex1\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\deleteexdir2\", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"deleteexdir2\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (!fsli.Directory)\n                throw runtime_error(\"Directory was false, expected true\");\n        });\n\n        test(\"Set disposition\", [&]() {\n            set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE);\n        });\n\n        test(\"Check directory entry still there\", [&]() {\n            u16string_view name = u\"deleteexdir2\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check standard information again\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information again\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (!fsli.Directory)\n                throw runtime_error(\"Directory was false, expected true\");\n        });\n\n        h.reset();\n\n        test(\"Check directory entry gone after close\", [&]() {\n            u16string_view name = u\"deleteexdir2\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\deleteexfile3\", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set deletion flag on file\", [&]() {\n            set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE);\n        });\n\n        test(\"Try to reopen file marked for deletion\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\deleteexfile3\", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED);\n            }, STATUS_DELETE_PENDING);\n        });\n\n        test(\"Clear deletion flag on file\", [&]() {\n            set_disposition_information_ex(h.get(), FILE_DISPOSITION_DO_NOT_DELETE);\n        });\n\n        test(\"Check standard information again\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information again\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Reopen file now no longer marked for deletion\", [&]() {\n            h2 = create_file(dir + u\"\\\\deleteexfile3\", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        test(\"Set deletion flag again\", [&]() {\n            set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry after first handle closed\", [&]() {\n            u16string_view name = u\"deleteexfile3\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        h2.reset();\n\n        test(\"Check directory entry gone after second handle closed\", [&]() {\n            u16string_view name = u\"deleteexfile3\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n    }\n\n    test(\"Create image file\", [&]() {\n        h = create_file(dir + u\"\\\\deleteexfile4\",\n                        SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE,\n                        0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle sect;\n\n        test(\"Write to file\", [&]() {\n            auto img = pe_image(as_bytes(span(\"hello\")));\n\n            write_file(h.get(), img);\n        });\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get());\n        });\n\n        if (sect) {\n            test(\"Try deleting mapped image file with 1 link and FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK\", [&]() {\n                exp_status([&]() {\n                    set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK);\n                }, STATUS_CANNOT_DELETE);\n            });\n\n            test(\"Try deleting mapped image file with 1 link\", [&]() {\n                exp_status([&]() {\n                    set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE);\n                }, STATUS_CANNOT_DELETE);\n            });\n\n            test(\"Create hard link\", [&]() {\n                set_link_information(h.get(), false, nullptr, dir + u\"\\\\deleteexfile4a\");\n            });\n\n            test(\"Try deleting mapped image file with 2 links and FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK\", [&]() {\n                exp_status([&]() {\n                    set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK);\n                }, STATUS_CANNOT_DELETE);\n            });\n\n            test(\"Delete mapped image file with 2 links\", [&]() {\n                set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE);\n            });\n        }\n\n        h.reset();\n    }\n\n    test(\"Create readonly file\", [&]() {\n        h = create_file(dir + u\"\\\\deleteexfile5\", DELETE, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to set deletion flag on readonly file\", [&]() {\n            exp_status([&]() {\n                set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE);\n            }, STATUS_CANNOT_DELETE);\n        });\n\n        test(\"Set deletion flag on readonly file with FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE\", [&]() {\n            set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE);\n        });\n\n        h.reset();\n    }\n\n    // traverse privilege needed to query hard links\n    test(\"Add SeChangeNotifyPrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\deleteexfile6\", DELETE, 0, FILE_SHARE_DELETE, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Open second handle to file\", [&]() {\n        h2 = create_file(dir + u\"\\\\deleteexfile6\", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"deleteexfile6\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\deleteexfile6\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\deleteexfile6\\\".\");\n        });\n\n        int64_t dir_id;\n\n        test(\"Check hardlinks\", [&]() {\n            auto items = query_links(h.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& item = items.front();\n\n            if (item.second != u\"deleteexfile6\")\n                throw formatted_error(\"Link was called {}, expected deleteexfile6\", u16string_to_string(item.second));\n\n            dir_id = item.first;\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi.NumberOfLinks);\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Set disposition with FILE_DISPOSITION_POSIX_SEMANTICS\", [&]() {\n            set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_POSIX_SEMANTICS);\n        });\n\n        test(\"Check directory entry still there\", [&]() {\n            u16string_view name = u\"deleteexfile6\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check standard information again\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.NumberOfLinks != 0)\n                throw formatted_error(\"NumberOfLinks was {}, expected 0\", fsi.NumberOfLinks);\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information again\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h.reset();\n\n        test(\"Check directory entry gone after close\", [&]() {\n            u16string_view name = u\"deleteexfile6\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h2.get());\n\n            static const u16string_view ends_with = u\"\\\\deleteexfile6\";\n\n            // NTFS moves this to \\$Extend\\$Deleted directory\n\n            if (fn.size() >= ends_with.size() && fn.substr(fn.size() - ends_with.size()) == ends_with)\n                throw runtime_error(\"Name ended with \\\"\\\\deleteexfile6\\\".\");\n        });\n\n        test(\"Check hardlinks\", [&]() {\n            auto items = query_links(h2.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& item = items.front();\n\n            if (item.second == u\"deleteexfile6\")\n                throw formatted_error(\"Link was called deleteexfile6, expected something else\", u16string_to_string(item.second));\n\n            if (item.first == dir_id)\n                throw runtime_error(\"Dir ID of orphaned inode is same as before\");\n        });\n\n        test(\"Check standard information on second handle\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h2.get());\n\n            if (fsi.NumberOfLinks != 0)\n                throw formatted_error(\"NumberOfLinks was {}, expected 0\", fsi.NumberOfLinks);\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information on second handle\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h2.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h2.reset();\n\n        test(\"Try opening deleted file\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\deleteexfile6\", DELETE, 0, FILE_SHARE_DELETE, FILE_OPEN, 0, FILE_OPENED);\n            }, STATUS_OBJECT_NAME_NOT_FOUND);\n        });\n    }\n\n    disable_token_privileges(token);\n\n    test(\"Create file with FILE_DELETE_ON_CLOSE\", [&]() {\n        h = create_file(dir + u\"\\\\deleteexfile7\", DELETE, 0, 0, FILE_CREATE,\n                        FILE_DELETE_ON_CLOSE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check standard information on file\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information on file\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Clear delete on close flag\", [&]() {\n            set_disposition_information_ex(h.get(), FILE_DISPOSITION_DO_NOT_DELETE | FILE_DISPOSITION_ON_CLOSE);\n        });\n\n        test(\"Check standard information on file\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information on file\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h.reset();\n\n        test(\"Check directory entry still there after file closed\", [&]() {\n            u16string_view name = u\"deleteexfile7\";\n\n            query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n        });\n    }\n\n    test(\"Create file without FILE_DELETE_ON_CLOSE\", [&]() {\n        h = create_file(dir + u\"\\\\deleteexfile8\", DELETE, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check standard information on file\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information on file\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        // see https://community.osr.com/discussion/comment/302155/#Comment_302155\n        test(\"Try to set delete on close flag\", [&]() {\n            exp_status([&]() {\n                set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_ON_CLOSE);\n            }, STATUS_NOT_SUPPORTED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file with FILE_DELETE_ON_CLOSE\", [&]() {\n        h = create_file(dir + u\"\\\\deleteexfile9\", DELETE, 0, 0, FILE_CREATE,\n                        FILE_DELETE_ON_CLOSE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set delete on close flag\", [&]() {\n            set_disposition_information_ex(h.get(), FILE_DISPOSITION_DELETE | FILE_DISPOSITION_ON_CLOSE);\n        });\n\n        h.reset();\n    }\n}\n"
  },
  {
    "path": "src/tests/ea.cpp",
    "content": "#include \"test.h\"\n#include <array>\n\nusing namespace std;\n\n#ifdef _MSC_VER\ntypedef struct _FILE_FULL_EA_INFORMATION {\n    ULONG NextEntryOffset;\n    UCHAR Flags;\n    UCHAR EaNameLength;\n    USHORT EaValueLength;\n    CHAR EaName[1];\n} FILE_FULL_EA_INFORMATION, *PFILE_FULL_EA_INFORMATION;\n#endif\n\nstatic constexpr uint32_t ea_size(string_view name, string_view value) {\n    uint32_t size = offsetof(FILE_FULL_EA_INFORMATION, EaName) + name.length() + 1 + value.length();\n\n    if (size & 3)\n        size = ((size >> 2) + 1) << 2;\n\n    return size;\n}\n\nvoid write_ea(HANDLE h, string_view name, string_view value, bool need_ea) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf;\n\n    buf.resize(ea_size(name, value));\n\n    auto& ffeai = *(FILE_FULL_EA_INFORMATION*)buf.data();\n\n    ffeai.NextEntryOffset = 0;\n    ffeai.Flags = need_ea ? FILE_NEED_EA : 0;\n    ffeai.EaNameLength = name.size();\n    ffeai.EaValueLength = value.size();\n\n    memcpy(ffeai.EaName, name.data(), name.size());\n    ffeai.EaName[name.size()] = 0;\n    memcpy(ffeai.EaName + name.size() + 1, value.data(), value.size());\n\n    Status = NtSetEaFile(h, &iosb, buf.data(), buf.size());\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\ntemplate<size_t N>\nstatic void write_eas(HANDLE h, const array<pair<string_view, string_view>, N>& arr) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf;\n    size_t size = 0;\n\n    for (unsigned int i = 0; i < N; i++) {\n        size += ea_size(arr[i].first, arr[i].second);\n    }\n\n    buf.resize(size);\n\n    auto ptr = buf.data();\n\n    for (unsigned int i = 0; i < N; i++) {\n        auto& ffeai = *(FILE_FULL_EA_INFORMATION*)ptr;\n\n        ffeai.NextEntryOffset = 0;\n        ffeai.Flags = 0;\n        ffeai.EaNameLength = arr[i].first.size();\n        ffeai.EaValueLength = arr[i].second.size();\n\n        memcpy(ffeai.EaName, arr[i].first.data(), arr[i].first.size());\n        ffeai.EaName[arr[i].first.size()] = 0;\n        memcpy(ffeai.EaName + arr[i].first.size() + 1, arr[i].second.data(), arr[i].second.size());\n\n        if (i != N - 1) {\n            ffeai.NextEntryOffset = ea_size(arr[i].first, arr[i].second);\n            ptr += ffeai.NextEntryOffset;\n        }\n    }\n\n    Status = NtSetEaFile(h, &iosb, buf.data(), buf.size());\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic vector<varbuf<FILE_FULL_EA_INFORMATION>> read_ea(HANDLE h) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    char buf[4096];\n\n    Status = NtQueryEaFile(h, &iosb, buf, sizeof(buf), false, nullptr,\n                           0, nullptr, true);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    vector<varbuf<FILE_FULL_EA_INFORMATION>> ret;\n    auto ptr = buf;\n\n    do {\n        auto& ffeai = *(FILE_FULL_EA_INFORMATION*)ptr;\n\n        ret.emplace_back();\n        auto& item = ret.back();\n\n        item.buf.resize(offsetof(FILE_FULL_EA_INFORMATION, EaName) + ffeai.EaNameLength + 1 + ffeai.EaValueLength);\n\n        memcpy(item.buf.data(), &ffeai, item.buf.size());\n\n        if (ffeai.NextEntryOffset == 0)\n            break;\n\n        ptr += ffeai.NextEntryOffset;\n    } while (true);\n\n    return ret;\n}\n\ntemplate<size_t N>\nstatic unique_handle create_file_ea(u16string_view path, ACCESS_MASK access, ULONG atts, ULONG share,\n                                    ULONG dispo, ULONG options, ULONG_PTR exp_info, optional<uint64_t> allocation,\n                                    const array<pair<string_view, string_view>, N>& eas) {\n    NTSTATUS Status;\n    HANDLE h;\n    IO_STATUS_BLOCK iosb;\n    UNICODE_STRING us;\n    OBJECT_ATTRIBUTES oa;\n    LARGE_INTEGER alloc_size;\n    vector<uint8_t> buf;\n\n    oa.Length = sizeof(oa);\n    oa.RootDirectory = nullptr;\n\n    us.Length = us.MaximumLength = path.length() * sizeof(char16_t);\n    us.Buffer = (WCHAR*)path.data();\n    oa.ObjectName = &us;\n\n    oa.Attributes = OBJ_CASE_INSENSITIVE;\n    oa.SecurityDescriptor = nullptr;\n    oa.SecurityQualityOfService = nullptr;\n\n    if (allocation)\n        alloc_size.QuadPart = allocation.value();\n\n    if constexpr (N != 0) {\n        size_t size = 0;\n\n        for (unsigned int i = 0; i < N; i++) {\n            size += ea_size(eas[i].first, eas[i].second);\n        }\n\n        buf.resize(size);\n\n        auto ptr = buf.data();\n\n        for (unsigned int i = 0; i < N; i++) {\n            auto& ffeai = *(FILE_FULL_EA_INFORMATION*)ptr;\n\n            ffeai.NextEntryOffset = 0;\n            ffeai.Flags = 0;\n            ffeai.EaNameLength = eas[i].first.size();\n            ffeai.EaValueLength = eas[i].second.size();\n\n            memcpy(ffeai.EaName, eas[i].first.data(), eas[i].first.size());\n            ffeai.EaName[eas[i].first.size()] = 0;\n            memcpy(ffeai.EaName + eas[i].first.size() + 1, eas[i].second.data(), eas[i].second.size());\n\n            if (i != N - 1) {\n                ffeai.NextEntryOffset = ea_size(eas[i].first, eas[i].second);\n                ptr += ffeai.NextEntryOffset;\n            }\n        }\n    }\n\n    iosb.Information = 0xdeadbeef;\n\n    Status = NtCreateFile(&h, access, &oa, &iosb, allocation ? &alloc_size : nullptr,\n                          atts, share, dispo, options, buf.data(), buf.size());\n\n    if (Status != STATUS_SUCCESS) {\n        if (NT_SUCCESS(Status)) // STATUS_OPLOCK_BREAK_IN_PROGRESS etc.\n            NtClose(h);\n\n        throw ntstatus_error(Status);\n    }\n\n    if (iosb.Information != exp_info)\n        throw formatted_error(\"iosb.Information was {}, expected {}\", iosb.Information, exp_info);\n\n    return unique_handle(h);\n}\n\ntemplate<typename T>\nstatic void check_ea_dirent(const u16string& dir, u16string_view name, uint32_t exp_size) {\n    auto items = query_dir<T>(dir, name);\n\n    if (items.size() != 1)\n        throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n    auto& fdi = *static_cast<const T*>(items.front());\n\n    if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n        throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n    if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n        throw runtime_error(\"FileName did not match.\");\n\n    if (fdi.EaSize != exp_size)\n        throw formatted_error(\"EaSize was {}, expected {}\", fdi.EaSize, exp_size);\n}\n\ntemplate<size_t N>\nstatic vector<varbuf<FILE_FULL_EA_INFORMATION>> read_eas(HANDLE h, const array<string_view, N>& arr,\n                                                         bool single_entry, ULONG* index,\n                                                         bool restart) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buflist;\n    size_t size;\n    char buf[4096];\n\n    size = 0;\n    for (unsigned int i = 0; i < N; i++) {\n        size += offsetof(FILE_GET_EA_INFORMATION, EaName) + arr[i].size() + 1;\n\n        if (i != N - 1 && size & 3)\n            size = ((size >> 2) + 1) << 2;\n    }\n\n    buflist.resize(size);\n\n    {\n        auto ptr = buflist.data();\n\n        for (unsigned int i = 0; i < N; i++) {\n            auto& fgeai = *(FILE_GET_EA_INFORMATION*)ptr;\n\n            fgeai.NextEntryOffset = 0;\n            fgeai.EaNameLength = arr[i].size();\n\n            memcpy(fgeai.EaName, arr[i].data(), arr[i].size());\n            fgeai.EaName[arr[i].size()] = 0;\n\n            if (i != N - 1) {\n                fgeai.NextEntryOffset = offsetof(FILE_GET_EA_INFORMATION, EaName) + arr[i].size() + 1;\n\n                if (fgeai.NextEntryOffset & 3)\n                    fgeai.NextEntryOffset = ((fgeai.NextEntryOffset >> 2) + 1) << 2;\n\n                ptr += fgeai.NextEntryOffset;\n            }\n        }\n    }\n\n    Status = NtQueryEaFile(h, &iosb, buf, sizeof(buf), single_entry, buflist.data(),\n                           buflist.size(), index, restart);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    vector<varbuf<FILE_FULL_EA_INFORMATION>> ret;\n    auto ptr = buf;\n\n    do {\n        auto& ffeai = *(FILE_FULL_EA_INFORMATION*)ptr;\n\n        ret.emplace_back();\n        auto& item = ret.back();\n\n        item.buf.resize(offsetof(FILE_FULL_EA_INFORMATION, EaName) + ffeai.EaNameLength + 1 + ffeai.EaValueLength);\n\n        memcpy(item.buf.data(), &ffeai, item.buf.size());\n\n        if (ffeai.NextEntryOffset == 0)\n            break;\n\n        ptr += ffeai.NextEntryOffset;\n    } while (true);\n\n    return ret;\n}\n\nvoid test_ea(const u16string& dir) {\n    unique_handle h;\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\ea1\", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        static const string_view ea_name = \"hello\", ea_value = \"world\";\n\n        test(\"Read EA\", [&]() {\n            exp_status([&]() {\n                read_ea(h.get());\n            }, STATUS_NO_EAS_ON_FILE);\n        });\n\n        test(\"Write EA\", [&]() {\n            write_ea(h.get(), ea_name, ea_value);\n        });\n\n        test(\"Read EA\", [&]() {\n            auto items = read_ea(h.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& ffeai = *static_cast<FILE_FULL_EA_INFORMATION*>(items.front());\n\n            if (ffeai.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai.Flags);\n\n            auto name = string_view(ffeai.EaName, ffeai.EaNameLength);\n\n            if (name != \"HELLO\") // gets capitalized\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"HELLO\\\"\", name);\n\n            auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength);\n\n            if (value != ea_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value, ea_value);\n        });\n\n        auto exp_size = ea_size(ea_name, ea_value);\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", feai.EaSize, exp_size);\n        });\n\n        test(\"Query FileAllInformation\", [&]() {\n            auto buf = query_all_information(h.get());\n            auto& fai = *static_cast<FILE_ALL_INFORMATION*>(buf);\n\n            if (fai.EaInformation.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", fai.EaInformation.EaSize, exp_size);\n        });\n\n        h.reset();\n\n        // EaSize in dirents not updated until file closed?\n        test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\ea1\", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        static const string_view ea1_name = \"HELLO\", ea1_value = \"world\";\n        static const string_view ea2_name = \"fOo\", ea2_value = \"bar\";\n\n        test(\"Add second EA\", [&]() {\n            write_ea(h.get(), ea2_name, ea2_value);\n        });\n\n        test(\"Read EAs\", [&]() {\n            auto items = read_ea(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2\", items.size());\n\n            auto& ffeai1 = *static_cast<FILE_FULL_EA_INFORMATION*>(items[0]);\n\n            if (ffeai1.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai1.Flags);\n\n            auto name1 = string_view(ffeai1.EaName, ffeai1.EaNameLength);\n\n            if (name1 != ea1_name)\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"{}\\\"\", name1, ea1_name);\n\n            auto value1 = string_view(ffeai1.EaName + ffeai1.EaNameLength + 1, ffeai1.EaValueLength);\n\n            if (value1 != ea1_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value1, ea1_value);\n\n            auto& ffeai2 = *static_cast<FILE_FULL_EA_INFORMATION*>(items[1]);\n\n            if (ffeai2.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai2.Flags);\n\n            auto name2 = string_view(ffeai2.EaName, ffeai2.EaNameLength);\n\n            if (name2 != \"FOO\")\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"FOO\\\"\", name2);\n\n            auto value2 = string_view(ffeai2.EaName + ffeai2.EaNameLength + 1, ffeai2.EaValueLength);\n\n            if (value2 != ea2_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value2, ea2_value);\n        });\n\n        auto exp_size = ea_size(ea1_name, ea1_value) + ea_size(ea2_name, ea2_value);\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", feai.EaSize, exp_size);\n        });\n\n        test(\"Query FileAllInformation\", [&]() {\n            auto buf = query_all_information(h.get());\n            auto& fai = *static_cast<FILE_ALL_INFORMATION*>(buf);\n\n            if (fai.EaInformation.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", fai.EaInformation.EaSize, exp_size);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\ea1\", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        static const string_view ea1_name = \"FOO\", ea1_value = \"bar\";\n        static const string_view ea2_name = \"HeLlO\", ea2_value = \"baz\";\n\n        test(\"Replace first EA\", [&]() {\n            write_ea(h.get(), ea2_name, ea2_value);\n        });\n\n        test(\"Read EAs\", [&]() {\n            auto items = read_ea(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2\", items.size());\n\n            auto& ffeai1 = *static_cast<FILE_FULL_EA_INFORMATION*>(items[0]);\n\n            if (ffeai1.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai1.Flags);\n\n            auto name1 = string_view(ffeai1.EaName, ffeai1.EaNameLength);\n\n            if (name1 != ea1_name)\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"{}\\\"\", name1, ea1_name);\n\n            auto value1 = string_view(ffeai1.EaName + ffeai1.EaNameLength + 1, ffeai1.EaValueLength);\n\n            if (value1 != ea1_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value1, ea1_value);\n\n            auto& ffeai2 = *static_cast<FILE_FULL_EA_INFORMATION*>(items[1]);\n\n            if (ffeai2.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai2.Flags);\n\n            auto name2 = string_view(ffeai2.EaName, ffeai2.EaNameLength);\n\n            if (name2 != \"HELLO\")\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"HELLO\\\"\", name2);\n\n            auto value2 = string_view(ffeai2.EaName + ffeai2.EaNameLength + 1, ffeai2.EaValueLength);\n\n            if (value2 != ea2_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value2, ea2_value);\n        });\n\n        auto exp_size = ea_size(ea1_name, ea1_value) + ea_size(ea2_name, ea2_value);\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", feai.EaSize, exp_size);\n        });\n\n        test(\"Query FileAllInformation\", [&]() {\n            auto buf = query_all_information(h.get());\n            auto& fai = *static_cast<FILE_ALL_INFORMATION*>(buf);\n\n            if (fai.EaInformation.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", fai.EaInformation.EaSize, exp_size);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\ea1\", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        static const string_view ea1_name = \"FOO\", ea1_value = \"bar\";\n        static const string_view ea2_name = \"HELlo\";\n\n        test(\"Delete first EA\", [&]() {\n            write_ea(h.get(), ea2_name, \"\");\n        });\n\n        test(\"Read EAs\", [&]() {\n            auto items = read_ea(h.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& ffeai1 = *static_cast<FILE_FULL_EA_INFORMATION*>(items[0]);\n\n            if (ffeai1.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai1.Flags);\n\n            auto name1 = string_view(ffeai1.EaName, ffeai1.EaNameLength);\n\n            if (name1 != ea1_name)\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"{}\\\"\", name1, ea1_name);\n\n            auto value1 = string_view(ffeai1.EaName + ffeai1.EaNameLength + 1, ffeai1.EaValueLength);\n\n            if (value1 != ea1_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value1, ea1_value);\n        });\n\n        auto exp_size = ea_size(ea1_name, ea1_value);\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", feai.EaSize, exp_size);\n        });\n\n        test(\"Query FileAllInformation\", [&]() {\n            auto buf = query_all_information(h.get());\n            auto& fai = *static_cast<FILE_ALL_INFORMATION*>(buf);\n\n            if (fai.EaInformation.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", fai.EaInformation.EaSize, exp_size);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\ea1\", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        static const string_view ea1_name = \"foo\";\n\n        test(\"Delete second EA\", [&]() {\n            write_ea(h.get(), ea1_name, \"\");\n        });\n\n        test(\"Read EAs\", [&]() {\n            exp_status([&]() {\n                read_ea(h.get());\n            }, STATUS_NO_EAS_ON_FILE);\n        });\n\n        uint32_t exp_size = 0;\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", feai.EaSize, exp_size);\n        });\n\n        test(\"Query FileAllInformation\", [&]() {\n            auto buf = query_all_information(h.get());\n            auto& fai = *static_cast<FILE_ALL_INFORMATION*>(buf);\n\n            if (fai.EaInformation.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", fai.EaInformation.EaSize, exp_size);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"ea1\", exp_size);\n        });\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\ea2\", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        static const string_view ea1_name = \"qux\", ea1_value = \"xyzzy\";\n        static const string_view ea2_name = \"y2\", ea2_value = \"plugh\";\n\n        test(\"Add two EAs\", [&]() {\n            write_eas(h.get(), array{ pair(ea1_name, ea1_value), pair(ea2_name, ea2_value) });\n        });\n\n        test(\"Read EAs\", [&]() {\n            auto items = read_ea(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2\", items.size());\n\n            auto& ffeai1 = *static_cast<FILE_FULL_EA_INFORMATION*>(items[0]);\n\n            if (ffeai1.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai1.Flags);\n\n            auto name1 = string_view(ffeai1.EaName, ffeai1.EaNameLength);\n\n            if (name1 != \"QUX\")\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"QUX\\\"\", name1);\n\n            auto value1 = string_view(ffeai1.EaName + ffeai1.EaNameLength + 1, ffeai1.EaValueLength);\n\n            if (value1 != ea1_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value1, ea1_value);\n\n            auto& ffeai2 = *static_cast<FILE_FULL_EA_INFORMATION*>(items[1]);\n\n            if (ffeai2.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai2.Flags);\n\n            auto name2 = string_view(ffeai2.EaName, ffeai2.EaNameLength);\n\n            if (name2 != \"Y2\")\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"Y2\\\"\", name2);\n\n            auto value2 = string_view(ffeai2.EaName + ffeai2.EaNameLength + 1, ffeai2.EaValueLength);\n\n            if (value2 != ea2_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value2, ea2_value);\n        });\n\n        uint32_t exp_size = ea_size(ea1_name, ea1_value) + ea_size(ea2_name, ea2_value);\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", feai.EaSize, exp_size);\n        });\n\n        test(\"Query FileAllInformation\", [&]() {\n            auto buf = query_all_information(h.get());\n            auto& fai = *static_cast<FILE_ALL_INFORMATION*>(buf);\n\n            if (fai.EaInformation.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", fai.EaInformation.EaSize, exp_size);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"ea2\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"ea2\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"ea2\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"ea2\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"ea2\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"ea2\", exp_size);\n        });\n    }\n\n    test(\"Create file with EAs\", [&]() {\n        static const string_view ea1_name = \"foo\", ea1_value = \"bar\";\n        static const string_view ea2_name = \"baz\", ea2_value = \"quux\";\n\n        h = create_file_ea(dir + u\"\\\\ea3\", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0,\n                           FILE_CREATE, 0, FILE_CREATED, nullopt,\n                           array{ pair(ea1_name, ea1_value), pair(ea2_name, ea2_value) });\n    });\n\n    if (h) {\n        static const string_view ea1_name = \"foo\", ea1_value = \"bar\";\n        static const string_view ea2_name = \"baz\", ea2_value = \"quux\";\n\n        test(\"Read EAs\", [&]() {\n            auto items = read_ea(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2\", items.size());\n\n            auto& ffeai1 = *static_cast<FILE_FULL_EA_INFORMATION*>(items[0]);\n\n            if (ffeai1.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai1.Flags);\n\n            auto name1 = string_view(ffeai1.EaName, ffeai1.EaNameLength);\n\n            if (name1 != \"FOO\")\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"FOO\\\"\", name1);\n\n            auto value1 = string_view(ffeai1.EaName + ffeai1.EaNameLength + 1, ffeai1.EaValueLength);\n\n            if (value1 != ea1_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value1, ea1_value);\n\n            auto& ffeai2 = *static_cast<FILE_FULL_EA_INFORMATION*>(items[1]);\n\n            if (ffeai2.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai2.Flags);\n\n            auto name2 = string_view(ffeai2.EaName, ffeai2.EaNameLength);\n\n            if (name2 != \"BAZ\")\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"BAZ\\\"\", name2);\n\n            auto value2 = string_view(ffeai2.EaName + ffeai2.EaNameLength + 1, ffeai2.EaValueLength);\n\n            if (value2 != ea2_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value2, ea2_value);\n        });\n\n        uint32_t exp_size = ea_size(ea1_name, ea1_value) + ea_size(ea2_name, ea2_value);\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", feai.EaSize, exp_size);\n        });\n\n        test(\"Query FileAllInformation\", [&]() {\n            auto buf = query_all_information(h.get());\n            auto& fai = *static_cast<FILE_ALL_INFORMATION*>(buf);\n\n            if (fai.EaInformation.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", fai.EaInformation.EaSize, exp_size);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"ea3\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"ea3\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"ea3\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"ea3\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"ea3\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"ea3\", exp_size);\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\ea4\", FILE_READ_EA | FILE_WRITE_EA | FILE_READ_ATTRIBUTES, 0, 0,\n                        FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        static const string_view ea_name = \"hello\", ea_value = \"world\";\n\n        test(\"Write EA\", [&]() {\n            write_ea(h.get(), ea_name, ea_value);\n        });\n\n        test(\"Read EA\", [&]() {\n            auto items = read_ea(h.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& ffeai = *static_cast<FILE_FULL_EA_INFORMATION*>(items.front());\n\n            if (ffeai.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai.Flags);\n\n            auto name = string_view(ffeai.EaName, ffeai.EaNameLength);\n\n            if (name != \"HELLO\") // gets capitalized\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"HELLO\\\"\", name);\n\n            auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength);\n\n            if (value != ea_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value, ea_value);\n        });\n\n        auto exp_size = ea_size(ea_name, ea_value);\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", feai.EaSize, exp_size);\n        });\n\n        test(\"Query FileAllInformation\", [&]() {\n            auto buf = query_all_information(h.get());\n            auto& fai = *static_cast<FILE_ALL_INFORMATION*>(buf);\n\n            if (fai.EaInformation.EaSize != exp_size)\n                throw formatted_error(\"EaSize was {}, expected {}\", fai.EaInformation.EaSize, exp_size);\n        });\n\n        h.reset();\n\n        // EaSize in dirents not updated until file closed?\n        test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"ea4\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"ea4\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"ea4\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"ea4\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"ea4\", exp_size);\n        });\n\n        test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n            check_ea_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"ea4\", exp_size);\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\ea5\", FILE_WRITE_EA | FILE_READ_EA, 0, 0,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        static const string_view ea_name = \"hello\", ea_value = \"world\";\n\n        test(\"Write EA with FILE_NEED_EA\", [&]() {\n            write_ea(h.get(), ea_name, ea_value, true);\n        });\n\n        test(\"Read EA\", [&]() {\n            auto items = read_ea(h.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& ffeai = *static_cast<FILE_FULL_EA_INFORMATION*>(items.front());\n\n            if (ffeai.Flags != FILE_NEED_EA)\n                throw formatted_error(\"Flags was {:x}, expected FILE_NEED_EA\", ffeai.Flags);\n\n            auto name = string_view(ffeai.EaName, ffeai.EaNameLength);\n\n            if (name != \"HELLO\") // gets capitalized\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"HELLO\\\"\", name);\n\n            auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength);\n\n            if (value != ea_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value, ea_value);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file with FILE_NO_EA_KNOWLEDGE\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\ea5\", FILE_WRITE_EA | FILE_READ_EA, 0, 0,\n                        FILE_CREATE, FILE_NO_EA_KNOWLEDGE, FILE_CREATED);\n        }, STATUS_ACCESS_DENIED);\n    });\n\n    test(\"Create file without FILE_WRITE_EA\", [&]() {\n        h = create_file(dir + u\"\\\\ea6\", FILE_READ_EA, 0, 0,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        static const string_view ea_name = \"hello\", ea_value = \"world\";\n\n        test(\"Try to write EA\", [&]() {\n            exp_status([&]() {\n                write_ea(h.get(), ea_name, ea_value, false);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file without FILE_READ_EA\", [&]() {\n        h = create_file(dir + u\"\\\\ea6\", FILE_WRITE_EA, 0, 0,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Try to read EA\", [&]() {\n            exp_status([&]() {\n                read_ea(h.get());\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\ea7\", FILE_WRITE_EA | FILE_READ_EA, 0, 0,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        static const string_view ea1_name = \"hello\", ea1_value = \"world\";\n        static const string_view ea2_name = \"foo\", ea2_value = \"bar\";\n        static const string_view ea3_name = \"xyzzy\", ea3_value = \"plugh\";\n\n        test(\"Write EAs\", [&]() {\n            write_eas(h.get(), array{ pair(ea1_name, ea1_value), pair(ea2_name, ea2_value), pair(ea3_name, ea3_value) });\n        });\n\n        test(\"Read EAs with filter\", [&]() {\n            auto items = read_eas(h.get(), array{ ea2_name, ea3_name }, false, nullptr, true);\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2\", items.size());\n\n            auto& ffeai1 = *static_cast<FILE_FULL_EA_INFORMATION*>(items[0]);\n\n            if (ffeai1.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai1.Flags);\n\n            auto name1 = string_view(ffeai1.EaName, ffeai1.EaNameLength);\n\n            if (name1 != \"FOO\") // gets capitalized\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"FOO\\\"\", name1);\n\n            auto value1 = string_view(ffeai1.EaName + ffeai1.EaNameLength + 1, ffeai1.EaValueLength);\n\n            if (value1 != ea2_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value1, ea2_value);\n\n            auto& ffeai2 = *static_cast<FILE_FULL_EA_INFORMATION*>(items[1]);\n\n            if (ffeai2.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai2.Flags);\n\n            auto name2 = string_view(ffeai2.EaName, ffeai2.EaNameLength);\n\n            if (name2 != \"XYZZY\") // gets capitalized\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"FOO\\\"\", name2);\n\n            auto value2 = string_view(ffeai2.EaName + ffeai2.EaNameLength + 1, ffeai2.EaValueLength);\n\n            if (value2 != ea3_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value2, ea3_value);\n        });\n\n        test(\"Read single EA\", [&]() {\n            auto items = read_eas(h.get(), array{ ea2_name, ea3_name }, true, nullptr, true);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& ffeai = *static_cast<FILE_FULL_EA_INFORMATION*>(items.front());\n\n            if (ffeai.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai.Flags);\n\n            auto name = string_view(ffeai.EaName, ffeai.EaNameLength);\n\n            if (name != \"FOO\") // gets capitalized\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"FOO\\\"\", name);\n\n            auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength);\n\n            if (value != ea2_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value, ea2_value);\n        });\n\n        test(\"Read next EA\", [&]() { // still returns first entry\n            auto items = read_eas(h.get(), array{ ea2_name, ea3_name }, true, nullptr, false);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& ffeai = *static_cast<FILE_FULL_EA_INFORMATION*>(items.front());\n\n            if (ffeai.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai.Flags);\n\n            auto name = string_view(ffeai.EaName, ffeai.EaNameLength);\n\n            if (name != \"FOO\") // gets capitalized\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"FOO\\\"\", name);\n\n            auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength);\n\n            if (value != ea2_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value, ea2_value);\n        });\n\n        test(\"Read single EA without filter\", [&]() {\n            auto items = read_eas(h.get(), array<string_view, 0>{}, true, nullptr, true);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& ffeai = *static_cast<FILE_FULL_EA_INFORMATION*>(items.front());\n\n            if (ffeai.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai.Flags);\n\n            auto name = string_view(ffeai.EaName, ffeai.EaNameLength);\n\n            if (name != \"HELLO\") // gets capitalized\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"HELLO\\\"\", name);\n\n            auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength);\n\n            if (value != ea1_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value, ea1_value);\n        });\n\n        test(\"Read single EA without filter\", [&]() {\n            auto items = read_eas(h.get(), array<string_view, 0>{}, true, nullptr, false);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& ffeai = *static_cast<FILE_FULL_EA_INFORMATION*>(items.front());\n\n            if (ffeai.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai.Flags);\n\n            auto name = string_view(ffeai.EaName, ffeai.EaNameLength);\n\n            if (name != \"FOO\") // gets capitalized\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"FOO\\\"\", name);\n\n            auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength);\n\n            if (value != ea2_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value, ea2_value);\n        });\n\n        test(\"Read single EA without filter\", [&]() {\n            auto items = read_eas(h.get(), array<string_view, 0>{}, true, nullptr, false);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& ffeai = *static_cast<FILE_FULL_EA_INFORMATION*>(items.front());\n\n            if (ffeai.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai.Flags);\n\n            auto name = string_view(ffeai.EaName, ffeai.EaNameLength);\n\n            if (name != \"XYZZY\") // gets capitalized\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"XYZZY\\\"\", name);\n\n            auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength);\n\n            if (value != ea3_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value, ea3_value);\n        });\n\n        test(\"Read single EA without filter\", [&]() {\n            exp_status([&]() {\n                read_eas(h.get(), array<string_view, 0>{}, true, nullptr, false);\n            }, STATUS_NO_MORE_EAS);\n        });\n\n        test(\"Read single EA with offset\", [&]() {\n            ULONG index = 3; // starts at 1\n            auto items = read_eas(h.get(), array<string_view, 0>{}, true, &index, false);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& ffeai = *static_cast<FILE_FULL_EA_INFORMATION*>(items.front());\n\n            if (ffeai.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai.Flags);\n\n            auto name = string_view(ffeai.EaName, ffeai.EaNameLength);\n\n            if (name != \"XYZZY\") // gets capitalized\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"XYZZY\\\"\", name);\n\n            auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength);\n\n            if (value != ea3_value)\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"{}\\\"\", value, ea3_value);\n        });\n\n        test(\"Read non-existent EA\", [&]() {\n            auto items = read_eas(h.get(), array{ \"baz\"sv }, false, nullptr, true);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& ffeai = *static_cast<FILE_FULL_EA_INFORMATION*>(items.front());\n\n            if (ffeai.Flags != 0)\n                throw formatted_error(\"Flags was {:x}, expected 0\", ffeai.Flags);\n\n            auto name = string_view(ffeai.EaName, ffeai.EaNameLength);\n\n            if (name != \"BAZ\") // gets capitalized\n                throw formatted_error(\"EA name was \\\"{}\\\", expected \\\"BAZ\\\"\", name);\n\n            auto value = string_view(ffeai.EaName + ffeai.EaNameLength + 1, ffeai.EaValueLength);\n\n            if (value != \"\")\n                throw formatted_error(\"EA value was \\\"{}\\\", expected \\\"\\\"\", value);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open volume\", [&]() {\n        auto colon = dir.find(u\":\");\n\n        if (colon == string::npos)\n            throw runtime_error(\"Unable to extract volume path from directory.\");\n\n        auto vol = dir.substr(0, colon + 1);\n\n        h = create_file(vol, FILE_WRITE_EA | FILE_READ_EA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Try to write EA on volume\", [&]() {\n            exp_status([&]() {\n                write_ea(h.get(), \"HELLO\", \"world\", true);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Try to read EA on volume\", [&]() {\n            exp_status([&]() {\n                read_ea(h.get());\n            }, STATUS_INVALID_PARAMETER);\n        });\n    }\n}\n"
  },
  {
    "path": "src/tests/fileinfo.cpp",
    "content": "#include \"test.h\"\n\nusing namespace std;\n\nvoid set_basic_information(HANDLE h, int64_t creation_time, int64_t last_access_time,\n                           int64_t last_write_time, int64_t change_time, uint32_t attributes) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    FILE_BASIC_INFORMATION fbi;\n\n    fbi.CreationTime.QuadPart = creation_time;\n    fbi.LastAccessTime.QuadPart = last_access_time;\n    fbi.LastWriteTime.QuadPart = last_write_time;\n    fbi.ChangeTime.QuadPart = change_time;\n    fbi.FileAttributes = attributes;\n\n    Status = NtSetInformationFile(h, &iosb, &fbi, sizeof(fbi), FileBasicInformation);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != 0)\n        throw formatted_error(\"iosb.Information was {}, expected 0\", iosb.Information);\n                                  }\n\nvoid test_fileinfo(const u16string& dir) {\n    unique_handle h;\n    LARGE_INTEGER delay;\n\n    // ignoring LastAccessTime for the most part here, as the NtfsDisableLastAccessUpdate option means it's unpredictable\n\n    delay.QuadPart = -1000000; // 100ms - should be 2 seconds for FAT?\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\fileinfo1\", SYNCHRONIZE | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | FILE_WRITE_DATA,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        static const vector<uint8_t> data = {'a','b','c','d','e','f'};\n        FILE_BASIC_INFORMATION fbi;\n\n        test(\"Query basic information\", [&]() {\n            fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_ARCHIVE\", fbi.FileAttributes);\n        });\n\n        test(\"Set times and attributes to 0\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, 0);\n        });\n\n        test(\"Check times and attributes unchanged\", [&]() {\n            auto fbi2 = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart)\n                throw formatted_error(\"CreationTime was {}, expected {}\", fbi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart);\n\n            if (fbi2.LastAccessTime.QuadPart != fbi.LastAccessTime.QuadPart)\n                throw formatted_error(\"LastAccessTime was {}, expected {}\", fbi2.LastAccessTime.QuadPart, fbi.LastAccessTime.QuadPart);\n\n            if (fbi2.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart)\n                throw formatted_error(\"LastWriteTime was {}, expected {}\", fbi2.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart);\n\n            if (fbi2.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart)\n                throw formatted_error(\"ChangeTime was {}, expected {}\", fbi2.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart);\n\n            if (fbi2.FileAttributes != fbi.FileAttributes)\n                throw formatted_error(\"FileAttributes was {:x}, expected {:x}\", fbi2.FileAttributes, fbi.FileAttributes);\n        });\n\n        NtDelayExecution(false, &delay);\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), data);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi2 = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart)\n                throw formatted_error(\"CreationTime was {}, expected {}\", fbi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart);\n\n            if (fbi2.LastWriteTime.QuadPart == fbi.LastWriteTime.QuadPart)\n                throw runtime_error(\"LastWriteTime was unchanged\");\n\n            if (fbi2.ChangeTime.QuadPart == fbi.ChangeTime.QuadPart)\n                throw runtime_error(\"ChangeTime was unchanged\");\n\n            if (fbi2.FileAttributes != fbi.FileAttributes)\n                throw formatted_error(\"FileAttributes was {:x}, expected {:x}\", fbi2.FileAttributes, fbi.FileAttributes);\n\n            fbi = fbi2;\n        });\n\n        test(\"Set LastWriteTime to -1\", [&]() {\n            set_basic_information(h.get(), 0, 0, -1, 0, 0);\n        });\n\n        NtDelayExecution(false, &delay);\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), data);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi2 = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart)\n                throw formatted_error(\"CreationTime was {}, expected {}\", fbi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart);\n\n            if (fbi2.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart)\n                throw formatted_error(\"LastWriteTime was {}, expected {}\", fbi2.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart);\n\n            if (fbi2.ChangeTime.QuadPart == fbi.ChangeTime.QuadPart)\n                throw runtime_error(\"ChangeTime was unchanged\");\n\n            if (fbi2.FileAttributes != fbi.FileAttributes)\n                throw formatted_error(\"FileAttributes was {:x}, expected {:x}\", fbi2.FileAttributes, fbi.FileAttributes);\n\n            fbi = fbi2;\n        });\n\n        test(\"Set ChangeTime to -1\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, -1, 0);\n        });\n\n        NtDelayExecution(false, &delay);\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), data);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi2 = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart)\n                throw formatted_error(\"CreationTime was {}, expected {}\", fbi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart);\n\n            if (fbi2.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart)\n                throw formatted_error(\"LastWriteTime was {}, expected {}\", fbi2.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart);\n\n            if (fbi2.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart)\n                throw formatted_error(\"ChangeTime was {}, expected {}\", fbi2.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart);\n\n            if (fbi2.FileAttributes != fbi.FileAttributes)\n                throw formatted_error(\"FileAttributes was {:x}, expected {:x}\", fbi2.FileAttributes, fbi.FileAttributes);\n\n            fbi = fbi2;\n        });\n\n        test(\"Set LastWriteTime to -2\", [&]() {\n            set_basic_information(h.get(), 0, 0, -2, 0, 0);\n        });\n\n        NtDelayExecution(false, &delay);\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), data);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi2 = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart)\n                throw formatted_error(\"CreationTime was {}, expected {}\", fbi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart);\n\n            if (fbi2.LastWriteTime.QuadPart == fbi.LastWriteTime.QuadPart)\n                throw runtime_error(\"LastWriteTime was unchanged\");\n\n            if (fbi2.ChangeTime.QuadPart != fbi.ChangeTime.QuadPart)\n                throw formatted_error(\"ChangeTime was {}, expected {}\", fbi2.ChangeTime.QuadPart, fbi.ChangeTime.QuadPart);\n\n            if (fbi2.FileAttributes != fbi.FileAttributes)\n                throw formatted_error(\"FileAttributes was {:x}, expected {:x}\", fbi2.FileAttributes, fbi.FileAttributes);\n\n            fbi = fbi2;\n        });\n\n        test(\"Set ChangeTime to -2\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, -2, 0);\n        });\n\n        NtDelayExecution(false, &delay);\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), data);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi2 = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi2.CreationTime.QuadPart != fbi.CreationTime.QuadPart)\n                throw formatted_error(\"CreationTime was {}, expected {}\", fbi2.CreationTime.QuadPart, fbi.CreationTime.QuadPart);\n\n            if (fbi2.LastWriteTime.QuadPart == fbi.LastWriteTime.QuadPart)\n                throw runtime_error(\"LastWriteTime was unchanged\");\n\n            if (fbi2.ChangeTime.QuadPart == fbi.ChangeTime.QuadPart)\n                throw runtime_error(\"ChangeTime was unchanged\");\n\n            if (fbi2.FileAttributes != fbi.FileAttributes)\n                throw formatted_error(\"FileAttributes was {:x}, expected {:x}\", fbi2.FileAttributes, fbi.FileAttributes);\n\n            fbi = fbi2;\n        });\n\n        int64_t date_val = 128790414900000000; // 2009-02-13T23:31:30\n\n        NtDelayExecution(false, &delay);\n\n        test(\"Set CreationTime\", [&]() {\n            set_basic_information(h.get(), date_val, 0, 0, 0, 0);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi2 = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi2.CreationTime.QuadPart != date_val)\n                throw formatted_error(\"CreationTime was {}, expected {}\", fbi2.CreationTime.QuadPart, date_val);\n\n            if (fbi2.LastWriteTime.QuadPart != fbi.LastWriteTime.QuadPart)\n                throw formatted_error(\"LastWriteTime was {}, expected {}\", fbi2.LastWriteTime.QuadPart, fbi.LastWriteTime.QuadPart);\n\n            if (fbi2.ChangeTime.QuadPart == fbi.ChangeTime.QuadPart)\n                throw runtime_error(\"ChangeTime was unchanged\");\n\n            if (fbi2.FileAttributes != fbi.FileAttributes)\n                throw formatted_error(\"FileAttributes was {:x}, expected {:x}\", fbi2.FileAttributes, fbi.FileAttributes);\n\n            fbi = fbi2;\n        });\n\n        NtDelayExecution(false, &delay);\n\n        test(\"Set LastWriteTime\", [&]() {\n            set_basic_information(h.get(), 0, 0, date_val, 0, 0);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi2 = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi2.CreationTime.QuadPart != date_val)\n                throw formatted_error(\"CreationTime was {}, expected {}\", fbi2.CreationTime.QuadPart, date_val);\n\n            if (fbi2.LastWriteTime.QuadPart != date_val)\n                throw formatted_error(\"LastWriteTime was {}, expected {}\", fbi2.LastWriteTime.QuadPart, date_val);\n\n            if (fbi2.ChangeTime.QuadPart == fbi.ChangeTime.QuadPart)\n                throw runtime_error(\"ChangeTime was unchanged\");\n\n            if (fbi2.FileAttributes != fbi.FileAttributes)\n                throw formatted_error(\"FileAttributes was {:x}, expected {:x}\", fbi2.FileAttributes, fbi.FileAttributes);\n\n            fbi = fbi2;\n        });\n\n        NtDelayExecution(false, &delay);\n\n        test(\"Set ChangeTime\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, date_val, 0);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi2 = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi2.CreationTime.QuadPart != date_val)\n                throw formatted_error(\"CreationTime was {}, expected {}\", fbi2.CreationTime.QuadPart, date_val);\n\n            if (fbi2.LastWriteTime.QuadPart != date_val)\n                throw formatted_error(\"LastWriteTime was {}, expected {}\", fbi2.LastWriteTime.QuadPart, date_val);\n\n            if (fbi2.ChangeTime.QuadPart != date_val)\n                throw formatted_error(\"ChangeTime was {}, expected {}\", fbi2.ChangeTime.QuadPart, date_val);\n\n            if (fbi2.FileAttributes != fbi.FileAttributes)\n                throw formatted_error(\"FileAttributes was {:x}, expected {:x}\", fbi2.FileAttributes, fbi.FileAttributes);\n\n            fbi = fbi2;\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file without FILE_READ_ATTRIBUTES\", [&]() {\n        h = create_file(dir + u\"\\\\fileinfo1\", FILE_WRITE_ATTRIBUTES,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Try to query basic information\", [&]() {\n            exp_status([&]() {\n                query_information<FILE_BASIC_INFORMATION>(h.get());\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file without FILE_WRITE_ATTRIBUTES\", [&]() {\n        h = create_file(dir + u\"\\\\fileinfo1\", FILE_READ_ATTRIBUTES,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Try to set basic information\", [&]() {\n            exp_status([&]() {\n                set_basic_information(h.get(), 0, 0, 0, 0, 0);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\fileinfo1\", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Set hidden flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_HIDDEN);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_HIDDEN)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_HIDDEN\", fbi.FileAttributes);\n        });\n\n        test(\"Set attributes to FILE_ATTRIBUTE_NORMAL\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL\", fbi.FileAttributes);\n        });\n\n        test(\"Set attributes to FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_READONLY\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_READONLY);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_READONLY)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_READONLY\", fbi.FileAttributes);\n        });\n\n        test(\"Try to set attributes to FILE_ATTRIBUTE_DIRECTORY\", [&]() { // fails\n            exp_status([&]() {\n                set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_DIRECTORY);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Set attributes to FILE_ATTRIBUTE_REPARSE_POINT\", [&]() { // gets ignored\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_REPARSE_POINT);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL\", fbi.FileAttributes);\n        });\n\n        test(\"Set attributes to FILE_ATTRIBUTE_SPARSE_FILE\", [&]() { // gets ignored\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_SPARSE_FILE);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL\", fbi.FileAttributes);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\fileinfo2\", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,\n                        0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set hidden flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_HIDDEN);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN))\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_HIDDEN\", fbi.FileAttributes);\n        });\n\n        test(\"Set attributes to FILE_ATTRIBUTE_NORMAL\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY\", fbi.FileAttributes);\n        });\n\n        test(\"Set attributes to FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_READONLY\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_READONLY);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY))\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_READONLY\", fbi.FileAttributes);\n        });\n\n        test(\"Set attributes to FILE_ATTRIBUTE_DIRECTORY\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_DIRECTORY);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY\", fbi.FileAttributes);\n        });\n\n        test(\"Set attributes to FILE_ATTRIBUTE_REPARSE_POINT\", [&]() { // gets ignored\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_REPARSE_POINT);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY\", fbi.FileAttributes);\n        });\n\n        test(\"Set attributes to FILE_ATTRIBUTE_SPARSE_FILE\", [&]() { // gets ignored\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_SPARSE_FILE);\n        });\n\n        test(\"Query basic information\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY\", fbi.FileAttributes);\n        });\n\n        h.reset();\n    }\n}\n"
  },
  {
    "path": "src/tests/io.cpp",
    "content": "#include \"test.h\"\n#include <random>\n#include <span>\n#include <array>\n\nusing namespace std;\n\n#define FSCTL_SET_ZERO_DATA CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 50, METHOD_BUFFERED, FILE_WRITE_DATA)\n\nvoid adjust_token_privileges(HANDLE token, const LUID_AND_ATTRIBUTES& priv) {\n    NTSTATUS Status;\n    array<uint8_t, offsetof(TOKEN_PRIVILEGES, Privileges) + sizeof(LUID_AND_ATTRIBUTES)> buf;\n    auto& tp = *(TOKEN_PRIVILEGES*)buf.data();\n\n    tp.PrivilegeCount = 1;\n    tp.Privileges[0] = priv;\n\n    Status = NtAdjustPrivilegesToken(token, false, &tp, 0, nullptr, nullptr);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nvoid set_allocation(HANDLE h, uint64_t alloc) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    FILE_ALLOCATION_INFORMATION fai;\n\n    fai.AllocationSize.QuadPart = alloc;\n\n    Status = NtSetInformationFile(h, &iosb, &fai, sizeof(fai), FileAllocationInformation);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != 0)\n        throw formatted_error(\"iosb.Information was {}, expected 0\", iosb.Information);\n}\n\nvector<uint8_t> random_data(size_t len) {\n    vector<uint8_t> random(len);\n\n    random_device rd;\n    mt19937 gen(rd());\n    uniform_int_distribution<uint32_t> distrib(0, 0xffffffff);\n\n    for (auto& s : span((uint32_t*)random.data(), random.size() / sizeof(uint32_t))) {\n        s = distrib(gen);\n    }\n\n    return random;\n}\n\nvoid write_file(HANDLE h, span<const uint8_t> data, optional<uint64_t> offset) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    LARGE_INTEGER off;\n\n    if (offset)\n        off.QuadPart = *offset;\n\n    Status = NtWriteFile(h, nullptr, nullptr, nullptr, &iosb, (void*)data.data(),\n                         data.size(), offset ? &off : nullptr,\n                         nullptr);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != data.size())\n        throw formatted_error(\"iosb.Information was {}, expected {}\", iosb.Information, data.size());\n}\n\nunique_handle create_event() {\n    NTSTATUS Status;\n    HANDLE h;\n\n    Status = NtCreateEvent(&h, EVENT_ALL_ACCESS, nullptr, NotificationEvent, false);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    return unique_handle(h);\n}\n\nvoid write_file_wait(HANDLE h, span<const uint8_t> data, optional<uint64_t> offset) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    LARGE_INTEGER off;\n\n    if (offset)\n        off.QuadPart = *offset;\n\n    auto event = create_event();\n\n    Status = NtWriteFile(h, event.get(), nullptr, nullptr, &iosb, (void*)data.data(),\n                         data.size(), offset ? &off : nullptr,\n                         nullptr);\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(event.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != data.size())\n        throw formatted_error(\"iosb.Information was {}, expected {}\", iosb.Information, data.size());\n}\n\nvector<uint8_t> read_file(HANDLE h, ULONG len, optional<uint64_t> offset) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf(len);\n    LARGE_INTEGER off;\n\n    if (offset)\n        off.QuadPart = *offset;\n\n    buf.resize(len);\n\n    Status = NtReadFile(h, nullptr, nullptr, nullptr, &iosb,\n                        buf.data(), len, offset ? &off : nullptr,\n                        nullptr);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information > len)\n        throw formatted_error(\"iosb.Information was {}, expected maximum of {}\", iosb.Information, len);\n\n    buf.resize(iosb.Information);\n\n    return buf;\n}\n\nvector<uint8_t> read_file_wait(HANDLE h, ULONG len, optional<uint64_t> offset) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf(len);\n    LARGE_INTEGER off;\n\n    if (offset)\n        off.QuadPart = *offset;\n\n    buf.resize(len);\n\n    auto event = create_event();\n\n    Status = NtReadFile(h, event.get(), nullptr, nullptr, &iosb,\n                        buf.data(), len, offset ? &off : nullptr,\n                        nullptr);\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(event.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information > len)\n        throw formatted_error(\"iosb.Information was {}, expected maximum of {}\", iosb.Information, len);\n\n    buf.resize(iosb.Information);\n\n    return buf;\n}\n\nstatic void set_position(HANDLE h, uint64_t pos) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    FILE_POSITION_INFORMATION fpi;\n\n    fpi.CurrentByteOffset.QuadPart = pos;\n\n    iosb.Information = 0xdeadbeef;\n\n    Status = NtSetInformationFile(h, &iosb, &fpi, sizeof(fpi), FilePositionInformation);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != 0)\n        throw formatted_error(\"iosb.Information was {}, expected 0\", iosb.Information);\n}\n\nvoid set_valid_data_length(HANDLE h, uint64_t vdl) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    FILE_VALID_DATA_LENGTH_INFORMATION fvdli;\n\n    fvdli.ValidDataLength.QuadPart = vdl;\n\n    Status = NtSetInformationFile(h, &iosb, &fvdli, sizeof(fvdli), FileValidDataLengthInformation);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != 0)\n        throw formatted_error(\"iosb.Information was {}, expected 0\", iosb.Information);\n}\n\nvoid set_end_of_file(HANDLE h, uint64_t eof) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    FILE_END_OF_FILE_INFORMATION feofi;\n\n    feofi.EndOfFile.QuadPart = eof;\n\n    Status = NtSetInformationFile(h, &iosb, &feofi, sizeof(feofi), FileEndOfFileInformation);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != 0)\n        throw formatted_error(\"iosb.Information was {}, expected 0\", iosb.Information);\n}\n\nvoid set_zero_data(HANDLE h, uint64_t start, uint64_t end) {\n    NTSTATUS Status;\n    FILE_ZERO_DATA_INFORMATION fzdi;\n    IO_STATUS_BLOCK iosb;\n\n    fzdi.FileOffset.QuadPart = start;\n    fzdi.BeyondFinalZero.QuadPart = end;\n\n    auto ev = create_event();\n\n    Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb,\n                             FSCTL_SET_ZERO_DATA, &fzdi, sizeof(fzdi),\n                             nullptr, 0);\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(ev.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nvoid test_io(HANDLE token, const u16string& dir) {\n    unique_handle h;\n\n    // needed to set VDL\n    test(\"Add SeManageVolumePrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    auto write_check = [&](uint64_t multiple, bool sector_align) {\n        auto random = random_data(multiple * 2);\n\n        test(\"Write file\", [&]() {\n            write_file(h.get(), random);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size()) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, random.size());\n            }\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if ((uint64_t)fsi.AllocationSize.QuadPart < random.size()) {\n                throw formatted_error(\"AllocationSize was {}, expected at least {}\",\n                                      fsi.AllocationSize.QuadPart, random.size());\n            }\n\n            if ((uint64_t)fsi.EndOfFile.QuadPart != random.size()) {\n                throw formatted_error(\"EndOfFile was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, random.size());\n            }\n        });\n\n        test(\"Check compression information\", [&]() {\n            auto fci = query_information<FILE_COMPRESSION_INFORMATION>(h.get());\n\n            if ((size_t)fci.CompressedFileSize.QuadPart != random.size())\n                throw formatted_error(\"CompressedFileSize was {}, expected {}\", fci.CompressedFileSize.QuadPart, random.size());\n        });\n\n        test(\"Read file at end\", [&]() {\n            exp_status([&]() {\n                read_file(h.get(), sector_align ? multiple : 100);\n            }, STATUS_END_OF_FILE);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size()) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, random.size());\n            }\n        });\n\n        if (sector_align) {\n            test(\"Set position\", [&]() {\n                set_position(h.get(), random.size() + multiple);\n            });\n\n            test(\"Check position\", [&]() {\n                auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n                if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size() + multiple) {\n                    throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                          fpi.CurrentByteOffset.QuadPart, random.size() + multiple);\n                }\n            });\n\n            test(\"Read file after end\", [&]() {\n                exp_status([&]() {\n                    read_file(h.get(), multiple);\n                }, STATUS_END_OF_FILE);\n            });\n\n            test(\"Check position\", [&]() {\n                auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n                if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size() + multiple) {\n                    throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                          fpi.CurrentByteOffset.QuadPart, random.size() + multiple);\n                }\n            });\n        } else {\n            test(\"Set position\", [&]() {\n                set_position(h.get(), random.size() + 100);\n            });\n\n            test(\"Check position\", [&]() {\n                auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n                if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size() + 100) {\n                    throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                        fpi.CurrentByteOffset.QuadPart, random.size() + 100);\n                }\n            });\n\n            test(\"Read file after end\", [&]() {\n                exp_status([&]() {\n                    read_file(h.get(), 100);\n                }, STATUS_END_OF_FILE);\n            });\n\n            test(\"Check position\", [&]() {\n                auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n                if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size() + 100) {\n                    throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                        fpi.CurrentByteOffset.QuadPart, random.size() + 100);\n                }\n            });\n        }\n\n        test(\"Set negative position\", [&]() {\n            exp_status([&]() {\n                set_position(h.get(), -100);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Set position to start\", [&]() {\n            set_position(h.get(), 0);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 0) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 0);\n            }\n        });\n\n        test(\"Read whole file\", [&]() {\n            auto ret = read_file(h.get(), random.size());\n\n            if (ret.size() != random.size())\n                throw formatted_error(\"{} bytes read, expected {}\", ret.size(), random.size());\n\n            if (memcmp(ret.data(), random.data(), random.size()))\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size()) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, random.size());\n            }\n        });\n\n        test(\"Set position to start\", [&]() {\n            set_position(h.get(), 0);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 0) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 0);\n            }\n        });\n\n        if (sector_align) {\n            test(\"Try reading 100 bytes\", [&]() {\n                exp_status([&]() {\n                    read_file(h.get(), 100);\n                }, STATUS_INVALID_PARAMETER);\n            });\n\n            test(\"Try seting position to odd value near end\", [&]() {\n                exp_status([&]() {\n                    set_position(h.get(), random.size() - 100);\n                }, STATUS_INVALID_PARAMETER);\n            });\n\n            test(\"Set position to near end\", [&]() {\n                set_position(h.get(), random.size() - multiple);\n            });\n\n            test(\"Read past end of file\", [&]() {\n                auto ret = read_file(h.get(), multiple * 2);\n\n                if (ret.size() != multiple)\n                    throw formatted_error(\"{} bytes read, expected {}\", ret.size(), multiple);\n\n                if (memcmp(ret.data(), random.data() + random.size() - multiple, multiple))\n                    throw runtime_error(\"Data read did not match data written\");\n            });\n        } else {\n            test(\"Read 100 bytes\", [&]() {\n                auto ret = read_file(h.get(), 100);\n\n                if (ret.size() != 100)\n                    throw formatted_error(\"{} bytes read, expected {}\", ret.size(), 100);\n\n                if (memcmp(ret.data(), random.data(), 100))\n                    throw runtime_error(\"Data read did not match data written\");\n            });\n\n            test(\"Check position\", [&]() {\n                auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n                if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 100) {\n                    throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                        fpi.CurrentByteOffset.QuadPart, 100);\n                }\n            });\n\n            test(\"Set position to near end\", [&]() {\n                set_position(h.get(), random.size() - 100);\n            });\n\n            test(\"Check position\", [&]() {\n                auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n                if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size() - 100) {\n                    throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                        fpi.CurrentByteOffset.QuadPart, random.size() - 100);\n                }\n            });\n\n            test(\"Read past end of file\", [&]() {\n                auto ret = read_file(h.get(), 200);\n\n                if (ret.size() != 100)\n                    throw formatted_error(\"{} bytes read, expected {}\", ret.size(), 100);\n\n                if (memcmp(ret.data(), random.data() + random.size() - 100, 100))\n                    throw runtime_error(\"Data read did not match data written\");\n            });\n\n            test(\"Check position\", [&]() {\n                auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n                if ((uint64_t)fpi.CurrentByteOffset.QuadPart != random.size()) {\n                    throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                        fpi.CurrentByteOffset.QuadPart, random.size());\n                }\n            });\n        }\n\n\n        test(\"Extend file\", [&]() {\n            set_end_of_file(h.get(), multiple * 3);\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if ((uint64_t)fsi.EndOfFile.QuadPart != multiple * 3) {\n                throw formatted_error(\"EndOfFile was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, random.size());\n            }\n        });\n\n        test(\"Read new end of file\", [&]() {\n            auto ret = read_file(h.get(), multiple * 2);\n\n            if (ret.size() != multiple)\n                throw formatted_error(\"{} bytes read, expected {}\", ret.size(), multiple);\n\n            auto it = ranges::find_if(ret, [](uint8_t c) {\n                return c != 0;\n            });\n\n            if (it != ret.end())\n                throw runtime_error(\"End of file not zeroed\");\n        });\n\n        test(\"Try setting valid data length to 0\", [&]() {\n            exp_status([&]() {\n                set_valid_data_length(h.get(), 0);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Set valid data length to end of file\", [&]() {\n            set_valid_data_length(h.get(), multiple * 3);\n        });\n\n        test(\"Try setting valid data length to after end of file\", [&]() {\n            exp_status([&]() {\n                set_valid_data_length(h.get(), multiple * 4);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Truncate file\", [&]() {\n            set_end_of_file(h.get(), multiple);\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if ((uint64_t)fsi.EndOfFile.QuadPart != multiple) {\n                throw formatted_error(\"EndOfFile was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, multiple);\n            }\n        });\n\n        test(\"Set position to start\", [&]() {\n            set_position(h.get(), 0);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 0) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 0);\n            }\n        });\n\n        test(\"Read whole file\", [&]() {\n            auto ret = read_file(h.get(), random.size());\n\n            if (ret.size() != multiple)\n                throw formatted_error(\"{} bytes read, expected {}\", ret.size(), multiple);\n\n            if (memcmp(ret.data(), random.data(), multiple))\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != multiple) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, random.size());\n            }\n        });\n    };\n\n    test(\"Create file (long)\", [&]() {\n        h = create_file(dir + u\"\\\\io\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        write_check(4096, false);\n\n        h.reset();\n    }\n\n    test(\"Create file (short)\", [&]() {\n        h = create_file(dir + u\"\\\\ioshort\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        write_check(200, false);\n\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\io2\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Extend file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        test(\"Try setting valid data length without privilege\", [&]() {\n            exp_status([&]() {\n                set_valid_data_length(h.get(), 4096);\n            }, STATUS_PRIVILEGE_NOT_HELD);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\ioalloc\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set allocation to 4096\", [&]() {\n            set_allocation(h.get(), 4096);\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if ((uint64_t)fsi.AllocationSize.QuadPart != 4096) {\n                throw formatted_error(\"AllocationSize was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 4096);\n            }\n\n            if ((uint64_t)fsi.EndOfFile.QuadPart != 0) {\n                throw formatted_error(\"EndOfFile was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 0);\n            }\n        });\n\n        auto random = random_data(4096);\n\n        test(\"Write data\", [&]() {\n            write_file(h.get(), random);\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if ((uint64_t)fsi.AllocationSize.QuadPart != 4096) {\n                throw formatted_error(\"AllocationSize was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 4096);\n            }\n\n            if ((uint64_t)fsi.EndOfFile.QuadPart != 4096) {\n                throw formatted_error(\"EndOfFile was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 4096);\n            }\n        });\n\n        test(\"Set allocation to 0\", [&]() {\n            set_allocation(h.get(), 0);\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if ((uint64_t)fsi.AllocationSize.QuadPart != 0) {\n                throw formatted_error(\"AllocationSize was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 0);\n            }\n\n            if ((uint64_t)fsi.EndOfFile.QuadPart != 0) {\n                throw formatted_error(\"EndOfFile was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 0);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\iodir\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try setting end of file\", [&]() {\n            exp_status([&]() {\n                set_end_of_file(h.get(), 0);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Try setting allocation\", [&]() {\n            exp_status([&]() {\n                set_allocation(h.get(), 0);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create preallocated file\", [&]() {\n        h = create_file(dir + u\"\\\\ioprealloc\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n                        FILE_CREATED, 4096);\n    });\n\n    if (h) {\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if ((uint64_t)fsi.AllocationSize.QuadPart != 4096) {\n                throw formatted_error(\"AllocationSize was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 4096);\n            }\n\n            if ((uint64_t)fsi.EndOfFile.QuadPart != 0) {\n                throw formatted_error(\"EndOfFile was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 0);\n            }\n        });\n\n        auto random = random_data(4096);\n\n        test(\"Write data\", [&]() {\n            write_file(h.get(), random);\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if ((uint64_t)fsi.AllocationSize.QuadPart != 4096) {\n                throw formatted_error(\"AllocationSize was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 4096);\n            }\n\n            if ((uint64_t)fsi.EndOfFile.QuadPart != 4096) {\n                throw formatted_error(\"EndOfFile was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 4096);\n            }\n        });\n\n        test(\"Set allocation to 0\", [&]() {\n            set_allocation(h.get(), 0);\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if ((uint64_t)fsi.AllocationSize.QuadPart != 0) {\n                throw formatted_error(\"AllocationSize was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 0);\n            }\n\n            if ((uint64_t)fsi.EndOfFile.QuadPart != 0) {\n                throw formatted_error(\"EndOfFile was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 0);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Create preallocated directory\", [&]() {\n        h = create_file(dir + u\"\\\\iopreallocdir\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n                        FILE_CREATED, 4096);\n    });\n\n    if (h) {\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if ((uint64_t)fsi.AllocationSize.QuadPart != 0) {\n                throw formatted_error(\"AllocationSize was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 0);\n            }\n\n            if ((uint64_t)fsi.EndOfFile.QuadPart != 0) {\n                throw formatted_error(\"EndOfFile was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 0);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\io3\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        vector<uint8_t> data = {'a','b','c','d','e','f'};\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), data);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != data.size()) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, data.size());\n            }\n        });\n\n        test(\"Read from file specifying offset\", [&]() {\n            auto buf = read_file(h.get(), 2, 0);\n\n            if (buf.size() != 2)\n                throw formatted_error(\"Read {} bytes, expected {}\", buf.size(), 2);\n\n            if (buf[0] != data[0] || buf[1] != data[1])\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 2) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 2);\n            }\n        });\n\n        test(\"Read from file specifying offset as FILE_USE_FILE_POINTER_POSITION\", [&]() {\n            LARGE_INTEGER li;\n\n            li.HighPart = -1;\n            li.LowPart = FILE_USE_FILE_POINTER_POSITION;\n\n            auto buf = read_file(h.get(), 2, li.QuadPart);\n\n            if (buf.size() != 2)\n                throw formatted_error(\"Read {} bytes, expected {}\", buf.size(), 2);\n\n            if (buf[0] != data[2] || buf[1] != data[3])\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 4) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 4);\n            }\n        });\n\n        vector<uint8_t> data2 = {'g','h'};\n\n        test(\"Write to file specifying offset as FILE_USE_FILE_POINTER_POSITION\", [&]() {\n            LARGE_INTEGER li;\n\n            li.HighPart = -1;\n            li.LowPart = FILE_USE_FILE_POINTER_POSITION;\n\n            write_file(h.get(), data2, li.QuadPart);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 6) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 6);\n            }\n        });\n\n        test(\"Set position to 0\", [&]() {\n            set_position(h.get(), 0);\n        });\n\n        vector<uint8_t> data3 = {'i','j'};\n\n        test(\"Write to file specifying offset as FILE_WRITE_TO_END_OF_FILE\", [&]() {\n            LARGE_INTEGER li;\n\n            li.HighPart = -1;\n            li.LowPart = FILE_WRITE_TO_END_OF_FILE;\n\n            write_file(h.get(), data3, li.QuadPart);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 8) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 8);\n            }\n        });\n\n        test(\"Try reading from file specifying offset as FILE_WRITE_TO_END_OF_FILE\", [&]() {\n            LARGE_INTEGER li;\n\n            li.HighPart = -1;\n            li.LowPart = FILE_WRITE_TO_END_OF_FILE;\n\n            exp_status([&]() {\n                read_file(h.get(), 2, li.QuadPart);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Set position to 0\", [&]() {\n            set_position(h.get(), 0);\n        });\n\n        test(\"Check file contents\", [&]() {\n            auto buf = read_file(h.get(), 8);\n\n            if (buf.size() != 8)\n                throw formatted_error(\"Read {} bytes, expected {}\", buf.size(), 8);\n\n            if (buf[0] != data[0] || buf[1] != data[1] || buf[2] != data[2] || buf[3] != data[3] ||\n                buf[4] != data2[0] || buf[5] != data2[1] || buf[6] != data3[0] || buf[7] != data3[1]) {\n                throw runtime_error(\"Data read did not match data written\");\n            }\n        });\n    }\n\n    test(\"Create file without file pointer\", [&]() {\n        h = create_file(dir + u\"\\\\io4\", FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        vector<uint8_t> data = {'a','b','c','d','e','f'};\n\n        test(\"Try writing to file with no offset specified\", [&]() {\n            exp_status([&]() {\n                write_file_wait(h.get(), data);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Write to file at 0\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Check position hasn't moved\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 0) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 0);\n            }\n        });\n\n        test(\"Set position\", [&]() {\n            set_position(h.get(), data.size());\n        });\n\n        test(\"Check position has now moved\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != data.size()) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, data.size());\n            }\n        });\n\n        test(\"Read from file specifying offset\", [&]() {\n            auto buf = read_file_wait(h.get(), 2, 0);\n\n            if (buf.size() != 2)\n                throw formatted_error(\"Read {} bytes, expected {}\", buf.size(), 2);\n\n            if (buf[0] != data[0] || buf[1] != data[1])\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != data.size()) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, data.size());\n            }\n        });\n\n        test(\"Read from file specifying offset as FILE_USE_FILE_POINTER_POSITION\", [&]() {\n            LARGE_INTEGER li;\n\n            li.HighPart = -1;\n            li.LowPart = FILE_USE_FILE_POINTER_POSITION;\n\n            exp_status([&]() {\n                read_file_wait(h.get(), 2, li.QuadPart);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        vector<uint8_t> data2 = {'g','h'};\n\n        test(\"Write to file specifying offset as FILE_USE_FILE_POINTER_POSITION\", [&]() {\n            LARGE_INTEGER li;\n\n            li.HighPart = -1;\n            li.LowPart = FILE_USE_FILE_POINTER_POSITION;\n\n            exp_status([&]() {\n                write_file_wait(h.get(), data2, li.QuadPart);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Set position to 0\", [&]() {\n            set_position(h.get(), 0);\n        });\n\n        vector<uint8_t> data3 = {'i','j'};\n\n        test(\"Write to file specifying offset as FILE_WRITE_TO_END_OF_FILE\", [&]() {\n            LARGE_INTEGER li;\n\n            li.HighPart = -1;\n            li.LowPart = FILE_WRITE_TO_END_OF_FILE;\n\n            write_file_wait(h.get(), data3, li.QuadPart);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 0) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 0);\n            }\n        });\n\n        test(\"Try reading from file specifying offset as FILE_WRITE_TO_END_OF_FILE\", [&]() {\n            LARGE_INTEGER li;\n\n            li.HighPart = -1;\n            li.LowPart = FILE_WRITE_TO_END_OF_FILE;\n\n            exp_status([&]() {\n                read_file_wait(h.get(), 2, li.QuadPart);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Check file contents\", [&]() {\n            auto buf = read_file_wait(h.get(), 8, 0);\n\n            if (buf.size() != 8)\n                throw formatted_error(\"Read {} bytes, expected {}\", buf.size(), 8);\n\n            if (buf[0] != data[0] || buf[1] != data[1] || buf[2] != data[2] || buf[3] != data[3] ||\n                buf[4] != data[4] || buf[5] != data[5] || buf[6] != data3[0] || buf[7] != data3[1]) {\n                throw runtime_error(\"Data read did not match data written\");\n            }\n        });\n    }\n\n    test(\"Create file for FILE_APPEND_DATA\", [&]() {\n        h = create_file(dir + u\"\\\\io5\", SYNCHRONIZE | FILE_READ_DATA | FILE_APPEND_DATA, 0, 0,\n                        FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        vector<uint8_t> data = {'a','b','c','d','e','f'};\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), data);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != data.size()) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, data.size());\n            }\n        });\n\n        test(\"Read from file specifying offset\", [&]() {\n            auto buf = read_file(h.get(), 2, 0);\n\n            if (buf.size() != 2)\n                throw formatted_error(\"Read {} bytes, expected {}\", buf.size(), 2);\n\n            if (buf[0] != data[0] || buf[1] != data[1])\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 2) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 2);\n            }\n        });\n\n        test(\"Read from file specifying offset as FILE_USE_FILE_POINTER_POSITION\", [&]() {\n            LARGE_INTEGER li;\n\n            li.HighPart = -1;\n            li.LowPart = FILE_USE_FILE_POINTER_POSITION;\n\n            auto buf = read_file(h.get(), 2, li.QuadPart);\n\n            if (buf.size() != 2)\n                throw formatted_error(\"Read {} bytes, expected {}\", buf.size(), 2);\n\n            if (buf[0] != data[2] || buf[1] != data[3])\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 4) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 4);\n            }\n        });\n\n        vector<uint8_t> data2 = {'g','h'};\n\n        test(\"Write to file specifying offset as FILE_USE_FILE_POINTER_POSITION\", [&]() {\n            LARGE_INTEGER li;\n\n            li.HighPart = -1;\n            li.LowPart = FILE_USE_FILE_POINTER_POSITION;\n\n            write_file(h.get(), data2, li.QuadPart);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 8) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 8);\n            }\n        });\n\n        test(\"Set position to 0\", [&]() {\n            set_position(h.get(), 0);\n        });\n\n        vector<uint8_t> data3 = {'i','j'};\n\n        test(\"Write to file specifying offset as 0\", [&]() {\n            write_file(h.get(), data3, 0);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 10) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 10);\n            }\n        });\n\n        test(\"Write to file specifying offset as FILE_WRITE_TO_END_OF_FILE\", [&]() {\n            LARGE_INTEGER li;\n\n            li.HighPart = -1;\n            li.LowPart = FILE_WRITE_TO_END_OF_FILE;\n\n            write_file(h.get(), data3, li.QuadPart);\n        });\n\n        test(\"Check position\", [&]() {\n            auto fpi = query_information<FILE_POSITION_INFORMATION>(h.get());\n\n            if ((uint64_t)fpi.CurrentByteOffset.QuadPart != 12) {\n                throw formatted_error(\"CurrentByteOffset was {}, expected {}\",\n                                      fpi.CurrentByteOffset.QuadPart, 12);\n            }\n        });\n\n        test(\"Try reading from file specifying offset as FILE_WRITE_TO_END_OF_FILE\", [&]() {\n            LARGE_INTEGER li;\n\n            li.HighPart = -1;\n            li.LowPart = FILE_WRITE_TO_END_OF_FILE;\n\n            exp_status([&]() {\n                read_file(h.get(), 2, li.QuadPart);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Set position to 0\", [&]() {\n            set_position(h.get(), 0);\n        });\n\n        test(\"Check file contents\", [&]() {\n            auto buf = read_file(h.get(), 12);\n\n            if (buf.size() != 12)\n                throw formatted_error(\"Read {} bytes, expected {}\", buf.size(), 12);\n\n            if (buf[0] != data[0] || buf[1] != data[1] || buf[2] != data[2] || buf[3] != data[3] ||\n                buf[4] != data[4] || buf[5] != data[5] || buf[6] != data2[0] || buf[7] != data2[1] ||\n                buf[8] != data3[0] || buf[9] != data3[1] || buf[10] != data3[0] || buf[11] != data3[1]) {\n                throw runtime_error(\"Data read did not match data written\");\n            }\n        });\n    }\n\n    test(\"Add SeManageVolumePrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file with FILE_NO_INTERMEDIATE_BUFFERING\", [&]() {\n        h = create_file(dir + u\"\\\\io6\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_NO_INTERMEDIATE_BUFFERING,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        write_check(4096, true);\n\n        auto random = random_data(100);\n\n        test(\"Try writing less than sector\", [&]() {\n            exp_status([&]() {\n                write_file(h.get(), random);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Try setting position to odd value\", [&]() {\n            exp_status([&]() {\n                set_position(h.get(), 100);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Set position to 0\", [&]() {\n            set_position(h.get(), 0);\n        });\n\n        test(\"Try reading less than sector\", [&]() {\n            exp_status([&]() {\n                read_file(h.get(), 100);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Set length to odd value\", [&]() {\n            set_end_of_file(h.get(), 100);\n        });\n\n        test(\"Set length to sector\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if ((uint64_t)fsi.EndOfFile.QuadPart != 4096) {\n                throw formatted_error(\"EndOfFile was {}, expected {}\",\n                                      fsi.EndOfFile.QuadPart, 4096);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\io7\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        auto random = random_data(150);\n\n        test(\"Write file\", [&]() {\n            write_file(h.get(), random);\n        });\n\n        test(\"Set zero data\", [&]() {\n            set_zero_data(h.get(), 25, 125);\n        });\n\n        test(\"Read file\", [&]() {\n            auto ret = read_file(h.get(), random.size(), 0);\n            auto exp = random;\n\n            memset(exp.data() + 25, 0, 100);\n\n            if (memcmp(ret.data(), exp.data(), exp.size()))\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\io8\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n                        FILE_CREATED);\n    });\n\n    if (h) {\n        auto random = random_data(4096 * 3);\n\n        test(\"Write file\", [&]() {\n            write_file(h.get(), random);\n        });\n\n        test(\"Set zero data\", [&]() {\n            set_zero_data(h.get(), 4096, 4096 * 2);\n        });\n\n        test(\"Read file\", [&]() {\n            auto ret = read_file(h.get(), random.size(), 0);\n            auto exp = random;\n\n            memset(exp.data() + 4096, 0, 4096);\n\n            if (memcmp(ret.data(), exp.data(), exp.size()))\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        h.reset();\n    }\n\n    // FIXME - DASD I/O\n}\n"
  },
  {
    "path": "src/tests/links.cpp",
    "content": "#include \"test.h\"\n\nusing namespace std;\n\nvector<pair<int64_t, u16string>> query_links(HANDLE h) {\n    IO_STATUS_BLOCK iosb;\n    NTSTATUS Status;\n    FILE_LINKS_INFORMATION fli;\n    vector<pair<int64_t, u16string>> ret;\n\n    fli.BytesNeeded = 0;\n\n    Status = NtQueryInformationFile(h, &iosb, &fli, sizeof(fli), FileHardLinkInformation);\n\n    if (Status != STATUS_SUCCESS && Status != STATUS_BUFFER_OVERFLOW)\n        throw ntstatus_error(Status);\n\n    if (fli.BytesNeeded == 0)\n        throw runtime_error(\"fli.BytesNeeded was 0\");\n\n    vector<uint8_t> buf(fli.BytesNeeded);\n\n    auto& fli2 = *reinterpret_cast<FILE_LINKS_INFORMATION*>(buf.data());\n\n    Status = NtQueryInformationFile(h, &iosb, &fli2, buf.size(), FileHardLinkInformation);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != buf.size())\n        throw formatted_error(\"iosb.Information was {}, expected {}\", iosb.Information, buf.size());\n\n    ret.resize(fli2.EntriesReturned);\n\n    auto flei = &fli2.Entry;\n    for (unsigned int i = 0; i < fli2.EntriesReturned; i++) {\n        auto& p = ret[i];\n\n        p.first = flei->ParentFileId;\n\n        p.second.resize(flei->FileNameLength);\n        memcpy(p.second.data(), flei->FileName, flei->FileNameLength * sizeof(char16_t));\n\n        if (flei->NextEntryOffset == 0)\n            break;\n\n        flei = (FILE_LINK_ENTRY_INFORMATION*)((uint8_t*)flei + flei->NextEntryOffset);\n    }\n\n    return ret;\n}\n\nvoid set_link_information(HANDLE h, bool replace_if_exists, HANDLE root_dir, u16string_view filename) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf(offsetof(FILE_LINK_INFORMATION, FileName) + (filename.length() * sizeof(char16_t)));\n    auto& fli = *(FILE_LINK_INFORMATION*)buf.data();\n\n    fli.ReplaceIfExists = replace_if_exists;\n    fli.RootDirectory = root_dir;\n    fli.FileNameLength = filename.length() * sizeof(char16_t);\n    memcpy(fli.FileName, filename.data(), fli.FileNameLength);\n\n    Status = NtSetInformationFile(h, &iosb, &fli, buf.size(), FileLinkInformation);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != 0)\n        throw formatted_error(\"iosb.Information was {}, expected 0\", iosb.Information);\n}\n\nstatic void set_link_information_ex(HANDLE h, ULONG flags, HANDLE root_dir, u16string_view filename) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf(offsetof(FILE_LINK_INFORMATION_EX, FileName) + (filename.length() * sizeof(char16_t)));\n    auto& fli = *(FILE_LINK_INFORMATION_EX*)buf.data();\n\n    fli.Flags = flags;\n    fli.RootDirectory = root_dir;\n    fli.FileNameLength = filename.length() * sizeof(char16_t);\n    memcpy(fli.FileName, filename.data(), fli.FileNameLength);\n\n    Status = NtSetInformationFile(h, &iosb, &fli, buf.size(), FileLinkInformationEx);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != 0)\n        throw formatted_error(\"iosb.Information was {}, expected 0\", iosb.Information);\n}\n\nvoid test_links(HANDLE token, const std::u16string& dir) {\n    unique_handle h, h2;\n\n    // traverse privilege needed to query hard links\n    test(\"Add SeChangeNotifyPrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link1a\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\link1a\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\link1a\\\".\");\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi.NumberOfLinks);\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Check links\", [&]() {\n            auto items = query_links(h.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& item = items.front();\n\n            if (item.second != u\"link1a\")\n                throw formatted_error(\"Link was called {}, expected link1a\", u16string_to_string(item.second));\n        });\n\n        test(\"Create link\", [&]() {\n            set_link_information(h.get(), false, nullptr, dir + u\"\\\\link1b\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\link1a\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\link1a\\\".\");\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.NumberOfLinks != 2)\n                throw formatted_error(\"NumberOfLinks was {}, expected 2\", fsi.NumberOfLinks);\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 2)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 2\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 2)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 2\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Check links\", [&]() {\n            auto items = query_links(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2.\", items.size());\n\n            auto& item1 = items[0];\n            auto& item2 = items[1];\n\n            if (item1.first != item2.first)\n                throw runtime_error(\"Links were in different directories\");\n\n            if (!(item1.second == u\"link1a\" && item2.second == u\"link1b\") && !(item1.second == u\"link1b\" && item2.second == u\"link1a\"))\n                throw runtime_error(\"Link names were not what was expected\");\n        });\n\n        test(\"Open second link\", [&]() {\n            h2 = create_file(dir + u\"\\\\link1b\", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        if (h2) {\n            int64_t file_id = 0;\n\n            test(\"Check index numbers are the same\", [&]() {\n                auto fii1 = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n                auto fii2 = query_information<FILE_INTERNAL_INFORMATION>(h2.get());\n\n                if (fii1.IndexNumber.QuadPart != fii2.IndexNumber.QuadPart)\n                    throw runtime_error(\"Index numbers did not match\");\n\n                file_id = fii1.IndexNumber.QuadPart;\n            });\n\n            test(\"Check name on second link\", [&]() {\n                auto fn = query_file_name_information(h2.get());\n\n                static const u16string_view ends_with = u\"\\\\link1b\";\n\n                if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                    throw runtime_error(\"Name did not end with \\\"\\\\link1b\\\".\");\n            });\n\n            test(\"Check directory entry of link 1\", [&]() {\n                u16string_view name = u\"link1a\";\n\n                auto items = query_dir<FILE_ID_FULL_DIR_INFORMATION>(dir, name);\n\n                if (items.size() != 1)\n                    throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n                auto& fdi = *static_cast<const FILE_ID_FULL_DIR_INFORMATION*>(items.front());\n\n                if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                    throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n                if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                    throw runtime_error(\"FileName did not match.\");\n\n                if (fdi.FileId.QuadPart != file_id)\n                    throw runtime_error(\"FileId did not match index number.\");\n            });\n\n            test(\"Check directory entry of link 2\", [&]() {\n                u16string_view name = u\"link1b\";\n\n                auto items = query_dir<FILE_ID_FULL_DIR_INFORMATION>(dir, name);\n\n                if (items.size() != 1)\n                    throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n                auto& fdi = *static_cast<const FILE_ID_FULL_DIR_INFORMATION*>(items.front());\n\n                if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                    throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n                if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                    throw runtime_error(\"FileName did not match.\");\n\n                if (fdi.FileId.QuadPart != file_id)\n                    throw runtime_error(\"FileId did not match index number.\");\n            });\n\n            test(\"Delete first link\", [&]() {\n                set_disposition_information(h.get(), true);\n            });\n\n            test(\"Check standard information of first link\", [&]() {\n                auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n                if (!fsi.DeletePending)\n                    throw runtime_error(\"DeletePending was false, expected true\");\n\n                if (fsi.NumberOfLinks != 1)\n                    throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi.NumberOfLinks);\n            });\n\n            test(\"Check standard link information of first link\", [&]() {\n                auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n                if (fsli.NumberOfAccessibleLinks != 1)\n                    throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n                if (fsli.TotalNumberOfLinks != 2)\n                    throw formatted_error(\"TotalNumberOfLinks was {}, expected 2\", fsli.TotalNumberOfLinks);\n\n                if (!fsli.DeletePending)\n                    throw runtime_error(\"DeletePending was false, expected true\");\n\n                if (fsli.Directory)\n                    throw runtime_error(\"Directory was true, expected false\");\n            });\n\n            test(\"Check standard information of second link\", [&]() {\n                auto fsi = query_information<FILE_STANDARD_INFORMATION>(h2.get());\n\n                if (fsi.DeletePending)\n                    throw runtime_error(\"DeletePending was true, expected false\");\n\n                if (fsi.NumberOfLinks != 1)\n                    throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi.NumberOfLinks);\n            });\n\n            test(\"Check standard link information of second link\", [&]() {\n                auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h2.get());\n\n                if (fsli.NumberOfAccessibleLinks != 1)\n                    throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n                if (fsli.TotalNumberOfLinks != 2)\n                    throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n                if (fsli.DeletePending)\n                    throw runtime_error(\"DeletePending was true, expected false\");\n\n                if (fsli.Directory)\n                    throw runtime_error(\"Directory was true, expected false\");\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n\n        test(\"Check directory entry of link 1 gone\", [&]() {\n            u16string_view name = u\"link1a\";\n\n            exp_status([&]() {\n                query_dir<FILE_ID_FULL_DIR_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n\n        test(\"Check directory entry of link 2\", [&]() {\n            u16string_view name = u\"link1b\";\n\n            auto items = query_dir<FILE_ID_FULL_DIR_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_ID_FULL_DIR_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\link2dir\", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create link\", [&]() {\n            exp_status([&]() {\n                set_link_information(h.get(), false, nullptr, dir + u\"\\\\link2dira\");\n            }, STATUS_FILE_IS_A_DIRECTORY);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link3a\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create second file\", [&]() {\n            create_file(dir + u\"\\\\link3b\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Try overwrite by link without ReplaceIfExists set\", [&]() {\n            exp_status([&]() {\n                set_link_information(h.get(), false, nullptr, dir + u\"\\\\link3b\");\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n    }\n\n    test(\"Create file with FILE_DELETE_ON_CLOSE\", [&]() {\n        h = create_file(dir + u\"\\\\link4a\", DELETE, 0, 0, FILE_CREATE, FILE_DELETE_ON_CLOSE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create link\", [&]() {\n            set_link_information(h.get(), false, nullptr, dir + u\"\\\\link4b\");\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsi.NumberOfLinks != 2)\n                throw formatted_error(\"NumberOfLinks was {}, expected 2\", fsi.NumberOfLinks);\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 2)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 2\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 2)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 2\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        h.reset();\n\n        test(\"Check directory entry of created link after close\", [&]() {\n            u16string_view name = u\"link4b\";\n\n            auto items = query_dir<FILE_ID_FULL_DIR_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_ID_FULL_DIR_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link5file\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create directory without FILE_SHARE_WRITE\", [&]() {\n        h2 = create_file(dir + u\"\\\\link5dir\", FILE_READ_DATA, 0, 0,\n                         FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Try create link through directory handle\", [&]() {\n            exp_status([&]() {\n                set_link_information(h.get(), false, h2.get(), u\"file\");\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        h.reset();\n        h2.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link6file\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create directory\", [&]() {\n        h2 = create_file(dir + u\"\\\\link6dir\", FILE_READ_DATA, 0, FILE_SHARE_WRITE,\n                         FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Create link\", [&]() {\n            set_link_information(h.get(), false, h2.get(), u\"file\");\n        });\n\n        h2.reset();\n\n        test(\"Check links\", [&]() {\n            auto items = query_links(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2.\", items.size());\n\n            auto& item1 = items[0];\n            auto& item2 = items[1];\n\n            if (item1.first == item2.first)\n                throw runtime_error(\"Links were in same directory\");\n\n            if (!(item1.second == u\"link6file\" && item2.second == u\"file\") && !(item1.second == u\"file\" && item2.second == u\"link6file\"))\n                throw runtime_error(\"Link names were not what was expected\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link6a\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create second file\", [&]() {\n            create_file(dir + u\"\\\\link6b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Overwrite second file by link\", [&]() {\n            set_link_information(h.get(), true, nullptr, dir + u\"\\\\link6b\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link7a\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create directory\", [&]() {\n            create_file(dir + u\"\\\\link7b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n        });\n\n        test(\"Try overwriting directory by link\", [&]() {\n            exp_status([&]() {\n                set_link_information(h.get(), true, nullptr, dir + u\"\\\\link7b\");\n            }, STATUS_ACCESS_DENIED);\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link8a\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create second file\", [&]() {\n            h2 = create_file(dir + u\"\\\\link8b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        if (h2) {\n            test(\"Try to overwrite open file by link\", [&]() {\n                exp_status([&]() {\n                    set_link_information(h.get(), true, nullptr, dir + u\"\\\\link8b\");\n                }, STATUS_ACCESS_DENIED);\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link9\", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try creating link with same name without ReplaceIfExists\", [&]() {\n            exp_status([&]() {\n                set_link_information(h.get(), false, nullptr, dir + u\"\\\\link9\");\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        test(\"Create link with same name with ReplaceIfExists\", [&]() {\n            set_link_information(h.get(), true, nullptr, dir + u\"\\\\link9\"); // nop\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi.NumberOfLinks);\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\link9\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\link9\\\".\");\n        });\n\n        test(\"Create link with same name but different case\", [&]() {\n            set_link_information(h.get(), true, nullptr, dir + u\"\\\\LINK9\");\n        });\n\n        test(\"Check standard information\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi.NumberOfLinks);\n        });\n\n        test(\"Check standard link information\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\LINK9\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\LINK9\\\".\");\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link10\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        struct {\n            u16string name;\n            string desc;\n        } invalid_names[] = {\n            { u\"/\", \"slash\" },\n            { u\":\", \"colon\" },\n            { u\"<\", \"less than\" },\n            { u\">\", \"greater than\" },\n            { u\"\\\"\", \"quote\" },\n            { u\"|\", \"pipe\" },\n            { u\"?\", \"question mark\" },\n            { u\"*\", \"asterisk\" }\n        };\n\n        for (const auto& n : invalid_names) {\n            test(\"Try creating link to invalid name (\" + n.desc + \")\", [&]() {\n                auto fn = dir + u\"\\\\link10\" + n.name;\n\n                exp_status([&]() {\n                    set_link_information(h.get(), false, nullptr, fn);\n                }, STATUS_OBJECT_NAME_INVALID);\n            });\n        }\n\n        bool is_ntfs = fstype == fs_type::ntfs;\n\n        test(\"Create link with more than 255 UTF-8 characters\", [&]() {\n            auto fn = dir + u\"\\\\link10\";\n\n            for (unsigned int i = 0; i < 64; i++) {\n                fn += u\"\\U0001f525\";\n            }\n\n            exp_status([&]() {\n                set_link_information(h.get(), false, nullptr, fn);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n        });\n\n        test(\"Create link with WTF-16 (1)\", [&]() {\n            auto fn = dir + u\"\\\\link10\";\n\n            fn += (char16_t)0xd83d;\n\n            exp_status([&]() {\n                set_link_information(h.get(), false, nullptr, fn);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n        });\n\n        test(\"Create link with WTF-16 (2)\", [&]() {\n            auto fn = dir + u\"\\\\link10\";\n\n            fn += (char16_t)0xdd25;\n\n            exp_status([&]() {\n                set_link_information(h.get(), false, nullptr, fn);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n        });\n\n        test(\"Create link with WTF-16 (3)\", [&]() {\n            auto fn = dir + u\"\\\\link10\";\n\n            fn += (char16_t)0xdd25;\n            fn += (char16_t)0xd83d;\n\n            exp_status([&]() {\n                set_link_information(h.get(), false, nullptr, fn);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link11\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try linking into non-existent directory\", [&]() {\n            exp_status([&]() {\n                set_link_information(h.get(), false, nullptr, dir + u\"\\\\linknonsuch\\\\file\");\n            }, STATUS_OBJECT_PATH_NOT_FOUND);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link12a\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create readonly file\", [&]() {\n            create_file(dir + u\"\\\\link12b\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Try overwriting readonly file by linking\", [&]() {\n            exp_status([&]() {\n                set_link_information(h.get(), true, nullptr, dir + u\"\\\\link12b\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link13\", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create directory 1\", [&]() {\n            h2 = create_file(dir + u\"\\\\link13dir1\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n        });\n\n        test(\"Set directory 1 ACL to SYNCHRONIZE | FILE_ADD_FILE\", [&]() {\n            set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_FILE);\n        });\n\n        h2.reset();\n\n        test(\"Create link\", [&]() {\n            set_link_information(h.get(), false, nullptr, dir + u\"\\\\link13dir1\\\\file\");\n        });\n\n        test(\"Create directory 2\", [&]() {\n            h2 = create_file(dir + u\"\\\\link13dir2\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n        });\n\n        test(\"Clear directory 2 ACL\", [&]() {\n            set_dacl(h2.get(), 0);\n        });\n\n        h2.reset();\n\n        test(\"Try to create link\", [&]() {\n            exp_status([&]() {\n                set_link_information(h.get(), false, nullptr, dir + u\"\\\\link13dir2\\\\file\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link14\", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle h3;\n\n        test(\"Create directory\", [&]() {\n            h2 = create_file(dir + u\"\\\\link14dir\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n        });\n\n        test(\"Create file in directory\", [&]() {\n            h3 = create_file(dir + u\"\\\\link14dir\\\\file\", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Clear directory ACL\", [&]() {\n            set_dacl(h2.get(), 0);\n        });\n\n        h2.reset();\n\n        test(\"Set file ACL to DELETE\", [&]() {\n            set_dacl(h3.get(), DELETE);\n        });\n\n        h3.reset();\n\n        test(\"Try to create link\", [&]() {\n            exp_status([&]() {\n                set_link_information(h.get(), true, nullptr, dir + u\"\\\\link14dir\\\\file\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link15\", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle h3;\n\n        test(\"Create directory\", [&]() {\n            h2 = create_file(dir + u\"\\\\link15dir\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n        });\n\n        test(\"Create file in directory\", [&]() {\n            h3 = create_file(dir + u\"\\\\link15dir\\\\file\", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Set directory ACL to SYNCHRONIZE | FILE_ADD_FILE\", [&]() {\n            set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_FILE);\n        });\n\n        h2.reset();\n\n        test(\"Clear file ACL\", [&]() {\n            set_dacl(h3.get(), 0);\n        });\n\n        h3.reset();\n\n        test(\"Try to create link\", [&]() {\n            exp_status([&]() {\n                set_link_information(h.get(), true, nullptr, dir + u\"\\\\link15dir\\\\file\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link16\", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle h3;\n\n        test(\"Create directory\", [&]() {\n            h2 = create_file(dir + u\"\\\\link16dir\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n        });\n\n        test(\"Create file in directory\", [&]() {\n            h3 = create_file(dir + u\"\\\\link16dir\\\\file\", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Set directory ACL to SYNCHRONIZE | FILE_ADD_FILE\", [&]() {\n            set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_FILE);\n        });\n\n        h2.reset();\n\n        test(\"Set file ACL to DELETE\", [&]() {\n            set_dacl(h3.get(), DELETE);\n        });\n\n        h3.reset();\n\n        test(\"Create link\", [&]() {\n            set_link_information(h.get(), true, nullptr, dir + u\"\\\\link16dir\\\\file\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\link17\", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle h3;\n\n        test(\"Create directory\", [&]() {\n            h2 = create_file(dir + u\"\\\\link17dir\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n        });\n\n        test(\"Create file in directory\", [&]() {\n            h3 = create_file(dir + u\"\\\\link17dir\\\\file\", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Set directory ACL to SYNCHRONIZE | FILE_ADD_FILE | FILE_DELETE_CHILD\", [&]() {\n            set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_FILE | FILE_DELETE_CHILD);\n        });\n\n        h2.reset();\n\n        test(\"Clear file ACL\", [&]() {\n            set_dacl(h3.get(), 0);\n        });\n\n        h3.reset();\n\n        test(\"Create link\", [&]() {\n            set_link_information(h.get(), true, nullptr, dir + u\"\\\\link17dir\\\\file\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file 1\", [&]() {\n        create_file(dir + u\"\\\\link18a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file 2\", [&]() {\n        h = create_file(dir + u\"\\\\link18b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to link file within other file\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\link18a\\\\file\");\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n}\n\nvoid test_links_ex(HANDLE token, const u16string& dir) {\n    unique_handle h, h2;\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\linkex1a\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create second file\", [&]() {\n            create_file(dir + u\"\\\\linkex1b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Try overwrite by link without FILE_LINK_REPLACE_IF_EXISTS\", [&]() {\n            exp_status([&]() {\n                set_link_information_ex(h.get(), 0, nullptr, dir + u\"\\\\linkex1b\");\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        test(\"Overwrite by link with FILE_LINK_REPLACE_IF_EXISTS\", [&]() {\n            set_link_information_ex(h.get(), FILE_LINK_REPLACE_IF_EXISTS, nullptr, dir + u\"\\\\linkex1b\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\linkex2a\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create readonly file\", [&]() {\n            create_file(dir + u\"\\\\linkex2b\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY,\n                        0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Try overwrite by link without FILE_LINK_IGNORE_READONLY_ATTRIBUTE\", [&]() {\n            exp_status([&]() {\n                set_link_information_ex(h.get(), FILE_LINK_REPLACE_IF_EXISTS, nullptr, dir + u\"\\\\linkex2b\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Overwrite by link with FILE_LINK_IGNORE_READONLY_ATTRIBUTE\", [&]() {\n            set_link_information_ex(h.get(), FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_IGNORE_READONLY_ATTRIBUTE, nullptr, dir + u\"\\\\linkex2b\");\n        });\n\n        h.reset();\n    }\n\n    // FIXME - FILE_LINK_POSIX_SEMANTICS\n\n    // traverse privilege needed to query hard links\n    test(\"Add SeChangeNotifyPrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file 1\", [&]() {\n        h = create_file(dir + u\"\\\\linkex3a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file 2\", [&]() {\n        h2 = create_file(dir + u\"\\\\linkex3b\", SYNCHRONIZE | DELETE | FILE_READ_DATA | FILE_WRITE_DATA, 0,\n                         FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Overwrite file 2 using FILE_LINK_POSIX_SEMANTICS\", [&]() {\n            set_link_information_ex(h.get(), FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_POSIX_SEMANTICS,\n                                    nullptr, dir + u\"\\\\linkex3b\");\n        });\n\n        test(\"Check name of file 1\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\linkex3a\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\linkex3a\\\".\");\n        });\n\n        test(\"Check name of file 2\", [&]() {\n            auto fn = query_file_name_information(h2.get());\n\n            static const u16string_view ends_with = u\"\\\\linkex3b\";\n\n            // NTFS moves this to \\$Extend\\$Deleted directory\n\n            if (fn.size() >= ends_with.size() && fn.substr(fn.size() - ends_with.size()) == ends_with)\n                throw runtime_error(\"Name ended with \\\"\\\\linkex3b\\\".\");\n        });\n\n        test(\"Check standard information of file 1\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.NumberOfLinks != 2)\n                throw formatted_error(\"NumberOfLinks was {}, expected 2\", fsi.NumberOfLinks);\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information of file 1\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 2)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 2\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 2)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 2\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Check standard information of file 2\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h2.get());\n\n            if (fsi.NumberOfLinks != 0)\n                throw formatted_error(\"NumberOfLinks was {}, expected 0\", fsi.NumberOfLinks);\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information of file 2\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h2.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Check new directory entry\", [&]() {\n            u16string_view name = u\"linkex3b\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check old directory entry\", [&]() {\n            u16string_view name = u\"linkex3a\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Try to clear delete bit on file 2\", [&]() {\n            exp_status([&]() {\n                set_disposition_information(h2.get(), false);\n            }, STATUS_FILE_DELETED);\n        });\n\n        test(\"Write to file 2\", [&]() {\n            static const vector<uint8_t> data = {'h','e','l','l','o'};\n\n            write_file(h2.get(), data);\n        });\n\n        test(\"Read from file 2\", [&]() {\n            static const vector<uint8_t> exp = {'h','e','l','l','o'};\n\n            auto buf = read_file(h2.get(), exp.size(), 0);\n\n            if (buf.size() != exp.size())\n                throw formatted_error(\"Read {} bytes, expected {}\", buf.size(), exp.size());\n\n            if (buf != exp)\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        int64_t dir_id;\n\n        test(\"Check file 1 hardlinks\", [&]() {\n            auto items = query_links(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2.\", items.size());\n\n            auto& item1 = items[0];\n            auto& item2 = items[1];\n\n            if (item1.first != item2.first)\n                throw runtime_error(\"Links were in different directories\");\n\n            if (!(item1.second == u\"linkex3a\" && item2.second == u\"linkex3b\") && !(item1.second == u\"linkex3b\" && item2.second == u\"linkex3a\"))\n                throw runtime_error(\"Link names were not what was expected\");\n\n            dir_id = item1.first;\n        });\n\n        test(\"Check file 2 hardlinks\", [&]() {\n            auto items = query_links(h2.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& item = items.front();\n\n            if (item.second == u\"linkex3b\")\n                throw formatted_error(\"Link was called linkex3b, expected something else\", u16string_to_string(item.second));\n\n            if (item.first == dir_id)\n                throw runtime_error(\"Dir ID of orphaned inode is same as before\");\n        });\n\n        h.reset();\n        h2.reset();\n    }\n\n    test(\"Disable token privileges\", [&]() {\n        disable_token_privileges(token);\n    });\n\n    test(\"Create file 1\", [&]() {\n        h = create_file(dir + u\"\\\\linkex4a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file 2 without FILE_SHARE_DELETE\", [&]() {\n        h2 = create_file(dir + u\"\\\\linkex4b\", MAXIMUM_ALLOWED, 0,\n                         0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Try to overwrite file 2 using FILE_LINK_POSIX_SEMANTICS\", [&]() {\n            exp_status([&]() {\n                set_link_information_ex(h.get(), FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_POSIX_SEMANTICS,\n                                        nullptr, dir + u\"\\\\linkex4b\");\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        h.reset();\n        h2.reset();\n    }\n\n    test(\"Create image file\", [&]() {\n        h = create_file(dir + u\"\\\\linkex5a\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        h2 = create_file(dir + u\"\\\\linkex5b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    auto img = pe_image(as_bytes(span(\"hello\")));\n\n    if (h && h2) {\n        unique_handle sect;\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), img);\n        });\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get());\n        });\n\n        h.reset();\n\n        if (sect) {\n            test(\"Try overwriting mapped image file by link\", [&]() {\n                exp_status([&]() {\n                    set_link_information_ex(h2.get(), FILE_LINK_REPLACE_IF_EXISTS, nullptr, dir + u\"\\\\linkex5a\");\n                }, STATUS_ACCESS_DENIED);\n            });\n        }\n\n        h2.reset();\n    }\n\n    test(\"Create image file\", [&]() {\n        h = create_file(dir + u\"\\\\linkex6a\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        h2 = create_file(dir + u\"\\\\linkex6b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        unique_handle sect;\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), img);\n        });\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get());\n        });\n\n        h.reset();\n\n        if (sect) {\n            test(\"Try overwriting mapped image file by POSIX link\", [&]() {\n                exp_status([&]() {\n                    set_link_information_ex(h2.get(), FILE_LINK_REPLACE_IF_EXISTS | FILE_LINK_POSIX_SEMANTICS,\n                                            nullptr, dir + u\"\\\\linkex6a\");\n                }, STATUS_ACCESS_DENIED);\n            });\n        }\n\n        h2.reset();\n    }\n\n    // FIXME - FILE_LINK_SUPPRESS_STORAGE_RESERVE_INHERITANCE\n    // FIXME - FILE_LINK_NO_INCREASE_AVAILABLE_SPACE\n    // FIXME - FILE_LINK_NO_DECREASE_AVAILABLE_SPACE\n    // FIXME - FILE_LINK_FORCE_RESIZE_TARGET_SR\n    // FIXME - FILE_LINK_FORCE_RESIZE_SOURCE_SR\n}\n"
  },
  {
    "path": "src/tests/manifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<assembly manifestVersion=\"1.0\" xmlns=\"urn:schemas-microsoft-com:asm.v1\">\n    <assemblyIdentity type=\"win32\" name=\"btrfs-test\" version=\"0.0.0.0\" />\n    <compatibility xmlns=\"urn:schemas-microsoft-com:compatibility.v1\">\n        <application>\n            <supportedOS Id=\"{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}\"/> <!-- Windows 10 -->\n            <supportedOS Id=\"{1f676c76-80e1-4239-95bb-83d0f6d0da78}\"/> <!-- Windows 8.1 -->\n            <supportedOS Id=\"{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}\"/> <!-- Windows 8 -->\n            <supportedOS Id=\"{35138b9a-5d96-4fbd-8e2d-a2440225f93a}\"/> <!-- Windows 7 -->\n            <supportedOS Id=\"{e2011457-1546-43c5-a5fe-008deee3d3f0}\"/> <!-- Windows Vista -->\n        </application>\n    </compatibility>\n    <application>\n        <windowsSettings>\n            <activeCodePage xmlns=\"http://schemas.microsoft.com/SMI/2019/WindowsSettings\">UTF-8</activeCodePage>\n        </windowsSettings>\n    </application>\n    <trustInfo>\n        <security>\n            <requestedPrivileges>\n                <requestedExecutionLevel level=\"requireAdministrator\" uiAccess=\"false\" />\n            </requestedPrivileges>\n        </security>\n    </trustInfo>\n</assembly>\n"
  },
  {
    "path": "src/tests/mmap.cpp",
    "content": "#include \"test.h\"\n\nusing namespace std;\n\nunique_handle create_section(ACCESS_MASK access, optional<uint64_t> max_size, ULONG prot,\n                             ULONG atts, HANDLE file) {\n    NTSTATUS Status;\n    HANDLE h;\n    LARGE_INTEGER li;\n\n    if (max_size)\n        li.QuadPart = max_size.value();\n\n    Status = NtCreateSection(&h, access, nullptr, max_size ? &li : nullptr, prot, atts, file);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    return unique_handle(h);\n}\n\nstatic void* map_view(HANDLE sect, uint64_t off, uint64_t len, ULONG prot) {\n    NTSTATUS Status;\n    void* addr = nullptr;\n    LARGE_INTEGER li;\n    SIZE_T size = len;\n\n    li.QuadPart = off;\n\n    Status = NtMapViewOfSection(sect, NtCurrentProcess(), &addr, 0, 0, &li, &size,\n                                ViewUnmap, 0, prot);\n\n    if (Status != STATUS_SUCCESS && Status != STATUS_IMAGE_NOT_AT_BASE)\n        throw ntstatus_error(Status);\n\n    if (!addr)\n        throw runtime_error(\"NtMapViewOfSection returned address of 0.\");\n\n    return addr;\n}\n\nstatic void unmap_view(void* addr) {\n    NTSTATUS Status;\n\n    Status = NtUnmapViewOfSection(NtCurrentProcess(), addr);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic void lock_file(HANDLE h, uint64_t offset, uint64_t length, bool exclusive) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    LARGE_INTEGER offli, lenli;\n\n    offli.QuadPart = offset;\n    lenli.QuadPart = length;\n\n    Status = NtLockFile(h, nullptr, nullptr, nullptr, &iosb, &offli, &lenli,\n                        0, false, exclusive);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic constexpr unsigned int align(unsigned int x, unsigned int a) {\n    return (x + a - 1) & ~(a - 1);\n}\n\nvector<uint8_t> pe_image(span<const std::byte> data) {\n    static const char stub[] = \"\\x0e\\x1f\\xba\\x0e\\x00\\xb4\\x09\\xcd\\x21\\xb8\\x01\\x4c\\xcd\\x21This program cannot be run in DOS mode.\\r\\r\\n\\x24\\x00\\x00\\x00\\x00\\x00\\x00\\x00\";\n    static const unsigned int SECTION_ALIGNMENT = 0x1000;\n    static const unsigned int FILE_ALIGNMENT = 0x200;\n    static constexpr unsigned int header_size = align(sizeof(IMAGE_DOS_HEADER) + sizeof(stub) - 1 + sizeof(IMAGE_NT_HEADERS32) + sizeof(IMAGE_SECTION_HEADER),\n                                                      FILE_ALIGNMENT);\n\n    vector<uint8_t> buf(header_size + align(data.size(), FILE_ALIGNMENT));\n    memset(buf.data(), 0, buf.size());\n\n    auto& h = *(IMAGE_DOS_HEADER*)buf.data();\n    h.e_magic = IMAGE_DOS_SIGNATURE;\n    h.e_cblp = 0x90;\n    h.e_cp = 0x3;\n    h.e_cparhdr = 0x4;\n    h.e_maxalloc = 0xffff;\n    h.e_sp = 0xb8;\n    h.e_lfarlc = 0x40;\n    h.e_lfanew = sizeof(h) + sizeof(stub) - 1;\n\n    memcpy(buf.data() + sizeof(h), stub, sizeof(stub) - 1);\n\n    auto& nth = *(IMAGE_NT_HEADERS32*)(buf.data() + h.e_lfanew);\n\n    nth.Signature = IMAGE_NT_SIGNATURE;\n    nth.FileHeader.Machine = IMAGE_FILE_MACHINE_I386;\n    nth.FileHeader.NumberOfSections = 1;\n    nth.FileHeader.SizeOfOptionalHeader = sizeof(IMAGE_OPTIONAL_HEADER32);\n    nth.FileHeader.Characteristics = IMAGE_FILE_EXECUTABLE_IMAGE;\n\n    nth.OptionalHeader.Magic = IMAGE_NT_OPTIONAL_HDR32_MAGIC;\n    nth.OptionalHeader.MajorLinkerVersion = 0x2;\n    nth.OptionalHeader.MinorLinkerVersion = 0x23;\n    nth.OptionalHeader.SizeOfCode = 0;\n    nth.OptionalHeader.SizeOfInitializedData = align(data.size(), SECTION_ALIGNMENT);\n    nth.OptionalHeader.SizeOfUninitializedData = 0;\n    nth.OptionalHeader.AddressOfEntryPoint = 0;\n    nth.OptionalHeader.BaseOfCode = 0x1000;\n    nth.OptionalHeader.BaseOfData = 0x1000;\n    nth.OptionalHeader.ImageBase = 0x10000000;\n    nth.OptionalHeader.SectionAlignment = SECTION_ALIGNMENT;\n    nth.OptionalHeader.FileAlignment = FILE_ALIGNMENT;\n    nth.OptionalHeader.MajorOperatingSystemVersion = 4;\n    nth.OptionalHeader.MinorOperatingSystemVersion = 0;\n    nth.OptionalHeader.MajorImageVersion = 0;\n    nth.OptionalHeader.MinorImageVersion = 0;\n    nth.OptionalHeader.MajorSubsystemVersion = 5;\n    nth.OptionalHeader.MinorSubsystemVersion = 2;\n    nth.OptionalHeader.Win32VersionValue = 0;\n    nth.OptionalHeader.SizeOfImage = align(header_size, SECTION_ALIGNMENT) + align(data.size(), SECTION_ALIGNMENT);\n    nth.OptionalHeader.SizeOfHeaders = header_size;\n    nth.OptionalHeader.Subsystem = IMAGE_SUBSYSTEM_WINDOWS_GUI;\n    nth.OptionalHeader.DllCharacteristics = 0;\n    nth.OptionalHeader.SizeOfStackReserve = 0x100000;\n    nth.OptionalHeader.SizeOfStackCommit = 0x1000;\n    nth.OptionalHeader.SizeOfHeapReserve = 0x100000;\n    nth.OptionalHeader.SizeOfHeapCommit = 0x1000;\n    nth.OptionalHeader.LoaderFlags = 0;\n    nth.OptionalHeader.NumberOfRvaAndSizes = IMAGE_NUMBEROF_DIRECTORY_ENTRIES;\n\n    auto& sect = *(IMAGE_SECTION_HEADER*)(&nth + 1);\n    memcpy(sect.Name, \".data\\0\\0\\0\", 8);\n    sect.Misc.VirtualSize = align(data.size(), SECTION_ALIGNMENT);\n    sect.VirtualAddress = align(header_size, SECTION_ALIGNMENT);\n    sect.SizeOfRawData = align(data.size(), FILE_ALIGNMENT);\n    sect.PointerToRawData = header_size;\n    sect.PointerToRelocations = 0;\n    sect.PointerToLinenumbers = 0;\n    sect.NumberOfRelocations = 0;\n    sect.NumberOfLinenumbers = 0;\n    sect.Characteristics = IMAGE_SCN_MEM_READ | IMAGE_SCN_CNT_INITIALIZED_DATA;\n\n    memcpy(buf.data() + header_size, data.data(), data.size());\n\n    return buf;\n}\n\nvoid test_mmap(const u16string& dir) {\n    unique_handle h, h2;\n\n    test(\"Create empty file\", [&]() {\n        h = create_file(dir + u\"\\\\mmapempty\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to create section on empty file\", [&]() {\n            exp_status([&]() {\n                auto sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READONLY, SEC_COMMIT, h.get());\n            }, STATUS_MAPPED_FILE_SIZE_ZERO);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\mmapdir\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to create section on directory\", [&]() {\n            exp_status([&]() {\n                auto sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READONLY, SEC_COMMIT, h.get());\n            }, STATUS_INVALID_FILE_FOR_SECTION);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap1\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        auto data = random_data(4096);\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), data);\n        });\n\n        unique_handle sect;\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READONLY, SEC_COMMIT, h.get());\n        });\n\n        void* addr = nullptr;\n\n        test(\"Map view\", [&]() {\n            addr = map_view(sect.get(), 0, data.size(), PAGE_READONLY);\n        });\n\n        if (addr) {\n            test(\"Check data in mapping\", [&]() {\n                if (memcmp(addr, data.data(), data.size()))\n                    throw runtime_error(\"Data in mapping did not match was written.\");\n            });\n\n            uint32_t num = 0xdeadbeef;\n\n            test(\"Write to file\", [&]() {\n                write_file(h.get(), span<uint8_t>((uint8_t*)&num, sizeof(uint32_t)), 0);\n            });\n\n            test(\"Check data in mapping again\", [&]() {\n                if (*(uint32_t*)addr != num)\n                    throw runtime_error(\"Data in mapping did not match was written.\");\n            });\n\n            test(\"Unmap view\", [&]() {\n                unmap_view(addr);\n            });\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap2\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        test(\"Try to create section larger than file\", [&]() {\n            exp_status([&]() {\n                create_section(SECTION_ALL_ACCESS, 8192, PAGE_READONLY, SEC_COMMIT, h.get());\n            }, STATUS_SECTION_TOO_BIG);\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap3\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        h.reset();\n\n        test(\"Reopen file without FILE_WRITE_DATA\", [&]() {\n            h = create_file(dir + u\"\\\\mmap3\", SYNCHRONIZE | FILE_READ_DATA, 0, 0,\n                            FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPENED);\n        });\n\n        test(\"Try to create RW section on RO file\", [&]() {\n            exp_status([&]() {\n                create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get());\n            }, STATUS_ACCESS_DENIED);\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap4\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        auto data = random_data(4096);\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), data);\n        });\n\n        unique_handle sect;\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_COMMIT, h.get());\n        });\n\n        void* addr = nullptr;\n\n        test(\"Map view\", [&]() {\n            addr = map_view(sect.get(), 0, data.size(), PAGE_READWRITE);\n        });\n\n        if (addr) {\n            test(\"Check data in mapping\", [&]() {\n                if (memcmp(addr, data.data(), data.size()))\n                    throw runtime_error(\"Data in mapping did not match was written.\");\n            });\n\n            *(uint32_t*)addr = 0xdeadbeef;\n\n            test(\"Read from file\", [&]() {\n                auto buf = read_file(h.get(), sizeof(uint32_t), 0);\n\n                if (buf.size() != sizeof(uint32_t))\n                    throw formatted_error(\"Read {} bytes, expected {}.\", buf.size(), sizeof(uint32_t));\n\n                auto& num = *(uint32_t*)buf.data();\n\n                if (num != 0xdeadbeef)\n                    throw runtime_error(\"Data read did not match was written to mapping.\");\n            });\n\n            test(\"Unmap view\", [&]() {\n                unmap_view(addr);\n            });\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap5\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file(h.get(), 0, 4096, true);\n        });\n\n        unique_handle sect;\n\n        test(\"Create section on locked file\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get());\n        });\n\n        void* addr = nullptr;\n\n        test(\"Map view\", [&]() {\n            addr = map_view(sect.get(), 0, 4096, PAGE_READWRITE);\n        });\n\n        if (addr) {\n            test(\"Unmap view\", [&]() {\n                unmap_view(addr);\n            });\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap6\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        unique_handle sect;\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get());\n        });\n\n        test(\"Extend file\", [&]() {\n            set_end_of_file(h.get(), 8192);\n        });\n\n        test(\"Try clearing file\", [&]() {\n            exp_status([&]() {\n                set_end_of_file(h.get(), 0);\n            }, STATUS_USER_MAPPED_FILE);\n        });\n\n        test(\"Try setting file to original size\", [&]() {\n            exp_status([&]() {\n                set_end_of_file(h.get(), 4096);\n            }, STATUS_USER_MAPPED_FILE);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap7\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        unique_handle sect;\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get());\n        });\n\n        test(\"Try deleting file\", [&]() {\n            exp_status([&]() {\n                set_disposition_information(h.get(), true);\n            }, STATUS_CANNOT_DELETE);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap8\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        unique_handle sect;\n        void* addr = nullptr;\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get());\n        });\n\n        if (sect) {\n            test(\"Map view\", [&]() {\n                addr = map_view(sect.get(), 0, 4096, PAGE_READWRITE);\n            });\n        }\n\n        h.reset();\n\n        test(\"Create file 2\", [&]() {\n            h = create_file(dir + u\"\\\\mmap8a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        if (h) {\n            test(\"Overwrite mapped file\", [&]() {\n                set_rename_information(h.get(), true, nullptr, dir + u\"\\\\mmap8\");\n            });\n        }\n\n        if (addr) {\n            test(\"Unmap view\", [&]() {\n                unmap_view(addr);\n            });\n        }\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap9\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        test(\"Mark file for deletion\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        unique_handle sect;\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get());\n        });\n\n        if (sect) {\n            void* addr = nullptr;\n\n            test(\"Map view\", [&]() {\n                addr = map_view(sect.get(), 0, 4096, PAGE_READWRITE);\n            });\n\n            if (addr) {\n                test(\"Unmap view\", [&]() {\n                    unmap_view(addr);\n                });\n            }\n        }\n\n        h.reset();\n    }\n\n    auto imgdata = as_bytes(span(\"hello\"));\n    auto img = pe_image(imgdata);\n\n    test(\"Create image file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap10\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle sect;\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), img);\n        });\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get());\n        });\n\n        if (sect) {\n            void* pe = nullptr;\n\n            test(\"Map view\", [&]() {\n                pe = map_view(sect.get(), 0, 0, PAGE_READWRITE);\n\n                if (!pe)\n                    throw runtime_error(\"Address returned was NULL.\");\n            });\n\n            if (pe) {\n                test(\"Check mapped data\", [&]() {\n                    if (memcmp((uint8_t*)pe + 0x1000, imgdata.data(), imgdata.size()))\n                        throw runtime_error(\"Data mapped did not match data written.\");\n                });\n\n                test(\"Unmap view\", [&]() {\n                    unmap_view(pe);\n                });\n            }\n        }\n\n        h.reset();\n    }\n\n    test(\"Create image file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap11\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle sect;\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), img);\n        });\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get());\n        });\n\n        if (sect) {\n            void* pe = nullptr;\n\n            test(\"Map view\", [&]() {\n                pe = map_view(sect.get(), 0, 0, PAGE_READWRITE);\n\n                if (!pe)\n                    throw runtime_error(\"Address returned was NULL.\");\n            });\n\n            if (pe) {\n                test(\"Try to truncate file\", [&]() {\n                    exp_status([&]() {\n                        set_end_of_file(h.get(), 0);\n                    }, STATUS_USER_MAPPED_FILE);\n                });\n\n                test(\"Extend file\", [&]() {\n                    set_end_of_file(h.get(), 8192);\n                });\n\n                test(\"Try to truncate file again\", [&]() {\n                    exp_status([&]() {\n                        set_end_of_file(h.get(), 4096);\n                    }, STATUS_USER_MAPPED_FILE);\n                });\n\n                test(\"Unmap view\", [&]() {\n                    unmap_view(pe);\n                });\n            }\n        }\n\n        h.reset();\n    }\n\n    test(\"Create image file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap12\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA,\n                        0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle sect;\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), img);\n        });\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get());\n        });\n\n        h.reset();\n\n        if (sect) {\n            test(\"Try overwriting mapped image file\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\mmap12\", MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OVERWRITE, 0, FILE_OVERWRITTEN);\n                }, STATUS_SHARING_VIOLATION);\n            });\n        }\n    }\n\n    test(\"Create image file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap13a\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        h2 = create_file(dir + u\"\\\\mmap13b\", MAXIMUM_ALLOWED,\n                         0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle sect;\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), img);\n        });\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get());\n        });\n\n        h.reset();\n\n        if (sect) {\n            test(\"Try overwriting mapped image file by rename\", [&]() {\n                exp_status([&]() {\n                    set_rename_information(h2.get(), true, nullptr, dir + u\"\\\\mmap13a\");\n                }, STATUS_ACCESS_DENIED);\n            });\n        }\n\n        h2.reset();\n    }\n\n    test(\"Create image file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap14\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle sect;\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), img);\n        });\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get());\n        });\n\n        if (sect) {\n            test(\"Try deleting mapped image file\", [&]() {\n                exp_status([&]() {\n                    set_disposition_information(h.get(), true);\n                }, STATUS_CANNOT_DELETE);\n            });\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap15\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        unique_handle sect;\n        void* addr = nullptr;\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, 4096, PAGE_READWRITE, SEC_COMMIT, h.get());\n        });\n\n        if (sect) {\n            test(\"Map view\", [&]() {\n                addr = map_view(sect.get(), 0, 4096, PAGE_READWRITE);\n            });\n        }\n\n        h.reset();\n\n        test(\"Create file 2\", [&]() {\n            h = create_file(dir + u\"\\\\mmap15a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        if (h) {\n            test(\"Overwrite mapped file by linking\", [&]() {\n                set_link_information(h.get(), true, nullptr, dir + u\"\\\\mmap15\");\n            });\n        }\n\n        if (addr) {\n            test(\"Unmap view\", [&]() {\n                unmap_view(addr);\n            });\n        }\n    }\n\n    test(\"Create image file\", [&]() {\n        h = create_file(dir + u\"\\\\mmap16a\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        h2 = create_file(dir + u\"\\\\mmap16b\", MAXIMUM_ALLOWED,\n                         0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle sect;\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), img);\n        });\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get());\n        });\n\n        h.reset();\n\n        if (sect) {\n            test(\"Try overwriting mapped image file by linking\", [&]() {\n                exp_status([&]() {\n                    set_link_information(h2.get(), true, nullptr, dir + u\"\\\\mmap16a\");\n                }, STATUS_ACCESS_DENIED);\n            });\n        }\n\n        h2.reset();\n    }\n}\n"
  },
  {
    "path": "src/tests/oplock.cpp",
    "content": "#include \"test.h\"\n#include <thread>\n\nusing namespace std;\n\n#define FSCTL_REQUEST_OPLOCK_LEVEL_1 CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 0, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define FSCTL_REQUEST_OPLOCK_LEVEL_2 CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 1, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define FSCTL_REQUEST_BATCH_OPLOCK CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 2, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define FSCTL_OPLOCK_BREAK_ACKNOWLEDGE CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 3, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define FSCTL_REQUEST_FILTER_OPLOCK CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 23, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define FSCTL_REQUEST_OPLOCK CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 144, METHOD_BUFFERED, FILE_ANY_ACCESS)\n\n#define FILE_OPLOCK_BROKEN_TO_LEVEL_2       0x00000007\n#define FILE_OPLOCK_BROKEN_TO_NONE          0x00000008\n\n#define OPLOCK_LEVEL_CACHE_READ         0x00000001\n#define OPLOCK_LEVEL_CACHE_HANDLE       0x00000002\n#define OPLOCK_LEVEL_CACHE_WRITE        0x00000004\n\n#define REQUEST_OPLOCK_INPUT_FLAG_REQUEST               0x00000001\n#define REQUEST_OPLOCK_INPUT_FLAG_ACK                   0x00000002\n#define REQUEST_OPLOCK_INPUT_FLAG_COMPLETE_ACK_ON_CLOSE 0x00000004\n\n#define REQUEST_OPLOCK_CURRENT_VERSION          1\n\nenum oplock_type {\n    level1,\n    level2,\n    batch,\n    filter,\n    read_oplock,\n    read_handle,\n    read_write,\n    read_write_handle\n};\n\nstatic unique_handle req_oplock(HANDLE h, IO_STATUS_BLOCK& iosb, enum oplock_type type) {\n    NTSTATUS Status;\n    HANDLE ev;\n\n    iosb.Information = 0;\n\n    Status = NtCreateEvent(&ev, MAXIMUM_ALLOWED, nullptr, NotificationEvent, false);\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    switch (type) {\n        case oplock_type::level1:\n            Status = NtFsControlFile(h, ev, nullptr, nullptr, &iosb,\n                                     FSCTL_REQUEST_OPLOCK_LEVEL_1,\n                                     nullptr, 0, nullptr, 0);\n        break;\n\n        case oplock_type::level2:\n            Status = NtFsControlFile(h, ev, nullptr, nullptr, &iosb,\n                                     FSCTL_REQUEST_OPLOCK_LEVEL_2,\n                                     nullptr, 0, nullptr, 0);\n        break;\n\n        case oplock_type::batch:\n            Status = NtFsControlFile(h, ev, nullptr, nullptr, &iosb,\n                                     FSCTL_REQUEST_BATCH_OPLOCK,\n                                     nullptr, 0, nullptr, 0);\n        break;\n\n        case oplock_type::filter:\n            Status = NtFsControlFile(h, ev, nullptr, nullptr, &iosb,\n                                     FSCTL_REQUEST_FILTER_OPLOCK,\n                                     nullptr, 0, nullptr, 0);\n        break;\n\n        default:\n            throw runtime_error(\"Invalid oplock type for function.\");\n    }\n\n    if (Status != STATUS_PENDING)\n        throw ntstatus_error(Status);\n\n    return unique_handle(ev);\n}\n\nstatic unique_handle req_oplock_win7(HANDLE h, IO_STATUS_BLOCK& iosb, enum oplock_type type,\n                                     REQUEST_OPLOCK_OUTPUT_BUFFER& roob) {\n    REQUEST_OPLOCK_INPUT_BUFFER roib;\n    NTSTATUS Status;\n    HANDLE ev;\n\n    iosb.Information = 0;\n\n    Status = NtCreateEvent(&ev, MAXIMUM_ALLOWED, nullptr, NotificationEvent, false);\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    roib.StructureVersion = REQUEST_OPLOCK_CURRENT_VERSION;\n    roib.StructureLength = sizeof(roib);\n    roib.Flags = REQUEST_OPLOCK_INPUT_FLAG_REQUEST;\n\n    switch (type) {\n        case oplock_type::read_oplock:\n            roib.RequestedOplockLevel = OPLOCK_LEVEL_CACHE_READ;\n        break;\n\n        case oplock_type::read_handle:\n            roib.RequestedOplockLevel = OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE;\n        break;\n\n        case oplock_type::read_write:\n            roib.RequestedOplockLevel = OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE;\n        break;\n\n        case oplock_type::read_write_handle:\n            roib.RequestedOplockLevel = OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE;\n        break;\n\n        default:\n            throw runtime_error(\"Invalid oplock type for function.\");\n    }\n\n    roob.StructureVersion = REQUEST_OPLOCK_CURRENT_VERSION;\n    roob.StructureLength = sizeof(roob);\n\n    Status = NtFsControlFile(h, ev, nullptr, nullptr, &iosb,\n                             FSCTL_REQUEST_OPLOCK, &roib, sizeof(roib),\n                             &roob, sizeof(roob));\n\n    if (Status != STATUS_PENDING)\n        throw ntstatus_error(Status);\n\n    return unique_handle(ev);\n}\n\nstatic bool check_event(HANDLE h) {\n    NTSTATUS Status;\n    EVENT_BASIC_INFORMATION ebi;\n\n    Status = NtQueryEvent(h, EventBasicInformation, &ebi, sizeof(ebi), nullptr);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    return ebi.EventState;\n}\n\nstatic void lock_file_wait(HANDLE h, uint64_t offset, uint64_t length, bool exclusive) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    LARGE_INTEGER offli, lenli;\n\n    offli.QuadPart = offset;\n    lenli.QuadPart = length;\n\n    auto event = create_event();\n\n    Status = NtLockFile(h, event.get(), nullptr, nullptr, &iosb, &offli, &lenli,\n                        0, false, exclusive);\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(event.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic void unlock_file(HANDLE h, uint64_t offset, uint64_t length) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    LARGE_INTEGER offli, lenli;\n\n    offli.QuadPart = offset;\n    lenli.QuadPart = length;\n\n    Status = NtUnlockFile(h, &iosb, &offli, &lenli, 0);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nvoid test_oplocks_ii(HANDLE token, const u16string& dir) {\n    unique_handle h, h2;\n    IO_STATUS_BLOCK iosb;\n    auto data = random_data(4096);\n\n    // needed to set valid data length\n    test(\"Add SeManageVolumePrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii1\", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii1\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get level 2 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level2);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockii1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Check oplock still not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            h2.reset();\n\n            test(\"Try to open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplockii1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED);\n                }, STATUS_CANNOT_BREAK_OPLOCK);\n            });\n\n            test(\"Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)\", [&]() {\n                create_file(dir + u\"\\\\oplockii1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_SUPERSEDE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockii1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_SUPERSEDE, 0, FILE_SUPERSEDED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n\n            test(\"Get level 2 oplock\", [&]() {\n                ev = req_oplock(h.get(), iosb, oplock_type::level2);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OVERWRITE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockii1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n\n            test(\"Try to open second handle with FILE_RESERVE_OPFILTER\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplockii1\", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED);\n                }, STATUS_OPLOCK_NOT_GRANTED);\n            });\n\n            test(\"Get level 2 oplock\", [&]() {\n                ev = req_oplock(h.get(), iosb, oplock_type::level2);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OVERWRITE_IF)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockii1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n\n            test(\"Write to file\", [&]() {\n                write_file_wait(h.get(), data, 0);\n            });\n\n            test(\"Get level 2 oplock\", [&]() {\n                ev = req_oplock(h.get(), iosb, oplock_type::level2);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockii1\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Read from second handle\", [&]() {\n                read_file_wait(h2.get(), 4096, 0);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Write to first handle\", [&]() {\n                write_file_wait(h.get(), data, 0);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            test(\"Get level 2 oplock\", [&]() {\n                ev = req_oplock(h.get(), iosb, oplock_type::level2);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Write to second handle\", [&]() {\n                write_file_wait(h2.get(), data, 0);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockii1\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Get level 2 oplock\", [&]() {\n                ev = req_oplock(h.get(), iosb, oplock_type::level2);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Lock file\", [&]() {\n                lock_file_wait(h2.get(), 0, 4096, false);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockii1\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0,\n                                 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPENED);\n            });\n\n            test(\"Get level 2 oplock\", [&]() {\n                ev = req_oplock(h.get(), iosb, oplock_type::level2);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Set end of file\", [&]() {\n                set_end_of_file(h2.get(), 4096);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            test(\"Get level 2 oplock\", [&]() {\n                ev = req_oplock(h.get(), iosb, oplock_type::level2);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Set allocation\", [&]() {\n                set_allocation(h2.get(), 4096);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            test(\"Get level 2 oplock\", [&]() {\n                ev = req_oplock(h.get(), iosb, oplock_type::level2);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Set valid data length\", [&]() {\n                set_valid_data_length(h2.get(), 4096);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            test(\"Get level 2 oplock\", [&]() {\n                ev = req_oplock(h.get(), iosb, oplock_type::level2);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Rename file\", [&]() {\n                set_rename_information(h2.get(), false, nullptr, dir + u\"\\\\oplockii1a\");\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Create hardlink\", [&]() {\n                set_link_information(h2.get(), false, nullptr, dir + u\"\\\\oplockii1b\");\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Set zero data\", [&]() {\n                set_zero_data(h2.get(), 0, 100);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            test(\"Get level 2 oplock\", [&]() {\n                ev = req_oplock(h.get(), iosb, oplock_type::level2);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Set disposition information\", [&]() {\n                set_disposition_information(h2.get(), true);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii2\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Try to get level 2 oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb, oplock_type::level2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file with FILE_SYNCHRONOUS_IO_NONALERT\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii3\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get level 2 oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb, oplock_type::level2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii4\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get level 2 oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb, oplock_type::level2);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get level 2 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level2);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Open second handle on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\oplockii5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        if (h2) {\n            unique_handle ev2;\n            IO_STATUS_BLOCK iosb2;\n\n            test(\"Get level 2 oplock on second handle\", [&]() {\n                ev2 = req_oplock(h2.get(), iosb2, oplock_type::level2);\n            });\n\n            test(\"Check first oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Check second oplock not broken\", [&]() {\n                if (check_event(ev2.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii6\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n\n        test(\"Get read oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Open second handle on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\oplockii6\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        if (h2) {\n            unique_handle ev2;\n            IO_STATUS_BLOCK iosb2;\n\n            test(\"Get level 2 oplock on second handle\", [&]() {\n                ev2 = req_oplock(h2.get(), iosb2, oplock_type::level2);\n            });\n\n            test(\"Check first oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Check second oplock not broken\", [&]() {\n                if (check_event(ev2.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii7\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Open second handle on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\oplockii7\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        if (h2) {\n            test(\"Try to get level 2 oplock on second handle\", [&]() {\n                IO_STATUS_BLOCK iosb2;\n\n                exp_status([&]() {\n                    req_oplock(h2.get(), iosb2, oplock_type::level2);\n                }, STATUS_OPLOCK_NOT_GRANTED);\n            });\n\n            test(\"Check first oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii8\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get level 2 oplock\", [&]() {\n            IO_STATUS_BLOCK iosb2;\n\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::level2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii9\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get level 2 oplock\", [&]() {\n            IO_STATUS_BLOCK iosb2;\n\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::level2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii10\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get level 2 oplock\", [&]() {\n            IO_STATUS_BLOCK iosb2;\n\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::level2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii11\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get level 2 oplock\", [&]() {\n            IO_STATUS_BLOCK iosb2;\n\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::level2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockii12\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get level 2 oplock\", [&]() {\n            IO_STATUS_BLOCK iosb2;\n\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::level2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n}\n\nvoid test_oplocks_r(HANDLE token, const u16string& dir) {\n    unique_handle h, h2;\n    IO_STATUS_BLOCK iosb;\n    REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n    auto data = random_data(4096);\n\n    // needed to set valid data length\n    test(\"Add SeManageVolumePrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr1\", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr1\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get read oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)\", [&]() {\n                create_file(dir + u\"\\\\oplockr1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockr1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Check oplock still not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            h2.reset();\n\n            test(\"Try to open second handle with FILE_RESERVE_OPFILTER\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplockr1\", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED);\n                }, STATUS_OPLOCK_NOT_GRANTED);\n            });\n\n            test(\"Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)\", [&]() {\n                create_file(dir + u\"\\\\oplockr1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED);\n            });\n\n            test(\"Check oplock is broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            test(\"Get read oplock\", [&]() {\n                ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_SUPERSEDE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockr1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_SUPERSEDE, 0, FILE_SUPERSEDED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n\n            test(\"Get read oplock\", [&]() {\n                ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OVERWRITE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockr1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n\n            test(\"Get read oplock\", [&]() {\n                ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OVERWRITE_IF)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockr1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n\n            test(\"Write to file\", [&]() {\n                write_file_wait(h.get(), data, 0);\n            });\n\n            test(\"Get read oplock\", [&]() {\n                ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockr1\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Read from second handle\", [&]() {\n                read_file_wait(h2.get(), 4096, 0);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Write to first handle\", [&]() {\n                write_file_wait(h.get(), data, 0);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Write to second handle\", [&]() {\n                write_file_wait(h2.get(), data, 0);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockr1\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Get read oplock\", [&]() {\n                ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Lock file\", [&]() {\n                lock_file_wait(h2.get(), 0, 4096, false);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockr1\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0,\n                                 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, FILE_SYNCHRONOUS_IO_NONALERT, FILE_OPENED);\n            });\n\n            test(\"Get read oplock\", [&]() {\n                ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Set end of file\", [&]() {\n                set_end_of_file(h2.get(), 4096);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            test(\"Get read oplock\", [&]() {\n                ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Set allocation\", [&]() {\n                set_allocation(h2.get(), 4096);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            test(\"Get read oplock\", [&]() {\n                ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Set valid data length\", [&]() {\n                set_valid_data_length(h2.get(), 4096);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            test(\"Get read oplock\", [&]() {\n                ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Rename file\", [&]() {\n                set_rename_information(h2.get(), false, nullptr, dir + u\"\\\\oplockr1a\");\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Create hardlink\", [&]() {\n                set_link_information(h2.get(), false, nullptr, dir + u\"\\\\oplockr1b\");\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Set zero data\", [&]() {\n                set_zero_data(h2.get(), 0, 100);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            test(\"Get read oplock\", [&]() {\n                ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Set disposition information\", [&]() {\n                set_disposition_information(h2.get(), true);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr2\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Try to get read oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file with FILE_SYNCHRONOUS_IO_NONALERT\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr3\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get read oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr4\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        // Succeeds on Windows 8 and above (see https://docs.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_request_oplock)\n        test(\"Get read oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Create file in directory\", [&]() {\n                create_file(dir + u\"\\\\oplockr4\\\\file\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get level 2 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level2);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Open second handle on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\oplockr5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        if (h2) {\n            unique_handle ev2;\n            IO_STATUS_BLOCK iosb2;\n\n            test(\"Get read oplock on second handle\", [&]() {\n                ev2 = req_oplock_win7(h2.get(), iosb2, oplock_type::read_oplock, roob);\n            });\n\n            test(\"Check first oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Check second oplock not broken\", [&]() {\n                if (check_event(ev2.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr6\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get read oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Open second handle on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\oplockr6\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        if (h2) {\n            unique_handle ev2;\n            IO_STATUS_BLOCK iosb2;\n            REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n\n            test(\"Get read oplock on second handle\", [&]() {\n                ev2 = req_oplock_win7(h2.get(), iosb2, oplock_type::read_oplock, roob2);\n            });\n\n            test(\"Check first oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Check second oplock not broken\", [&]() {\n                if (check_event(ev2.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr7\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Open second handle on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\oplockr7\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        if (h2) {\n            unique_handle ev2;\n\n            test(\"Get read oplock on second handle\", [&]() {\n                IO_STATUS_BLOCK iosb2;\n                REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n\n                ev2 = req_oplock_win7(h2.get(), iosb2, oplock_type::read_oplock, roob2);\n            });\n\n            test(\"Check first oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Check second oplock not broken\", [&]() {\n                if (check_event(ev2.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr8\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read oplock\", [&]() {\n            IO_STATUS_BLOCK iosb2;\n\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_oplock, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr9\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read oplock\", [&]() {\n            IO_STATUS_BLOCK iosb2;\n\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_oplock, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr10\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read oplock\", [&]() {\n            IO_STATUS_BLOCK iosb2;\n\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_oplock, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr11\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read oplock\", [&]() {\n            IO_STATUS_BLOCK iosb2;\n            REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_oplock, roob2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockr12\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read oplock\", [&]() {\n            IO_STATUS_BLOCK iosb2;\n            REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_oplock, roob2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n}\n\nstatic DWORD __stdcall wait_and_acknowledge(void* param) {\n    IO_STATUS_BLOCK iosb;\n    auto ctx = reinterpret_cast<HANDLE*>(param);\n    auto ev = ctx[0];\n    auto h = ctx[1];\n\n    NtWaitForSingleObject(ev, false, nullptr);\n\n    NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb,\n                    FSCTL_OPLOCK_BREAK_ACKNOWLEDGE,\n                    nullptr, 0, nullptr, 0);\n\n    delete[] ctx;\n\n    return 0;\n}\n\nstatic void ack_oplock(HANDLE event, HANDLE h) {\n    HANDLE thread;\n    auto ctx = new HANDLE[2];\n\n    ctx[0] = event;\n    ctx[1] = h;\n\n    thread = CreateThread(nullptr, 0, wait_and_acknowledge, ctx, 0, nullptr);\n\n    if (!thread) {\n        delete[] ctx;\n        throw runtime_error(\"Could not create thread.\");\n    }\n\n    NtClose(thread);\n}\n\nstatic DWORD __stdcall wait_and_acknowledge_win7(void* param) {\n    IO_STATUS_BLOCK iosb;\n    REQUEST_OPLOCK_INPUT_BUFFER roib;\n    REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n    auto ctx = reinterpret_cast<HANDLE*>(param);\n    auto ev = ctx[0];\n    auto h = ctx[1];\n\n    NtWaitForSingleObject(ev, false, nullptr);\n\n    roib.StructureVersion = REQUEST_OPLOCK_CURRENT_VERSION;\n    roib.StructureLength = sizeof(roib);\n    roib.RequestedOplockLevel = 0;\n    roib.Flags = REQUEST_OPLOCK_INPUT_FLAG_ACK;\n\n    roob.StructureVersion = REQUEST_OPLOCK_CURRENT_VERSION;\n    roob.StructureLength = sizeof(roob);\n\n    NtFsControlFile(h, nullptr, nullptr, nullptr, &iosb,\n                    FSCTL_REQUEST_OPLOCK, &roib, sizeof(roib),\n                    &roob, sizeof(roob));\n\n    delete[] ctx;\n\n    return 0;\n}\n\nstatic void ack_oplock_win7(HANDLE event, HANDLE h) {\n    HANDLE thread;\n    auto ctx = new HANDLE[2];\n\n    ctx[0] = event;\n    ctx[1] = h;\n\n    thread = CreateThread(nullptr, 0, wait_and_acknowledge_win7, ctx, 0, nullptr);\n\n    if (!thread) {\n        delete[] ctx;\n        throw runtime_error(\"Could not create thread.\");\n    }\n\n    NtClose(thread);\n}\n\nvoid test_oplocks_i(HANDLE token, const u16string& dir) {\n    unique_handle h, h2;\n    IO_STATUS_BLOCK iosb;\n    auto data = random_data(4096);\n\n    // needed to set valid data length\n    test(\"Add SeManageVolumePrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki1\", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki1\", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Try to open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplocki1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED);\n                }, STATUS_CANNOT_BREAK_OPLOCK);\n            });\n\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplocki1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED);\n                }, STATUS_OPLOCK_BREAK_IN_PROGRESS);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_LEVEL_2)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_LEVEL_2\", iosb.Information);\n            });\n        }\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki1\", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        if (ev) {\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplocki1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_LEVEL_2)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_LEVEL_2\", iosb.Information);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Try to open second handle with FILE_RESERVE_OPFILTER\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\oplocki1\", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_SUPERSEDE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplocki1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_SUPERSEDE, 0, FILE_SUPERSEDED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OVERWRITE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplocki1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OVERWRITE_IF)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplocki1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Read from file\", [&]() {\n            read_file_wait(h.get(), 4096, 0);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Unlock file\", [&]() {\n            unlock_file(h.get(), 0, 4096);\n        });\n\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set allocation\", [&]() {\n            set_allocation(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set valid data length\", [&]() {\n            set_valid_data_length(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Rename file\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\oplocki1a\");\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Create hardlink\", [&]() {\n            set_link_information(h.get(), false, nullptr, dir + u\"\\\\oplocki1b\");\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set zero data\", [&]() {\n            set_zero_data(h.get(), 0, 100);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set disposition information\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki2\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file with FILE_SYNCHRONOUS_IO_NONALERT\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki3\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get level 1 oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb, oplock_type::level1);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki4\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get level 1 oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb, oplock_type::level1);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Open second handle on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\oplocki5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        if (h2) {\n            test(\"Try to get level 1 oplock with two handles open\", [&]() {\n                exp_status([&]() {\n                    req_oplock(h2.get(), iosb, oplock_type::level1);\n                }, STATUS_OPLOCK_NOT_GRANTED);\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki6\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev, ev2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get level 2 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level2);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev2 = req_oplock(h.get(), iosb2, oplock_type::level1);\n        });\n\n        test(\"Check first oplock broken\", [&]() {\n            if (!check_event(ev.get()))\n                throw runtime_error(\"Oplock is not broken\");\n\n            if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n        });\n\n        test(\"Check second oplock not broken\", [&]() {\n            if (check_event(ev2.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki7\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get level 1 oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::level1);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki8\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get level 1 oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::level1);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki9\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get level 1 oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::level1);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki10\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get level 1 oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::level1);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki11\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get level 1 oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::level1);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki12\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get level 1 oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::level1);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplocki13\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get level 1 oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::level1);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n}\n\nvoid test_oplocks_rw(HANDLE token, const u16string& dir) {\n    unique_handle h, h2;\n    IO_STATUS_BLOCK iosb;\n    REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n    auto data = random_data(4096);\n\n    // needed to set valid data length\n    test(\"Add SeManageVolumePrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw1\", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw1\", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Try to open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplockrw1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED);\n                }, STATUS_CANNOT_BREAK_OPLOCK);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockrw1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != OPLOCK_LEVEL_CACHE_READ)\n                    throw formatted_error(\"NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        if (ev) {\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Try to open second handle with FILE_RESERVE_OPFILTER\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplockrw1\", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED);\n                }, STATUS_OPLOCK_NOT_GRANTED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n        }\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        if (ev) {\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplockrw1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED);\n                }, STATUS_OPLOCK_BREAK_IN_PROGRESS);\n            });\n\n            test(\"Check oplock is broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != OPLOCK_LEVEL_CACHE_READ)\n                    throw formatted_error(\"NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ\", roob.NewOplockLevel);\n            });\n        }\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_SUPERSEDE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockrw1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_SUPERSEDE, 0, FILE_SUPERSEDED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OVERWRITE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockrw1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OVERWRITE_IF)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockrw1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Read from file\", [&]() {\n            read_file_wait(h.get(), 4096, 0);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Unlock file\", [&]() {\n            unlock_file(h.get(), 0, 4096);\n        });\n\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set allocation\", [&]() {\n            set_allocation(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set valid data length\", [&]() {\n            set_valid_data_length(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Rename file\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\oplockrw1a\");\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Create hardlink\", [&]() {\n            set_link_information(h.get(), false, nullptr, dir + u\"\\\\oplockrw1b\");\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set zero data\", [&]() {\n            set_zero_data(h.get(), 0, 100);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set disposition information\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw2\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file with FILE_SYNCHRONOUS_IO_NONALERT\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw3\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get read-write oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw4\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get read-write oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Open second handle on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\oplockrw5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        if (h2) {\n            test(\"Try to get read-write oplock with two handles open\", [&]() {\n                exp_status([&]() {\n                    req_oplock_win7(h2.get(), iosb, oplock_type::read_write, roob);\n                }, STATUS_OPLOCK_NOT_GRANTED);\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw6\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get level 2 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level2);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-write oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw7\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev, ev2;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Get read-write oplock\", [&]() {\n            ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob2);\n        });\n\n        test(\"Check first oplock broken\", [&]() {\n            if (!check_event(ev.get()))\n                throw runtime_error(\"Oplock is not broken\");\n\n            if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE))\n                throw formatted_error(\"NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE\", roob.NewOplockLevel);\n        });\n\n        test(\"Check second oplock not broken\", [&]() {\n            if (check_event(ev2.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw8\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-write oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw9\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev, ev2;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Get read-write oplock\", [&]() {\n            ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob2);\n        });\n\n        test(\"Check first oplock broken\", [&]() {\n            if (!check_event(ev.get()))\n                throw runtime_error(\"Oplock is not broken\");\n\n            if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE))\n                throw formatted_error(\"NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE\", roob.NewOplockLevel);\n        });\n\n        test(\"Check second oplock not broken\", [&]() {\n            if (check_event(ev2.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw10\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-write oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw11\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-write oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw12\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-write oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrw13\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-write oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_write, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n}\n\nvoid test_oplocks_batch(HANDLE token, const u16string& dir) {\n    unique_handle h, h2;\n    IO_STATUS_BLOCK iosb;\n    auto data = random_data(4096);\n\n    // needed to set valid data length\n    test(\"Add SeManageVolumePrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb1\", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb1\", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Try to open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplockb1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED);\n                }, STATUS_CANNOT_BREAK_OPLOCK);\n            });\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockb1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_LEVEL_2)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_LEVEL_2\", iosb.Information);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Try to open second handle with FILE_RESERVE_OPFILTER\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\oplockb1\", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplockb1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED);\n                }, STATUS_OPLOCK_BREAK_IN_PROGRESS);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_LEVEL_2)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_LEVEL_2\", iosb.Information);\n            });\n        }\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb1\", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        if (ev) {\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_SUPERSEDE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockb1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_SUPERSEDE, 0, FILE_SUPERSEDED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OVERWRITE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockb1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OVERWRITE_IF)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockb1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Read from file\", [&]() {\n            read_file_wait(h.get(), 4096, 0);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Unlock file\", [&]() {\n            unlock_file(h.get(), 0, 4096);\n        });\n\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set allocation\", [&]() {\n            set_allocation(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set valid data length\", [&]() {\n            set_valid_data_length(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Rename file\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\oplockb1a\");\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Create hardlink\", [&]() {\n            set_link_information(h.get(), false, nullptr, dir + u\"\\\\oplockb1b\");\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set zero data\", [&]() {\n            set_zero_data(h.get(), 0, 100);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set disposition information\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb2\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file with FILE_SYNCHRONOUS_IO_NONALERT\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb3\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get batch oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb, oplock_type::batch);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb4\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get batch oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb, oplock_type::batch);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Open second handle on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\oplockb5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        if (h2) {\n            test(\"Try to get batch oplock with two handles open\", [&]() {\n                exp_status([&]() {\n                    req_oplock(h2.get(), iosb, oplock_type::batch);\n                }, STATUS_OPLOCK_NOT_GRANTED);\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb6\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev, ev2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get level 2 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level2);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Get batch oplock\", [&]() {\n            ev2 = req_oplock(h.get(), iosb2, oplock_type::batch);\n        });\n\n        test(\"Check first oplock broken\", [&]() {\n            if (!check_event(ev.get()))\n                throw runtime_error(\"Oplock is not broken\");\n\n            if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n        });\n\n        test(\"Check second oplock not broken\", [&]() {\n            if (check_event(ev2.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb7\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get batch oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::batch);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb8\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get batch oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::batch);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb9\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get batch oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::batch);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb10\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get batch oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::batch);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb11\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get batch oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::batch);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb12\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get batch oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::batch);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockb13\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get batch oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::batch);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n}\n\nvoid test_oplocks_rwh(HANDLE token, const u16string& dir) {\n    unique_handle h, h2;\n    IO_STATUS_BLOCK iosb;\n    REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n    auto data = random_data(4096);\n\n    // needed to set valid data length\n    test(\"Add SeManageVolumePrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh1\", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh1\", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplockrwh1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED);\n                }, STATUS_CANNOT_BREAK_OPLOCK);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockrwh1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE))\n                    throw formatted_error(\"NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Try to open second handle with FILE_RESERVE_OPFILTER\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\oplockrwh1\", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        if (ev) {\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplockrwh1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED);\n                }, STATUS_OPLOCK_BREAK_IN_PROGRESS);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE))\n                    throw formatted_error(\"NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE\", roob.NewOplockLevel);\n            });\n        }\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_SUPERSEDE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockrwh1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_SUPERSEDE, 0, FILE_SUPERSEDED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OVERWRITE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockrwh1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OVERWRITE_IF)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockrwh1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Read from file\", [&]() {\n            read_file_wait(h.get(), 4096, 0);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Unlock file\", [&]() {\n            unlock_file(h.get(), 0, 4096);\n        });\n\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set allocation\", [&]() {\n            set_allocation(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set valid data length\", [&]() {\n            set_valid_data_length(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Rename file\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\oplockrwh1a\");\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Create hardlink\", [&]() {\n            set_link_information(h.get(), false, nullptr, dir + u\"\\\\oplockrwh1b\");\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set zero data\", [&]() {\n            set_zero_data(h.get(), 0, 100);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set disposition information\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh2\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file with FILE_SYNCHRONOUS_IO_NONALERT\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh3\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get read-write-handle oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh4\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get read-write-handle oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Open second handle on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\oplockrwh5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        if (h2) {\n            test(\"Try to get read-write-handle oplock with two handles open\", [&]() {\n                exp_status([&]() {\n                    req_oplock_win7(h2.get(), iosb, oplock_type::read_write_handle, roob);\n                }, STATUS_OPLOCK_NOT_GRANTED);\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh6\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get level 2 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level2);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-write-handle oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh7\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev, ev2;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob2);\n        });\n\n        test(\"Check first oplock broken\", [&]() {\n            if (!check_event(ev.get()))\n                throw runtime_error(\"Oplock is not broken\");\n\n            if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE))\n                throw formatted_error(\"NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE\", roob.NewOplockLevel);\n        });\n\n        test(\"Check second oplock not broken\", [&]() {\n            if (check_event(ev2.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh8\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev, ev2;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob2);\n        });\n\n        test(\"Check first oplock broken\", [&]() {\n            if (!check_event(ev.get()))\n                throw runtime_error(\"Oplock is not broken\");\n\n            if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE))\n                throw formatted_error(\"NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE\", roob.NewOplockLevel);\n        });\n\n        test(\"Check second oplock not broken\", [&]() {\n            if (check_event(ev2.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh9\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev, ev2;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob2);\n        });\n\n        test(\"Check first oplock broken\", [&]() {\n            if (!check_event(ev.get()))\n                throw runtime_error(\"Oplock is not broken\");\n\n            if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE))\n                throw formatted_error(\"NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE\", roob.NewOplockLevel);\n        });\n\n        test(\"Check second oplock not broken\", [&]() {\n            if (check_event(ev2.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh10\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev, ev2;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob2);\n        });\n\n        test(\"Check first oplock broken\", [&]() {\n            if (!check_event(ev.get()))\n                throw runtime_error(\"Oplock is not broken\");\n\n            if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE))\n                throw formatted_error(\"NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE\", roob.NewOplockLevel);\n        });\n\n        test(\"Check second oplock not broken\", [&]() {\n            if (check_event(ev2.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh11\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-write-handle oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh12\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-write-handle oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrwh13\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get batch oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n}\n\nvoid test_oplocks_filter(HANDLE token, const u16string& dir) {\n    unique_handle h, h2;\n    IO_STATUS_BLOCK iosb;\n    auto data = random_data(4096);\n\n    // needed to set valid data length\n    test(\"Add SeManageVolumePrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf1\", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf1\", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Try to open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)\", [&]() {\n                exp_status([&]() {\n                    create_file(dir + u\"\\\\oplockf1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED);\n                }, STATUS_CANNOT_BREAK_OPLOCK);\n            });\n\n            test(\"Open second handle (FILE_OPEN, FILE_READ_DATA)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockf1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OPEN, FILE_WRITE_DATA)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockf1\", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Try to open second handle with FILE_RESERVE_OPFILTER\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\oplockf1\", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)\", [&]() {\n                create_file(dir + u\"\\\\oplockf1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED);\n            });\n\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_SUPERSEDE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockf1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_SUPERSEDE, 0, FILE_SUPERSEDED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OVERWRITE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockf1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OVERWRITE_IF)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockf1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                    throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Read from file\", [&]() {\n            read_file_wait(h.get(), 4096, 0);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Unlock file\", [&]() {\n            unlock_file(h.get(), 0, 4096);\n        });\n\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set allocation\", [&]() {\n            set_allocation(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set valid data length\", [&]() {\n            set_valid_data_length(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Rename file\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\oplockf1a\");\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Create hardlink\", [&]() {\n            set_link_information(h.get(), false, nullptr, dir + u\"\\\\oplockf1b\");\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set zero data\", [&]() {\n            set_zero_data(h.get(), 0, 100);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set disposition information\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf2\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file with FILE_SYNCHRONOUS_IO_NONALERT\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf3\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get filter oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb, oplock_type::filter);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf4\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get filter oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb, oplock_type::filter);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Open second handle on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\oplockf5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        if (h2) {\n            test(\"Try to get filter oplock with two handles open\", [&]() {\n                exp_status([&]() {\n                    req_oplock(h2.get(), iosb, oplock_type::batch);\n                }, STATUS_OPLOCK_NOT_GRANTED);\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf6\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev, ev2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get level 2 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level2);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Get filter oplock\", [&]() {\n            ev2 = req_oplock(h.get(), iosb2, oplock_type::filter);\n        });\n\n        test(\"Check first oplock broken\", [&]() {\n            if (!check_event(ev.get()))\n                throw runtime_error(\"Oplock is not broken\");\n\n            if (iosb.Information != FILE_OPLOCK_BROKEN_TO_NONE)\n                throw formatted_error(\"iosb.Information was {}, expected FILE_OPLOCK_BROKEN_TO_NONE\", iosb.Information);\n        });\n\n        test(\"Check second oplock not broken\", [&]() {\n            if (check_event(ev2.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf7\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get filter oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::filter);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf8\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get filter oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::filter);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf9\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get filter oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::filter);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf10\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get filter oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::filter);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf11\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get filter oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::filter);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf12\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get filter oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::filter);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockf13\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get filter oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock(h.get(), iosb2, oplock_type::filter);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n}\n\nvoid test_oplocks_rh(HANDLE token, const u16string& dir) {\n    unique_handle h, h2;\n    IO_STATUS_BLOCK iosb;\n    REQUEST_OPLOCK_OUTPUT_BUFFER roob;\n    auto data = random_data(4096);\n\n    // needed to set valid data length\n    test(\"Add SeManageVolumePrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_MANAGE_VOLUME_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh1\", FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh1\", FILE_READ_DATA | FILE_WRITE_DATA | DELETE, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK)\", [&]() {\n                create_file(dir + u\"\\\\oplockrh1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_OPEN, FILE_OPEN_REQUIRING_OPLOCK, FILE_OPENED);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Open second handle (FILE_OPEN)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockrh1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OPEN, 0, FILE_OPENED);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n            });\n\n            h2.reset();\n        }\n\n        test(\"Try to open second handle with FILE_RESERVE_OPFILTER\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\oplockrh1\", FILE_READ_ATTRIBUTES, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_OPEN, FILE_RESERVE_OPFILTER, FILE_OPENED);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        if (ev) {\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Try to open second handle (FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED)\", [&]() {\n                create_file(dir + u\"\\\\oplockrh1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_OPEN, FILE_COMPLETE_IF_OPLOCKED, FILE_OPENED);\n            });\n\n            test(\"Check oplock is not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n        }\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_SUPERSEDE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockrh1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_SUPERSEDE, 0, FILE_SUPERSEDED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OVERWRITE)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockrh1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            ack_oplock_win7(ev.get(), h.get());\n\n            test(\"Open second handle (FILE_OVERWRITE_IF)\", [&]() {\n                h2 = create_file(dir + u\"\\\\oplockrh1\", FILE_READ_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                                 FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Read from file\", [&]() {\n            read_file_wait(h.get(), 4096, 0);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Unlock file\", [&]() {\n            unlock_file(h.get(), 0, 4096);\n        });\n\n        test(\"Set end of file\", [&]() {\n            set_end_of_file(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set allocation\", [&]() {\n            set_allocation(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set valid data length\", [&]() {\n            set_valid_data_length(h.get(), 4096);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Rename file\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\oplockrh1a\");\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Create hardlink\", [&]() {\n            set_link_information(h.get(), false, nullptr, dir + u\"\\\\oplockrh1b\");\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set zero data\", [&]() {\n            set_zero_data(h.get(), 0, 100);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Set disposition information\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh2\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), data, 0);\n        });\n\n        test(\"Lock file\", [&]() {\n            lock_file_wait(h.get(), 0, 4096, false);\n        });\n\n        test(\"Try to get read-handle oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file with FILE_SYNCHRONOUS_IO_NONALERT\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh3\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to get read-handle oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh4\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n\n        // Succeeds on Windows 8 and above (see https://docs.microsoft.com/en-us/windows/win32/api/winioctl/ni-winioctl-fsctl_request_oplock)\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        if (ev) {\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            test(\"Create file in directory\", [&]() {\n                create_file(dir + u\"\\\\oplockrh4\\\\file\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                            FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n            });\n\n            test(\"Check oplock broken\", [&]() {\n                if (!check_event(ev.get()))\n                    throw runtime_error(\"Oplock is not broken\");\n\n                if (roob.NewOplockLevel != 0)\n                    throw formatted_error(\"NewOplockLevel was {}, expected 0\", roob.NewOplockLevel);\n            });\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Open second handle on file\", [&]() {\n            h2 = create_file(dir + u\"\\\\oplockrh5\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                             FILE_OPEN, 0, FILE_OPENED);\n        });\n\n        if (h2) {\n            unique_handle ev;\n\n            test(\"Get read-handle oplock with two handles open\", [&]() {\n                ev = req_oplock_win7(h2.get(), iosb, oplock_type::read_handle, roob);\n            });\n\n            test(\"Check oplock not broken\", [&]() {\n                if (check_event(ev.get()))\n                    throw runtime_error(\"Oplock is broken\");\n            });\n\n            h2.reset();\n        }\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh6\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get level 2 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level2);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-handle oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh7\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev, ev2;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_oplock, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_write_handle, roob2);\n        });\n\n        test(\"Check first oplock broken\", [&]() {\n            if (!check_event(ev.get()))\n                throw runtime_error(\"Oplock is not broken\");\n\n            if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE))\n                throw formatted_error(\"NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_WRITE | OPLOCK_LEVEL_CACHE_HANDLE\", roob.NewOplockLevel);\n        });\n\n        test(\"Check second oplock not broken\", [&]() {\n            if (check_event(ev2.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh8\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev, ev2;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Get read-handle oplock\", [&]() {\n            ev2 = req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob2);\n        });\n\n        test(\"Check first oplock broken\", [&]() {\n            if (!check_event(ev.get()))\n                throw runtime_error(\"Oplock is not broken\");\n\n            if (roob.NewOplockLevel != (OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE))\n                throw formatted_error(\"NewOplockLevel was {}, expected OPLOCK_LEVEL_CACHE_READ | OPLOCK_LEVEL_CACHE_HANDLE\", roob.NewOplockLevel);\n        });\n\n        test(\"Check second oplock not broken\", [&]() {\n            if (check_event(ev2.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh9\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-write oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-handle oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh10\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        REQUEST_OPLOCK_OUTPUT_BUFFER roob2;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get read-write-handle oplock\", [&]() {\n            ev = req_oplock_win7(h.get(), iosb, oplock_type::read_write_handle, roob);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-handle oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob2);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh11\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get level 1 oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::level1);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-handle oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh12\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get filter oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::filter);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get read-handle oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\oplockrh13\", FILE_READ_DATA | FILE_WRITE_DATA, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        unique_handle ev;\n        IO_STATUS_BLOCK iosb2;\n\n        test(\"Get batch oplock\", [&]() {\n            ev = req_oplock(h.get(), iosb, oplock_type::batch);\n        });\n\n        test(\"Check oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        test(\"Try to get batch oplock\", [&]() {\n            exp_status([&]() {\n                req_oplock_win7(h.get(), iosb2, oplock_type::read_handle, roob);\n            }, STATUS_OPLOCK_NOT_GRANTED);\n        });\n\n        test(\"Check first oplock not broken\", [&]() {\n            if (check_event(ev.get()))\n                throw runtime_error(\"Oplock is broken\");\n        });\n\n        h.reset();\n    }\n\n    // FIXME - FSCTL_OPLOCK_BREAK_ACK_NO_2\n    // FIXME - FSCTL_OPBATCH_ACK_CLOSE_PENDING\n    // FIXME - FSCTL_OPLOCK_BREAK_NOTIFY\n\n    disable_token_privileges(token);\n}\n"
  },
  {
    "path": "src/tests/overwrite.cpp",
    "content": "#include \"test.h\"\n\nusing namespace std;\n\nvoid test_overwrite(const u16string& dir) {\n    unique_handle h;\n\n    test(\"Try overwriting non-existent file\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\nonsuch\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE,\n                        0, FILE_OVERWRITTEN);\n        }, STATUS_OBJECT_NAME_NOT_FOUND);\n    });\n\n    test(\"Create readonly file\", [&]() {\n        h = create_file(dir + u\"\\\\overwritero\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        h.reset();\n\n        test(\"Try overwriting readonly file\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\overwritero\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE,\n                            0, FILE_OVERWRITTEN);\n            }, STATUS_ACCESS_DENIED);\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\overwrite\", MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try overwriting open file\", [&]() {\n            create_file(dir + u\"\\\\overwrite\", MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_OVERWRITE, 0, FILE_OVERWRITTEN);\n        });\n\n        h.reset();\n\n        test(\"Overwrite file\", [&]() {\n            h = create_file(dir + u\"\\\\overwrite\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE,\n                            0, FILE_OVERWRITTEN);\n        });\n    }\n\n    if (h) {\n        h.reset();\n\n        test(\"Overwrite file adding readonly flag\", [&]() {\n            create_file(dir + u\"\\\\overwrite\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_OVERWRITE,\n                        0, FILE_OVERWRITTEN);\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\overwrite2\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        h.reset();\n\n        test(\"Try overwriting file, changing to directory\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\overwrite2\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE,\n                            FILE_DIRECTORY_FILE, FILE_OVERWRITTEN);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Overwrite file adding hidden flag\", [&]() {\n            h = create_file(dir + u\"\\\\overwrite2\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN, 0, FILE_OVERWRITE,\n                            0, FILE_OVERWRITTEN);\n        });\n    }\n\n    if (h) {\n        h.reset();\n\n        test(\"Try overwriting file clearing hidden flag\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\overwrite2\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE,\n                            0, FILE_OVERWRITTEN);\n            }, STATUS_ACCESS_DENIED);\n        });\n    }\n\n    test(\"Overwrite file adding system flag\", [&]() {\n        h = create_file(dir + u\"\\\\overwrite2\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, 0,\n                        FILE_OVERWRITE, 0, FILE_OVERWRITTEN);\n    });\n\n    if (h) {\n        h.reset();\n\n        test(\"Try overwriting file clearing system flag\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\overwrite2\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN, 0, FILE_OVERWRITE,\n                            0, FILE_OVERWRITTEN);\n            }, STATUS_ACCESS_DENIED);\n        });\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\overwritedir\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        h.reset();\n\n        test(\"Try overwriting directory\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\overwritedir\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE,\n                            FILE_DIRECTORY_FILE, FILE_OVERWRITTEN);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Try overwriting directory, changing to file\", [&]() {\n            exp_status([&]() {\n                create_file(dir + u\"\\\\overwritedir\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE,\n                            FILE_NON_DIRECTORY_FILE, FILE_OVERWRITTEN);\n            }, STATUS_FILE_IS_A_DIRECTORY);\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\overwrite3\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        h.reset();\n\n        test(\"Overwrite file with different case\", [&]() {\n            h = create_file(dir + u\"\\\\OVERWRITE3\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE,\n                            0, FILE_OVERWRITTEN);\n        });\n\n        if (h) {\n            test(\"Check name\", [&]() {\n                auto fn = query_file_name_information(h.get());\n\n                static const u16string_view ends_with = u\"\\\\overwrite3\";\n\n                if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                    throw runtime_error(\"Name did not end with \\\"\\\\overwrite3\\\".\");\n            });\n        }\n    }\n\n    test(\"Create file with FILE_OPEN_IF\", [&]() {\n        h = create_file(dir + u\"\\\\overwriteif\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE_IF, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        h.reset();\n\n        test(\"Open file with FILE_OVERWRITE_IF\", [&]() {\n            create_file(dir + u\"\\\\overwriteif\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE_IF, 0, FILE_OVERWRITTEN);\n        });\n    }\n}\n"
  },
  {
    "path": "src/tests/rename.cpp",
    "content": "#include \"test.h\"\n\nusing namespace std;\n\nvoid set_rename_information(HANDLE h, bool replace_if_exists, HANDLE root_dir, u16string_view filename) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf(offsetof(FILE_RENAME_INFORMATION, FileName) + (filename.length() * sizeof(char16_t)));\n    auto& fri = *(FILE_RENAME_INFORMATION*)buf.data();\n\n    fri.ReplaceIfExists = replace_if_exists;\n    fri.RootDirectory = root_dir;\n    fri.FileNameLength = filename.length() * sizeof(char16_t);\n    memcpy(fri.FileName, filename.data(), fri.FileNameLength);\n\n    Status = NtSetInformationFile(h, &iosb, &fri, buf.size(), FileRenameInformation);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != 0)\n        throw formatted_error(\"iosb.Information was {}, expected 0\", iosb.Information);\n}\n\nstatic void set_rename_information_ex(HANDLE h, ULONG flags, HANDLE root_dir, u16string_view filename) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf(offsetof(FILE_RENAME_INFORMATION_EX, FileName) + (filename.length() * sizeof(char16_t)));\n    auto& fri = *(FILE_RENAME_INFORMATION_EX*)buf.data();\n\n    fri.Flags = flags;\n    fri.RootDirectory = root_dir;\n    fri.FileNameLength = filename.length() * sizeof(char16_t);\n    memcpy(fri.FileName, filename.data(), fri.FileNameLength);\n\n    Status = NtSetInformationFile(h, &iosb, &fri, buf.size(), FileRenameInformationEx);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != 0)\n        throw formatted_error(\"iosb.Information was {}, expected 0\", iosb.Information);\n}\n\nvoid test_rename(const u16string& dir) {\n    unique_handle h, h2;\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile1\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamefile1\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamefile1\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"renamefile1\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Rename file\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamefile1b\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamefile1b\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamefile1b\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"renamefile1b\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check old directory entry not there\", [&]() {\n            u16string_view name = u\"renamefile1\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir1\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamedir1\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamefile1\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"renamedir1\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Rename file\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir1b\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamedir1b\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamedir1b\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"renamedir1b\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check old directory entry not there\", [&]() {\n            u16string_view name = u\"renamedir1\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile2\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamefile2\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamefile2\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"renamefile2\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Rename file to same name\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamefile2\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamefile2\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamefile2\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"renamefile2\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile3\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamefile3\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamefile3\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"renamefile3\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Rename file to different case\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\RENAMEFILE3\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\RENAMEFILE3\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\RENAMEFILE3\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"RENAMEFILE3\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file 1\", [&]() {\n        create_file(dir + u\"\\\\renamefile4a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file 2\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile4b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try renaming file 2 to file 1 without ReplaceIfExists set\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamefile4a\");\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        test(\"Rename file 2 to file 1\", [&]() {\n            set_rename_information(h.get(), true, nullptr, dir + u\"\\\\renamefile4a\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamefile4a\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamefile4a\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"renamefile4a\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file 1\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamefile5a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file 2\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile5b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try renaming file 2 to file 1 without ReplaceIfExists set\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamefile5a\");\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        test(\"Try renaming file 2 to file 1 with file 1 open\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), true, nullptr, dir + u\"\\\\renamefile5a\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    h2.reset();\n\n    test(\"Create file 1\", [&]() {\n        create_file(dir + u\"\\\\renamefile6a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file 2\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile6b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try renaming file 2 to file 1 uppercase without ReplaceIfExists set\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\RENAMEFILE6A\");\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        test(\"Rename file 2 to file 1 uppercase\", [&]() {\n            set_rename_information(h.get(), true, nullptr, dir + u\"\\\\RENAMEFILE6A\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\RENAMEFILE6A\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\RENAMEFILE6A\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"RENAMEFILE6A\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        create_file(dir + u\"\\\\renamedir7\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile7\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamefile7\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamefile7\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"renamefile7\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Move file to subdir\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir7\\\\renamefile7a\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamedir7\\\\renamefile7a\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamedir7\\\\renamefile7a\\\".\");\n        });\n\n        test(\"Check old directory entry gone\", [&]() {\n            u16string_view name = u\"renamefile7\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n\n        test(\"Check new directory entry\", [&]() {\n            u16string_view name = u\"renamefile7a\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir + u\"\\\\renamedir7\", name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Try overwriting directory with file without ReplaceIfExists set\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir7\");\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        test(\"Try overwriting directory with file with ReplaceIfExists set\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), true, nullptr, dir + u\"\\\\renamedir7\");\n            }, STATUS_ACCESS_DENIED);\n        });\n    }\n\n    test(\"Create directory 1\", [&]() {\n        create_file(dir + u\"\\\\renamedir8\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create directory 2\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir8a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        create_file(dir + u\"\\\\renamefile8\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"renamedir8a\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamedir8a\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamedir8a\\\".\");\n        });\n\n        test(\"Move directory to subdir\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir8\\\\renamedir8b\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamedir8\\\\renamedir8b\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamedir8\\\\renamedir8b\\\".\");\n        });\n\n        test(\"Check old directory entry gone\", [&]() {\n            u16string_view name = u\"renamedir8a\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n\n        test(\"Check new directory entry\", [&]() {\n            u16string_view name = u\"renamedir8b\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir + u\"\\\\renamedir8\", name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Try overwriting file with directory without ReplaceIfExists set\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamefile8\");\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        test(\"Try overwriting file with directory with ReplaceIfExists set\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), true, nullptr, dir + u\"\\\\renamefile8\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile9\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create directory\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamedir9\", FILE_LIST_DIRECTORY | FILE_ADD_FILE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"renamefile9\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamefile9\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamefile9\\\".\");\n        });\n\n        test(\"Move file via RootDirectory handle\", [&]() {\n            set_rename_information(h.get(), false, h2.get(), u\"renamefile9\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamedir9\\\\renamefile9\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamedir9\\\\renamefile9\\\".\");\n        });\n\n        test(\"Check old directory entry gone\", [&]() {\n            u16string_view name = u\"renamefile9\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n\n        test(\"Try checking new directory entry with handle still open\", [&]() {\n            u16string_view name = u\"renamefile9\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir + u\"\\\\renamedir9\", name);\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        h2.reset();\n\n        test(\"Check new directory entry\", [&]() {\n            u16string_view name = u\"renamefile9\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir + u\"\\\\renamedir9\", name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamedir10\", FILE_LIST_DIRECTORY | FILE_ADD_FILE, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir10\\\\renamefile10\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try checking directory entry with handle open\", [&]() {\n            u16string_view name = u\"renamefile10\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir + u\"\\\\renamedir10\", name);\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamedir10\\\\renamefile10\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamedir10\\\\renamefile10\\\".\");\n        });\n\n        test(\"Rename file via RootDirectory handle\", [&]() {\n            set_rename_information(h.get(), false, h2.get(), u\"renamefile10a\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamedir10\\\\renamefile10a\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamedir10\\\\renamefile10a\\\".\");\n        });\n\n        h2.reset();\n\n        test(\"Check old directory entry gone\", [&]() {\n            u16string_view name = u\"renamefile10\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir + u\"\\\\renamedir10\", name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n\n        test(\"Check new directory entry\", [&]() {\n            u16string_view name = u\"renamefile10a\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir + u\"\\\\renamedir10\", name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n    }\n\n    test(\"Create directory\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamedir11\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir11\\\\renamefile11\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Set directory permissions\", [&]() {\n            set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_FILE);\n        });\n\n        h2.reset();\n\n        test(\"Rename file\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir11\\\\renamefile11a\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamedir12\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir12\\\\renamefile12\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Clear directory permissions\", [&]() {\n            set_dacl(h2.get(), 0);\n        });\n\n        h2.reset();\n\n        test(\"Try to rename file\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir12\\\\renamefile12a\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamedir13\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create subdir\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir13\\\\renamesubdir13\", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Set directory permissions\", [&]() {\n            set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_SUBDIRECTORY);\n        });\n\n        h2.reset();\n\n        test(\"Rename subdir\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir13\\\\renamesubdir13a\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamedir14\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create subdir\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir14\\\\renamesubdir14\", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Clear directory permissions\", [&]() {\n            set_dacl(h2.get(), 0);\n        });\n\n        h2.reset();\n\n        test(\"Try to rename subdir\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir14\\\\renamefile14a\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile15\", FILE_READ_DATA, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try renaming file without DELETE access\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamefile15a\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir16\", FILE_LIST_DIRECTORY, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try renaming directory without DELETE access\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir16a\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory 1\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamedir17a\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir17a\\\\file\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Clear directory 1 permissions\", [&]() {\n            set_dacl(h2.get(), 0);\n        });\n\n        h2.reset();\n\n        test(\"Create directory 2\", [&]() {\n            h2 = create_file(dir + u\"\\\\renamedir17b\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n        });\n\n        if (h2) {\n            test(\"Set directory 2 permissions\", [&]() {\n                set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_FILE);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Move file to directory 2\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir17b\\\\file\");\n        });\n\n        test(\"Try to move back to directory 1\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir17a\\\\file\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory 1\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamedir18a\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create subdir\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir18a\\\\subdir\", DELETE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Clear directory 1 permissions\", [&]() {\n            set_dacl(h2.get(), 0);\n        });\n\n        h2.reset();\n\n        test(\"Create directory 2\", [&]() {\n            h2 = create_file(dir + u\"\\\\renamedir18b\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n        });\n\n        if (h2) {\n            test(\"Set directory 2 permissions\", [&]() {\n                set_dacl(h2.get(), SYNCHRONIZE | FILE_ADD_SUBDIRECTORY);\n            });\n\n            h2.reset();\n        }\n\n        test(\"Move file to directory 2\", [&]() {\n            set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir18b\\\\subdir\");\n        });\n\n        test(\"Try to move back to directory 1\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamedir18a\\\\subdir\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir19\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set directory permissions\", [&]() {\n            set_dacl(h.get(), SYNCHRONIZE | FILE_TRAVERSE | FILE_ADD_FILE);\n        });\n\n        h.reset();\n\n        test(\"Create file 1\", [&]() {\n            h = create_file(dir + u\"\\\\renamedir19\\\\file1\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        if (h) {\n            test(\"Create file 2\", [&]() {\n                h2 = create_file(dir + u\"\\\\renamedir19\\\\file2\", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n            });\n\n            if (h2) {\n                test(\"Clear file 2 permissions\", [&]() {\n                    set_dacl(h2.get(), 0);\n                });\n\n                h2.reset();\n\n                test(\"Try to overwrite file 2 with file 1\", [&]() {\n                    exp_status([&]() {\n                        set_rename_information(h.get(), true, nullptr, dir + u\"\\\\renamedir19\\\\file2\");\n                    }, STATUS_ACCESS_DENIED);\n                });\n            }\n\n            h.reset();\n        }\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir20\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set directory permissions (inc. FILE_DELETE_CHILD)\", [&]() {\n            set_dacl(h.get(), SYNCHRONIZE | FILE_TRAVERSE | FILE_ADD_FILE | FILE_DELETE_CHILD);\n        });\n\n        h.reset();\n\n        test(\"Create file 1\", [&]() {\n            h = create_file(dir + u\"\\\\renamedir20\\\\file1\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        if (h) {\n            test(\"Create file 2\", [&]() {\n                h2 = create_file(dir + u\"\\\\renamedir20\\\\file2\", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n            });\n\n            if (h2) {\n                test(\"Clear file 2 permissions\", [&]() {\n                    set_dacl(h2.get(), 0);\n                });\n\n                h2.reset();\n\n                test(\"Overwrite file 2 with file 1\", [&]() {\n                    set_rename_information(h.get(), true, nullptr, dir + u\"\\\\renamedir20\\\\file2\");\n                });\n            }\n\n            h.reset();\n        }\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir21\", WRITE_DAC, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set directory permissions\", [&]() {\n            set_dacl(h.get(), SYNCHRONIZE | FILE_TRAVERSE | FILE_ADD_FILE);\n        });\n\n        h.reset();\n\n        test(\"Create file 1\", [&]() {\n            h = create_file(dir + u\"\\\\renamedir21\\\\file1\", DELETE, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        if (h) {\n            test(\"Create file 2\", [&]() {\n                h2 = create_file(dir + u\"\\\\renamedir21\\\\file2\", WRITE_DAC, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n            });\n\n            if (h2) {\n                test(\"Set file 2 permissions to DELETE\", [&]() {\n                    set_dacl(h2.get(), DELETE);\n                });\n\n                h2.reset();\n\n                test(\"Overwrite file 2 with file 1\", [&]() {\n                    set_rename_information(h.get(), true, nullptr, dir + u\"\\\\renamedir21\\\\file2\");\n                });\n            }\n\n            h.reset();\n        }\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile22a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create readonly file\", [&]() {\n            create_file(dir + u\"\\\\renamefile22b\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Try to overwrite readonly file\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), true, nullptr, dir + u\"\\\\renamefile22b\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile23a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create system file\", [&]() {\n            create_file(dir + u\"\\\\renamefile23b\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_SYSTEM, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Overwrite system file\", [&]() {\n            set_rename_information(h.get(), true, nullptr, dir + u\"\\\\renamefile23b\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile24\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        struct {\n            u16string name;\n            string desc;\n        } invalid_names[] = {\n            { u\"/\", \"slash\" },\n            { u\":\", \"colon\" },\n            { u\"<\", \"less than\" },\n            { u\">\", \"greater than\" },\n            { u\"\\\"\", \"quote\" },\n            { u\"|\", \"pipe\" },\n            { u\"?\", \"question mark\" },\n            { u\"*\", \"asterisk\" }\n        };\n\n        for (const auto& n : invalid_names) {\n            test(\"Try renaming to invalid name (\" + n.desc + \")\", [&]() {\n                auto fn = dir + u\"\\\\renamefile24\" + n.name;\n\n                exp_status([&]() {\n                    set_rename_information(h.get(), false, nullptr, fn);\n                }, STATUS_OBJECT_NAME_INVALID);\n            });\n        }\n\n        bool is_ntfs = fstype == fs_type::ntfs;\n\n        test(\"Rename to file with more than 255 UTF-8 characters\", [&]() {\n            auto fn = dir + u\"\\\\rename24\";\n\n            for (unsigned int i = 0; i < 64; i++) {\n                fn += u\"\\U0001f525\";\n            }\n\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, fn);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n        });\n\n        test(\"Rename to file with WTF-16 (1)\", [&]() {\n            auto fn = dir + u\"\\\\rename24\";\n\n            fn += (char16_t)0xd83d;\n\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, fn);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n        });\n\n        test(\"Rename to file with WTF-16 (2)\", [&]() {\n            auto fn = dir + u\"\\\\rename24\";\n\n            fn += (char16_t)0xdd25;\n\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, fn);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n        });\n\n        test(\"Rename to file with WTF-16 (3)\", [&]() {\n            auto fn = dir + u\"\\\\rename24\";\n\n            fn += (char16_t)0xdd25;\n            fn += (char16_t)0xd83d;\n\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, fn);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory 1\", [&]() {\n        h = create_file(dir + u\"\\\\renamedir25a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create directory 2\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamedir25b\", MAXIMUM_ALLOWED, 0, FILE_SHARE_DELETE,\n                         FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Try to overwrite directory 2\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), true, nullptr, dir + u\"\\\\renamedir25b\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n        h2.reset();\n    }\n\n    test(\"Create file 1\", [&]() {\n        create_file(dir + u\"\\\\renamefile26a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file 2\", [&]() {\n        h = create_file(dir + u\"\\\\renamefile26b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to move file within other file\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\renamefile26a\\\\file\");\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        h.reset();\n    }\n\n    // FIXME - does SD change when file moved across directories?\n    // FIXME - check can't rename root directory?\n}\n\nvoid test_rename_ex(HANDLE token, const u16string& dir) {\n    unique_handle h, h2;\n\n    // FileRenameInformationEx introduced with Windows 10 1709\n\n    test(\"Create file 1\", [&]() {\n        create_file(dir + u\"\\\\renamefileex1a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file 2\", [&]() {\n        h = create_file(dir + u\"\\\\renamefileex1b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try renaming file 2 to file 1 without FILE_RENAME_REPLACE_IF_EXISTS set\", [&]() {\n            exp_status([&]() {\n                set_rename_information_ex(h.get(), 0, nullptr, dir + u\"\\\\renamefileex1a\");\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        test(\"Rename file 2 to file 1 with FILE_RENAME_REPLACE_IF_EXISTS\", [&]() {\n            set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS, nullptr, dir + u\"\\\\renamefileex1a\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamefileex1a\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamefileex1a\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"renamefileex1a\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file 1\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamefileex2a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file 2\", [&]() {\n        h = create_file(dir + u\"\\\\renamefileex2b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try renaming file 2 to file 1 without FILE_RENAME_REPLACE_IF_EXISTS\", [&]() {\n            exp_status([&]() {\n                set_rename_information_ex(h.get(), 0, nullptr, dir + u\"\\\\renamefileex2a\");\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        test(\"Try renaming file 2 to file 1 with FILE_RENAME_REPLACE_IF_EXISTS and file 1 open\", [&]() {\n            exp_status([&]() {\n                set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS, nullptr, dir + u\"\\\\renamefileex2a\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    h2.reset();\n\n    test(\"Create file 1\", [&]() {\n        create_file(dir + u\"\\\\renamefileex3a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file 2\", [&]() {\n        h = create_file(dir + u\"\\\\renamefileex3b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try renaming file 2 to file 1 uppercase without FILE_RENAME_REPLACE_IF_EXISTS\", [&]() {\n            exp_status([&]() {\n                set_rename_information_ex(h.get(), 0, nullptr, dir + u\"\\\\RENAMEFILEEX3A\");\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        test(\"Rename file 2 to file 1 uppercase with FILE_RENAME_REPLACE_IF_EXISTS\", [&]() {\n            set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS, nullptr, dir + u\"\\\\RENAMEFILEEX3A\");\n        });\n\n        test(\"Check name\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\RENAMEFILEEX3A\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\RENAMEFILEEX3A\\\".\");\n        });\n\n        test(\"Check directory entry\", [&]() {\n            u16string_view name = u\"RENAMEFILEEX3A\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefileex4a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create readonly file\", [&]() {\n            create_file(dir + u\"\\\\renamefileex4b\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Try to overwrite readonly file\", [&]() {\n            exp_status([&]() {\n                set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS, nullptr, dir + u\"\\\\renamefileex4b\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefileex5a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Create readonly file\", [&]() {\n            create_file(dir + u\"\\\\renamefileex5b\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, 0, FILE_CREATE, 0, FILE_CREATED);\n        });\n\n        test(\"Overwrite readonly file using FILE_RENAME_IGNORE_READONLY_ATTRIBUTE\", [&]() {\n            set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_IGNORE_READONLY_ATTRIBUTE,\n                                      nullptr, dir + u\"\\\\renamefileex5b\");\n        });\n\n        h.reset();\n    }\n\n    // traverse privilege needed to query hard links\n    test(\"Add SeChangeNotifyPrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file 1\", [&]() {\n        h = create_file(dir + u\"\\\\renamefileex6a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file 2\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamefileex6b\", SYNCHRONIZE | DELETE | FILE_READ_DATA | FILE_WRITE_DATA, 0,\n                         FILE_SHARE_DELETE, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Overwrite file 2 using FILE_RENAME_POSIX_SEMANTICS\", [&]() {\n            set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS,\n                                      nullptr, dir + u\"\\\\renamefileex6b\");\n        });\n\n        test(\"Check name of file 1\", [&]() {\n            auto fn = query_file_name_information(h.get());\n\n            static const u16string_view ends_with = u\"\\\\renamefileex6b\";\n\n            if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                throw runtime_error(\"Name did not end with \\\"\\\\renamefileex6b\\\".\");\n        });\n\n        test(\"Check name of file 2\", [&]() {\n            auto fn = query_file_name_information(h2.get());\n\n            static const u16string_view ends_with = u\"\\\\renamefileex6b\";\n\n            // NTFS moves this to \\$Extend\\$Deleted directory\n\n            if (fn.size() >= ends_with.size() && fn.substr(fn.size() - ends_with.size()) == ends_with)\n                throw runtime_error(\"Name ended with \\\"\\\\renamefileex6b\\\".\");\n        });\n\n        test(\"Check standard information of file 1\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h.get());\n\n            if (fsi.NumberOfLinks != 1)\n                throw formatted_error(\"NumberOfLinks was {}, expected 1\", fsi.NumberOfLinks);\n\n            if (fsi.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n        });\n\n        test(\"Check standard link information of file 1\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h.get());\n\n            if (fsli.NumberOfAccessibleLinks != 1)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 1\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (fsli.DeletePending)\n                throw runtime_error(\"DeletePending was true, expected false\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Check standard information of file 2\", [&]() {\n            auto fsi = query_information<FILE_STANDARD_INFORMATION>(h2.get());\n\n            if (fsi.NumberOfLinks != 0)\n                throw formatted_error(\"NumberOfLinks was {}, expected 0\", fsi.NumberOfLinks);\n\n            if (!fsi.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n        });\n\n        test(\"Check standard link information of file 2\", [&]() {\n            auto fsli = query_information<FILE_STANDARD_LINK_INFORMATION>(h2.get());\n\n            if (fsli.NumberOfAccessibleLinks != 0)\n                throw formatted_error(\"NumberOfAccessibleLinks was {}, expected 0\", fsli.NumberOfAccessibleLinks);\n\n            if (fsli.TotalNumberOfLinks != 1)\n                throw formatted_error(\"TotalNumberOfLinks was {}, expected 1\", fsli.TotalNumberOfLinks);\n\n            if (!fsli.DeletePending)\n                throw runtime_error(\"DeletePending was false, expected true\");\n\n            if (fsli.Directory)\n                throw runtime_error(\"Directory was true, expected false\");\n        });\n\n        test(\"Check new directory entry\", [&]() {\n            u16string_view name = u\"renamefileex6b\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n\n        test(\"Check old directory entry gone\", [&]() {\n            u16string_view name = u\"renamefileex6a\";\n\n            exp_status([&]() {\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n\n        test(\"Try to clear delete bit on file 2\", [&]() {\n            exp_status([&]() {\n                set_disposition_information(h2.get(), false);\n            }, STATUS_FILE_DELETED);\n        });\n\n        test(\"Write to file 2\", [&]() {\n            static const vector<uint8_t> data = {'h','e','l','l','o'};\n\n            write_file(h2.get(), data);\n        });\n\n        test(\"Read from file 2\", [&]() {\n            static const vector<uint8_t> exp = {'h','e','l','l','o'};\n\n            auto buf = read_file(h2.get(), exp.size(), 0);\n\n            if (buf.size() != exp.size())\n                throw formatted_error(\"Read {} bytes, expected {}\", buf.size(), exp.size());\n\n            if (buf != exp)\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        int64_t dir_id;\n\n        test(\"Check file 1 hardlinks\", [&]() {\n            auto items = query_links(h.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& item = items.front();\n\n            if (item.second != u\"renamefileex6b\")\n                throw formatted_error(\"Link was called {}, expected renamefileex6b\", u16string_to_string(item.second));\n\n            dir_id = item.first;\n        });\n\n        test(\"Check file 2 hardlinks\", [&]() {\n            auto items = query_links(h2.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& item = items.front();\n\n            if (item.second == u\"renamefileex6b\")\n                throw formatted_error(\"Link was called renamefileex6b, expected something else\", u16string_to_string(item.second));\n\n            if (item.first == dir_id)\n                throw runtime_error(\"Dir ID of orphaned inode is same as before\");\n        });\n\n        h.reset();\n        h2.reset();\n    }\n\n    test(\"Disable token privileges\", [&]() {\n        disable_token_privileges(token);\n    });\n\n    test(\"Create file 1\", [&]() {\n        h = create_file(dir + u\"\\\\renamefileex7a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Create file 2 without FILE_SHARE_DELETE\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamefileex7b\", MAXIMUM_ALLOWED, 0,\n                         0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Try to overwrite file 2 using FILE_RENAME_POSIX_SEMANTICS\", [&]() {\n            exp_status([&]() {\n                set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS,\n                                          nullptr, dir + u\"\\\\renamefileex7b\");\n            }, STATUS_SHARING_VIOLATION);\n        });\n\n        h.reset();\n        h2.reset();\n    }\n\n    test(\"Create directory 1\", [&]() {\n        h = create_file(dir + u\"\\\\renamedirex8a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create directory 2\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamedirex8b\", MAXIMUM_ALLOWED, 0, FILE_SHARE_DELETE,\n                         FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Try to overwrite directory 2\", [&]() {\n            exp_status([&]() {\n                set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS,\n                                          nullptr, dir + u\"\\\\renamedirex8b\");\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n        h2.reset();\n    }\n\n    test(\"Create directory 1\", [&]() {\n        h = create_file(dir + u\"\\\\renamedirex9a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create directory 2\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamedirex9b\", MAXIMUM_ALLOWED, 0, FILE_SHARE_DELETE,\n                         FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Overwrite directory 2 using FILE_RENAME_POSIX_SEMANTICS\", [&]() {\n            set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS,\n                                      nullptr, dir + u\"\\\\renamedirex9b\");\n        });\n\n        h.reset();\n        h2.reset();\n    }\n\n    test(\"Create directory 1\", [&]() {\n        h = create_file(dir + u\"\\\\renamedirex10a\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create directory 2\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamedirex10b\", MAXIMUM_ALLOWED, 0, FILE_SHARE_DELETE,\n                         FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create file in directory 2\", [&]() {\n        create_file(dir + u\"\\\\renamedirex10b\\\\file\", MAXIMUM_ALLOWED, 0, FILE_SHARE_DELETE,\n                    FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        test(\"Try overwriting non-empty directory using FILE_RENAME_POSIX_SEMANTICS\", [&]() {\n            exp_status([&]() {\n                set_rename_information_ex(h.get(), FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS,\n                                          nullptr, dir + u\"\\\\renamedirex10b\");\n            }, STATUS_DIRECTORY_NOT_EMPTY);\n        });\n\n        h.reset();\n        h2.reset();\n    }\n\n    test(\"Create image file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefileex11a\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamefileex11b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    auto img = pe_image(as_bytes(span(\"hello\")));\n\n    if (h && h2) {\n        unique_handle sect;\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), img);\n        });\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get());\n        });\n\n        h.reset();\n\n        if (sect) {\n            test(\"Try overwriting mapped image file by rename\", [&]() {\n                exp_status([&]() {\n                    set_rename_information_ex(h2.get(), FILE_RENAME_REPLACE_IF_EXISTS, nullptr, dir + u\"\\\\renamefileex11a\");\n                }, STATUS_ACCESS_DENIED);\n            });\n        }\n\n        h2.reset();\n    }\n\n    test(\"Create image file\", [&]() {\n        h = create_file(dir + u\"\\\\renamefileex12a\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA,\n                        0, 0, FILE_CREATE, FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    test(\"Create file\", [&]() {\n        h2 = create_file(dir + u\"\\\\renamefileex12b\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h && h2) {\n        unique_handle sect;\n\n        test(\"Write to file\", [&]() {\n            write_file(h.get(), img);\n        });\n\n        test(\"Create section\", [&]() {\n            sect = create_section(SECTION_ALL_ACCESS, nullopt, PAGE_READWRITE, SEC_IMAGE, h.get());\n        });\n\n        h.reset();\n\n        if (sect) {\n            test(\"Try overwriting mapped image file by POSIX rename\", [&]() {\n                exp_status([&]() {\n                    set_rename_information_ex(h2.get(), FILE_RENAME_REPLACE_IF_EXISTS | FILE_RENAME_POSIX_SEMANTICS,\n                                              nullptr, dir + u\"\\\\renamefileex12a\");\n                }, STATUS_ACCESS_DENIED);\n            });\n        }\n\n        h2.reset();\n    }\n\n    // FIXME - FILE_RENAME_SUPPRESS_PIN_STATE_INHERITANCE\n    // FIXME - FILE_RENAME_SUPPRESS_STORAGE_RESERVE_INHERITANCE\n    // FIXME - FILE_RENAME_NO_INCREASE_AVAILABLE_SPACE\n    // FIXME - FILE_RENAME_NO_DECREASE_AVAILABLE_SPACE\n    // FIXME - FILE_RENAME_FORCE_RESIZE_TARGET_SR\n    // FIXME - FILE_RENAME_FORCE_RESIZE_SOURCE_SR\n}\n"
  },
  {
    "path": "src/tests/reparse.cpp",
    "content": "#include \"test.h\"\n\n#define FSCTL_SET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 41, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)\n#define FSCTL_GET_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 42, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define FSCTL_DELETE_REPARSE_POINT CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 43, METHOD_BUFFERED, FILE_SPECIAL_ACCESS)\n\n#define IO_REPARSE_TAG_FAKE_MICROSOFT 0x8000DEAD\n#define IO_REPARSE_TAG_FAKE 0x0000BEEF\n#define IO_REPARSE_TAG_FAKE_MICROSOFT_DIR 0x9000CAFE\n\n#ifdef _MSC_VER\n#define SYMLINK_FLAG_RELATIVE 1\n#endif\n\nstatic const uint8_t reparse_guid[] = { 0xc5, 0xcc, 0x8b, 0xf2, 0xdc, 0xc3, 0x88, 0x42, 0xa1, 0xe2, 0x50, 0x43, 0x97, 0xeb, 0x26, 0xa6 };\nstatic const uint8_t wrong_guid[] = { 0x61, 0x81, 0x36, 0x76, 0x32, 0xa6, 0xbc, 0x4d, 0xb7, 0xe0, 0x3a, 0xb6, 0x60, 0x03, 0x9e, 0x4e };\n\nusing namespace std;\n\nstatic void set_symlink(HANDLE h, u16string_view substitute_name, u16string_view print_name, bool relative) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf;\n\n    buf.resize(offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer.PathBuffer) + ((substitute_name.length() + print_name.length()) * sizeof(char16_t)));\n\n    auto& rdb = *(REPARSE_DATA_BUFFER*)buf.data();\n\n    rdb.ReparseTag = IO_REPARSE_TAG_SYMLINK;\n    rdb.ReparseDataLength = buf.size() - offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer);\n    rdb.Reserved = 0;\n    rdb.SymbolicLinkReparseBuffer.SubstituteNameOffset = 0;\n    rdb.SymbolicLinkReparseBuffer.SubstituteNameLength = substitute_name.length() * sizeof(char16_t);\n    rdb.SymbolicLinkReparseBuffer.PrintNameOffset = rdb.SymbolicLinkReparseBuffer.SubstituteNameLength;\n    rdb.SymbolicLinkReparseBuffer.PrintNameLength = print_name.length() * sizeof(char16_t);\n    rdb.SymbolicLinkReparseBuffer.Flags = relative ? SYMLINK_FLAG_RELATIVE : 0;\n\n    memcpy((char*)rdb.SymbolicLinkReparseBuffer.PathBuffer + rdb.SymbolicLinkReparseBuffer.SubstituteNameOffset,\n           substitute_name.data(), rdb.SymbolicLinkReparseBuffer.SubstituteNameLength);\n    memcpy((char*)rdb.SymbolicLinkReparseBuffer.PathBuffer + rdb.SymbolicLinkReparseBuffer.PrintNameOffset,\n           print_name.data(), rdb.SymbolicLinkReparseBuffer.PrintNameLength);\n\n    auto ev = create_event();\n\n    Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb,\n                             FSCTL_SET_REPARSE_POINT, buf.data(), buf.size(),\n                             nullptr, 0);\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(ev.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic void set_mount_point(HANDLE h, u16string_view substitute_name, u16string_view print_name) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf;\n\n    // both substitute and print strings need to be null-terminated\n\n    buf.resize(offsetof(REPARSE_DATA_BUFFER, MountPointReparseBuffer.PathBuffer) + ((substitute_name.length() + 1 + print_name.length() + 1) * sizeof(char16_t)));\n\n    auto& rdb = *(REPARSE_DATA_BUFFER*)buf.data();\n\n    rdb.ReparseTag = IO_REPARSE_TAG_MOUNT_POINT;\n    rdb.ReparseDataLength = buf.size() - offsetof(REPARSE_DATA_BUFFER, MountPointReparseBuffer);\n    rdb.Reserved = 0;\n\n    auto& mprb = rdb.MountPointReparseBuffer;\n\n    mprb.SubstituteNameOffset = 0;\n    mprb.SubstituteNameLength = substitute_name.length() * sizeof(char16_t);\n    mprb.PrintNameOffset = mprb.SubstituteNameLength + sizeof(char16_t);\n    mprb.PrintNameLength = print_name.length() * sizeof(char16_t);\n\n    memcpy((char*)mprb.PathBuffer + mprb.SubstituteNameOffset,\n           substitute_name.data(), mprb.SubstituteNameLength);\n    mprb.PathBuffer[(mprb.SubstituteNameOffset + mprb.SubstituteNameLength) / sizeof(char16_t)] = 0;\n    memcpy((char*)mprb.PathBuffer + mprb.PrintNameOffset,\n           print_name.data(), mprb.PrintNameLength);\n    mprb.PathBuffer[(mprb.PrintNameOffset + mprb.PrintNameLength) / sizeof(char16_t)] = 0;\n\n    auto ev = create_event();\n\n    Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb,\n                             FSCTL_SET_REPARSE_POINT, buf.data(), buf.size(),\n                             nullptr, 0);\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(ev.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic void set_ms_reparse_point(HANDLE h, uint32_t tag, span<const uint8_t> data) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf;\n\n    buf.resize(offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + data.size());\n\n    auto& rdb = *(REPARSE_DATA_BUFFER*)buf.data();\n\n    rdb.ReparseTag = tag;\n    rdb.ReparseDataLength = buf.size() - offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer);\n    rdb.Reserved = 0;\n\n    memcpy(rdb.GenericReparseBuffer.DataBuffer, data.data(), data.size());\n\n    auto ev = create_event();\n\n    Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb,\n                             FSCTL_SET_REPARSE_POINT, buf.data(), buf.size(),\n                             nullptr, 0);\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(ev.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic void set_reparse_point_guid(HANDLE h, uint32_t tag, const uint8_t* guid, span<const uint8_t> data) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf;\n\n    buf.resize(offsetof(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + data.size());\n\n    auto& rgdb = *(REPARSE_GUID_DATA_BUFFER*)buf.data();\n\n    rgdb.ReparseTag = tag;\n    rgdb.ReparseDataLength = buf.size() - offsetof(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer.DataBuffer);\n    rgdb.Reserved = 0;\n    memcpy(&rgdb.ReparseGuid, guid, sizeof(rgdb.ReparseGuid));\n    memcpy(rgdb.GenericReparseBuffer.DataBuffer, data.data(), data.size());\n\n    auto ev = create_event();\n\n    Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb,\n                             FSCTL_SET_REPARSE_POINT, buf.data(), buf.size(),\n                             nullptr, 0);\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(ev.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic varbuf<REPARSE_DATA_BUFFER> query_reparse_point(HANDLE h) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf;\n\n    buf.resize(4096);\n\n    auto ev = create_event();\n\n    Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb,\n                             FSCTL_GET_REPARSE_POINT, nullptr, 0,\n                             buf.data(), buf.size());\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(ev.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    auto& rdb = *(REPARSE_DATA_BUFFER*)buf.data();\n    varbuf<REPARSE_DATA_BUFFER> ret;\n\n    ret.buf.resize(offsetof(REPARSE_DATA_BUFFER, SymbolicLinkReparseBuffer) + rdb.ReparseDataLength);\n    memcpy(ret.buf.data(), buf.data(), ret.buf.size());\n\n    return ret;\n}\n\nstatic varbuf<REPARSE_GUID_DATA_BUFFER> query_reparse_point_guid(HANDLE h) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf;\n\n    buf.resize(4096);\n\n    auto ev = create_event();\n\n    Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb,\n                             FSCTL_GET_REPARSE_POINT, nullptr, 0,\n                             buf.data(), buf.size());\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(ev.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    auto& rgdb = *(REPARSE_GUID_DATA_BUFFER*)buf.data();\n    varbuf<REPARSE_GUID_DATA_BUFFER> ret;\n\n    ret.buf.resize(offsetof(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer.DataBuffer) + rgdb.ReparseDataLength);\n    memcpy(ret.buf.data(), buf.data(), ret.buf.size());\n\n    return ret;\n}\n\nstatic void delete_reparse_point(HANDLE h, uint32_t tag) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    REPARSE_DATA_BUFFER rdb;\n\n    auto ev = create_event();\n\n    rdb.ReparseTag = tag;\n    rdb.ReparseDataLength = 0;\n    rdb.Reserved = 0;\n\n    Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb,\n                             FSCTL_DELETE_REPARSE_POINT, &rdb,\n                             offsetof(REPARSE_DATA_BUFFER, GenericReparseBuffer),\n                             nullptr, 0);\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(ev.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic void delete_reparse_point_guid(HANDLE h, uint32_t tag, const uint8_t* guid) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    REPARSE_GUID_DATA_BUFFER rgdb;\n\n    auto ev = create_event();\n\n    rgdb.ReparseTag = tag;\n    rgdb.ReparseDataLength = 0;\n    rgdb.Reserved = 0;\n    memcpy(&rgdb.ReparseGuid, guid, sizeof(rgdb.ReparseGuid));\n\n    Status = NtFsControlFile(h, ev.get(), nullptr, nullptr, &iosb,\n                             FSCTL_DELETE_REPARSE_POINT, &rgdb,\n                             offsetof(REPARSE_GUID_DATA_BUFFER, GenericReparseBuffer),\n                             nullptr, 0);\n\n    if (Status == STATUS_PENDING) {\n        Status = NtWaitForSingleObject(ev.get(), false, nullptr);\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        Status = iosb.Status;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\ntemplate<typename T>\nstatic void check_reparse_dirent(const u16string& dir, u16string_view name, uint32_t tag) {\n    auto items = query_dir<T>(dir, name);\n\n    if (items.size() != 1)\n        throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n    auto& fdi = *static_cast<const T*>(items.front());\n\n    if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n        throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n    if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n        throw runtime_error(\"FileName did not match.\");\n\n    if constexpr (requires { T::ReparsePointTag; }) {\n        if (fdi.EaSize != 0)\n            throw formatted_error(\"EaSize was {:08x}, expected 0\", fdi.EaSize);\n\n        if (fdi.ReparsePointTag != tag)\n            throw formatted_error(\"ReparsePointTag was {:08x}, expected {:08x}\", fdi.ReparsePointTag, tag);\n    } else {\n        if (fdi.EaSize != tag)\n            throw formatted_error(\"EaSize was {:08x}, expected {:08x}\", fdi.EaSize, tag);\n    }\n}\n\nvoid test_reparse(HANDLE token, const u16string& dir) {\n    unique_handle h;\n    int64_t file1id = 0, file2id = 0;\n\n    test(\"Add SeCreateSymbolicLinkPrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_CREATE_SYMBOLIC_LINK_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse1\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file1id = fii.IndexNumber.QuadPart;\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse2\", FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_EA,\n                        0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file2id = fii.IndexNumber.QuadPart;\n        });\n\n        test(\"Clear archive flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL\", fbi.FileAttributes);\n        });\n\n        test(\"Set as symlink\", [&]() {\n            set_symlink(h.get(), u\"reparse1\", u\"reparse1\", true);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_REPARSE_POINT)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT\", fbi.FileAttributes);\n        });\n\n        test(\"Query reparse point\", [&]() {\n            auto buf = query_reparse_point(h.get());\n            auto& rdb = *static_cast<REPARSE_DATA_BUFFER*>(buf);\n\n            if (rdb.ReparseTag != IO_REPARSE_TAG_SYMLINK)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_SYMLINK\", rdb.ReparseTag);\n\n            auto& slrb = rdb.SymbolicLinkReparseBuffer;\n\n            auto dest = u16string_view((char16_t*)((char*)slrb.PathBuffer + slrb.SubstituteNameOffset),\n                                       slrb.SubstituteNameLength / sizeof(char16_t));\n\n            if (dest != u\"reparse1\")\n                throw formatted_error(\"Destination was \\\"{}\\\", expected \\\"reparse1\\\"\", u16string_to_string(dest));\n\n            if (slrb.Flags != SYMLINK_FLAG_RELATIVE)\n                throw formatted_error(\"Flags value was {}, expected SYMLINK_FLAG_RELATIVE\", slrb.Flags);\n        });\n\n        // Only works on specific NTFS metadata file - see section 2.1.5.5.2 of [MS-FSA]\n        test(\"Try checking FileReparsePointInformation\", [&]() {\n            exp_status([&]() {\n                query_dir<FILE_REPARSE_POINT_INFORMATION>(dir, u\"reparse2\");\n            }, STATUS_INVALID_INFO_CLASS);\n        });\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != 0)\n                throw formatted_error(\"EaSize was {:08x}, expected 0\", feai.EaSize);\n        });\n\n        // needs FILE_READ_ATTRIBUTES\n        test(\"Query FileStatInformation\", [&]() {\n            auto fsi = query_information<FILE_STAT_INFORMATION>(h.get());\n\n            if (fsi.ReparseTag != IO_REPARSE_TAG_SYMLINK)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_SYMLINK\", fsi.ReparseTag);\n        });\n\n        // needs FILE_READ_EA as well\n        test(\"Query FileStatLxInformation\", [&]() {\n            auto fsli = query_information<FILE_STAT_LX_INFORMATION>(h.get());\n\n            if (fsli.ReparseTag != IO_REPARSE_TAG_SYMLINK)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_SYMLINK\", fsli.ReparseTag);\n        });\n\n        test(\"Query FileAttributeTagInformation\", [&]() {\n            auto fati = query_information<FILE_ATTRIBUTE_TAG_INFORMATION>(h.get());\n\n            if (fati.ReparseTag != IO_REPARSE_TAG_SYMLINK)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_SYMLINK\", fati.ReparseTag);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse2\", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN,\n                        0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Check ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            if (fii.IndexNumber.QuadPart != file1id)\n                throw runtime_error(\"File ID was not expected value\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"reparse2\", IO_REPARSE_TAG_SYMLINK);\n    });\n\n    test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"reparse2\", IO_REPARSE_TAG_SYMLINK);\n    });\n\n    test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"reparse2\", IO_REPARSE_TAG_SYMLINK);\n    });\n\n    test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"reparse2\", IO_REPARSE_TAG_SYMLINK);\n    });\n\n    test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"reparse2\", IO_REPARSE_TAG_SYMLINK);\n    });\n\n    test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"reparse2\", IO_REPARSE_TAG_SYMLINK);\n    });\n\n    test(\"Open file with FILE_OPEN_REPARSE_POINT\", [&]() {\n        h = create_file(dir + u\"\\\\reparse2\", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OPEN,\n                        FILE_OPEN_REPARSE_POINT, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Check ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            if (fii.IndexNumber.QuadPart != file2id)\n                throw runtime_error(\"File ID was not expected value\");\n        });\n\n        test(\"Try deleting reparse point with wrong tag\", [&]() {\n            exp_status([&]() {\n                delete_reparse_point(h.get(), IO_REPARSE_TAG_MOUNT_POINT);\n            }, STATUS_IO_REPARSE_TAG_MISMATCH);\n        });\n\n        test(\"Clear archive flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_REPARSE_POINT)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT\", fbi.FileAttributes);\n        });\n\n        test(\"Delete reparse point\", [&]() {\n            delete_reparse_point(h.get(), IO_REPARSE_TAG_SYMLINK);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL\", fbi.FileAttributes);\n        });\n\n        test(\"Try to delete reparse point again\", [&]() {\n            exp_status([&]() {\n                delete_reparse_point(h.get(), IO_REPARSE_TAG_SYMLINK);\n            }, STATUS_NOT_A_REPARSE_POINT);\n        });\n\n        test(\"Try to query reparse point\", [&]() {\n            exp_status([&]() {\n                query_reparse_point_guid(h.get());\n            }, STATUS_NOT_A_REPARSE_POINT);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse2\", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN,\n                        0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Check ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            if (fii.IndexNumber.QuadPart != file2id)\n                throw runtime_error(\"File ID was not expected value\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse3\", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set as symlink\", [&]() {\n            set_symlink(h.get(), u\"reparse1\", u\"reparse1\", true);\n        });\n\n        h.reset();\n    }\n\n    test(\"Overwrite file through symlink\", [&]() {\n        create_file(dir + u\"\\\\reparse3\", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OVERWRITE,\n                    FILE_NON_DIRECTORY_FILE, FILE_OVERWRITTEN);\n    });\n\n    test(\"Check target rather than symlink overwritten\", [&]() {\n        check_reparse_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"reparse3\", IO_REPARSE_TAG_SYMLINK);\n    });\n\n    test(\"Overwrite symlink\", [&]() {\n        create_file(dir + u\"\\\\reparse3\", FILE_WRITE_ATTRIBUTES, 0, 0, FILE_OVERWRITE,\n                    FILE_NON_DIRECTORY_FILE | FILE_OPEN_REPARSE_POINT, FILE_OVERWRITTEN);\n    });\n\n    test(\"Check symlink overwritten\", [&]() {\n        check_reparse_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"reparse3\", 0);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse4\", FILE_WRITE_DATA, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), random_data(4096), 0);\n        });\n\n        test(\"Set as symlink\", [&]() {\n            exp_status([&]() {\n                set_symlink(h.get(), u\"reparse1\", u\"reparse1\", true);\n            }, STATUS_IO_REPARSE_DATA_INVALID);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse5\", FILE_WRITE_DATA, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set as symlink\", [&]() {\n            set_symlink(h.get(), u\"reparse1\", u\"reparse1\", true);\n        });\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), random_data(4096), 0);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\reparse6\", FILE_WRITE_DATA, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set as symlink\", [&]() {\n            set_symlink(h.get(), u\"reparse1\", u\"reparse1\", true);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse7\", FILE_WRITE_DATA | FILE_WRITE_EA, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write EA\", [&]() {\n            write_ea(h.get(), \"hello\", \"world\");\n        });\n\n        test(\"Set as symlink\", [&]() {\n            set_symlink(h.get(), u\"reparse1\", u\"reparse1\", true);\n        });\n\n        test(\"Write another EA\", [&]() {\n            write_ea(h.get(), \"lemon\", \"curry\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse8\", FILE_WRITE_DATA, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set as symlink with invalid target\", [&]() {\n            set_symlink(h.get(), u\"reparsenonsuch\", u\"reparsenonsuch\", true);\n        });\n\n        h.reset();\n    }\n\n    test(\"Try to open invalid file through symlink\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\reparse8\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                        FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n        }, STATUS_OBJECT_NAME_NOT_FOUND);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse9a\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file1id = fii.IndexNumber.QuadPart;\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse9b\", FILE_WRITE_DATA, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set as absolute symlink\", [&]() {\n            set_symlink(h.get(), dir + u\"\\\\reparse9a\", u\"reparse9a\", false);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file through absolute symlink\", [&]() {\n        h = create_file(dir + u\"\\\\reparse9b\", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN,\n                        FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            if (fii.IndexNumber.QuadPart != file1id)\n                throw runtime_error(\"File ID had unexpected value\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\reparse10a\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get directory ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file1id = fii.IndexNumber.QuadPart;\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file within directory\", [&]() {\n        create_file(dir + u\"\\\\reparse10a\\\\file\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                    FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\reparse10b\", FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_EA,\n                        0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get directory ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file2id = fii.IndexNumber.QuadPart;\n        });\n\n        test(\"Clear archive flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY\", fbi.FileAttributes);\n        });\n\n        test(\"Set as mount point\", [&]() {\n            set_mount_point(h.get(), dir + u\"\\\\reparse10a\", u\"reparse10a\");\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY\", fbi.FileAttributes);\n        });\n\n        test(\"Query reparse point\", [&]() {\n            auto buf = query_reparse_point(h.get());\n            auto& rdb = *static_cast<REPARSE_DATA_BUFFER*>(buf);\n\n            if (rdb.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_MOUNT_POINT\", rdb.ReparseTag);\n\n            auto& mprb = rdb.MountPointReparseBuffer;\n\n            auto dest = u16string_view((char16_t*)((char*)mprb.PathBuffer + mprb.SubstituteNameOffset),\n                                       mprb.SubstituteNameLength / sizeof(char16_t));\n\n            if (dest != dir + u\"\\\\reparse10a\")\n                throw formatted_error(\"Destination was \\\"{}\\\", expected \\\"{}\\\"\", u16string_to_string(dest), u16string_to_string(dir + u\"\\\\reparse10a\"));\n        });\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != 0)\n                throw formatted_error(\"EaSize was {:08x}, expected 0\", feai.EaSize);\n        });\n\n        // needs FILE_READ_ATTRIBUTES\n        test(\"Query FileStatInformation\", [&]() {\n            auto fsi = query_information<FILE_STAT_INFORMATION>(h.get());\n\n            if (fsi.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_MOUNT_POINT\", fsi.ReparseTag);\n        });\n\n        // needs FILE_READ_EA as well\n        test(\"Query FileStatLxInformation\", [&]() {\n            auto fsli = query_information<FILE_STAT_LX_INFORMATION>(h.get());\n\n            if (fsli.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_MOUNT_POINT\", fsli.ReparseTag);\n        });\n\n        test(\"Query FileAttributeTagInformation\", [&]() {\n            auto fati = query_information<FILE_ATTRIBUTE_TAG_INFORMATION>(h.get());\n\n            if (fati.ReparseTag != IO_REPARSE_TAG_MOUNT_POINT)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_MOUNT_POINT\", fati.ReparseTag);\n        });\n\n        h.reset();\n    }\n\n    test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"reparse10b\", IO_REPARSE_TAG_MOUNT_POINT);\n    });\n\n    test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"reparse10b\", IO_REPARSE_TAG_MOUNT_POINT);\n    });\n\n    test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"reparse10b\", IO_REPARSE_TAG_MOUNT_POINT);\n    });\n\n    test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"reparse10b\", IO_REPARSE_TAG_MOUNT_POINT);\n    });\n\n    test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"reparse10b\", IO_REPARSE_TAG_MOUNT_POINT);\n    });\n\n    test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"reparse10b\", IO_REPARSE_TAG_MOUNT_POINT);\n    });\n\n    test(\"Open mount point without FILE_OPEN_REPARSE_POINT\", [&]() {\n        h = create_file(dir + u\"\\\\reparse10b\", FILE_READ_ATTRIBUTES,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Get directory ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            if (fii.IndexNumber.QuadPart != file1id)\n                throw runtime_error(\"Directory ID had unexpected value\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Open mount point with FILE_OPEN_REPARSE_POINT\", [&]() {\n        h = create_file(dir + u\"\\\\reparse10b\", FILE_READ_ATTRIBUTES,\n                        0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Get directory ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            if (fii.IndexNumber.QuadPart != file2id)\n                throw runtime_error(\"Directory ID had unexpected value\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file through mount point (without FILE_OPEN_REPARSE_POINT)\", [&]() {\n        create_file(dir + u\"\\\\reparse10b\\\\file\", MAXIMUM_ALLOWED,\n                    0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    test(\"Open file through mount point (with FILE_OPEN_REPARSE_POINT)\", [&]() {\n        create_file(dir + u\"\\\\reparse10b\\\\file\", MAXIMUM_ALLOWED,\n                    0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED);\n    });\n\n    test(\"Open mount point with FILE_OPEN_REPARSE_POINT\", [&]() {\n        h = create_file(dir + u\"\\\\reparse10b\", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,\n                        0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Clear archive flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY))\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT | FILE_ATTRIBUTE_DIRECTORY\", fbi.FileAttributes);\n        });\n\n        test(\"Delete reparse point\", [&]() {\n            delete_reparse_point(h.get(), IO_REPARSE_TAG_MOUNT_POINT);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY\", fbi.FileAttributes);\n        });\n\n        test(\"Try to delete reparse point again\", [&]() {\n            exp_status([&]() {\n                delete_reparse_point(h.get(), IO_REPARSE_TAG_MOUNT_POINT);\n            }, STATUS_NOT_A_REPARSE_POINT);\n        });\n\n        test(\"Try to query reparse point\", [&]() {\n            exp_status([&]() {\n                query_reparse_point_guid(h.get());\n            }, STATUS_NOT_A_REPARSE_POINT);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open directory with children\", [&]() {\n        h = create_file(dir + u\"\\\\reparse10a\", FILE_WRITE_ATTRIBUTES,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Try to set as mount point\", [&]() {\n            exp_status([&]() {\n                set_mount_point(h.get(), dir + u\"\\\\reparse10b\", u\"reparse10b\");\n            }, STATUS_DIRECTORY_NOT_EMPTY);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open old mount point directory\", [&]() {\n        h = create_file(dir + u\"\\\\reparse10b\", FILE_WRITE_ATTRIBUTES,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Set as mount point\", [&]() {\n            set_mount_point(h.get(), dir + u\"\\\\reparse10a\", u\"reparse10a\");\n        });\n\n        test(\"Set as mount point again\", [&]() {\n            set_mount_point(h.get(), dir + u\"\\\\reparse10c\", u\"reparse10c\");\n        });\n\n        test(\"Set as symlink\", [&]() {\n            exp_status([&]() {\n                set_symlink(h.get(), dir + u\"\\\\reparse10a\", u\"reparse10a\", false);\n            }, STATUS_IO_REPARSE_TAG_MISMATCH);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse11\", FILE_WRITE_DATA | FILE_READ_ATTRIBUTES | FILE_READ_EA,\n                        0, 0, FILE_CREATE, FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to set as mount point\", [&]() {\n            exp_status([&]() {\n                set_mount_point(h.get(), dir + u\"\\\\reparse10a\", u\"reparse10a\");\n            }, STATUS_NOT_A_DIRECTORY);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse12\", FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_EA,\n                        0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file1id = fii.IndexNumber.QuadPart;\n        });\n\n        test(\"Clear archive flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL\", fbi.FileAttributes);\n        });\n\n        test(\"Set with Microsoft reparse tag\", [&]() {\n            static const string_view data = \"hello\";\n\n            set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT, span((uint8_t*)data.data(), data.size()));\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_REPARSE_POINT)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT\", fbi.FileAttributes);\n        });\n\n        test(\"Query reparse point\", [&]() {\n            auto buf = query_reparse_point(h.get());\n            auto& rdb = *static_cast<REPARSE_DATA_BUFFER*>(buf);\n\n            if (rdb.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT\", rdb.ReparseTag);\n\n            auto sv = string_view((char*)rdb.GenericReparseBuffer.DataBuffer, rdb.ReparseDataLength);\n\n            if (sv != \"hello\")\n                throw runtime_error(\"Reparse point data was not as expected\");\n        });\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != 0)\n                throw formatted_error(\"EaSize was {:08x}, expected 0\", feai.EaSize);\n        });\n\n        // needs FILE_READ_ATTRIBUTES\n        test(\"Query FileStatInformation\", [&]() {\n            auto fsi = query_information<FILE_STAT_INFORMATION>(h.get());\n\n            if (fsi.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT\", fsi.ReparseTag);\n        });\n\n        // needs FILE_READ_EA as well\n        test(\"Query FileStatLxInformation\", [&]() {\n            auto fsli = query_information<FILE_STAT_LX_INFORMATION>(h.get());\n\n            if (fsli.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT\", fsli.ReparseTag);\n        });\n\n        test(\"Query FileAttributeTagInformation\", [&]() {\n            auto fati = query_information<FILE_ATTRIBUTE_TAG_INFORMATION>(h.get());\n\n            if (fati.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT\", fati.ReparseTag);\n        });\n\n        h.reset();\n    }\n\n    test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"reparse12\", IO_REPARSE_TAG_FAKE_MICROSOFT);\n    });\n\n    test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"reparse12\", IO_REPARSE_TAG_FAKE_MICROSOFT);\n    });\n\n    test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"reparse12\", IO_REPARSE_TAG_FAKE_MICROSOFT);\n    });\n\n    test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"reparse12\", IO_REPARSE_TAG_FAKE_MICROSOFT);\n    });\n\n    test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"reparse12\", IO_REPARSE_TAG_FAKE_MICROSOFT);\n    });\n\n    test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"reparse12\", IO_REPARSE_TAG_FAKE_MICROSOFT);\n    });\n\n    test(\"Open file without FILE_OPEN_REPARSE_POINT\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\reparse12\", MAXIMUM_ALLOWED,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n        }, STATUS_IO_REPARSE_TAG_NOT_HANDLED);\n    });\n\n    test(\"Open file with FILE_OPEN_REPARSE_POINT\", [&]() {\n        h = create_file(dir + u\"\\\\reparse12\", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,\n                        0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            if (fii.IndexNumber.QuadPart != file1id)\n                throw runtime_error(\"File ID was not as expected.\");\n        });\n\n        test(\"Set with same Microsoft reparse tag\", [&]() {\n            static const string_view data = \"world\";\n\n            set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT, span((uint8_t*)data.data(), data.size()));\n        });\n\n        test(\"Try to set with different Microsoft reparse tag\", [&]() {\n            exp_status([&]() {\n                static const string_view data = \"world\";\n\n                set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT + 1, span((uint8_t*)data.data(), data.size()));\n            }, STATUS_IO_REPARSE_TAG_MISMATCH);\n        });\n\n        test(\"Clear archive flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_REPARSE_POINT)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT\", fbi.FileAttributes);\n        });\n\n        test(\"Delete reparse point\", [&]() {\n            delete_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL\", fbi.FileAttributes);\n        });\n\n        test(\"Try to delete reparse point again\", [&]() {\n            exp_status([&]() {\n                delete_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT);\n            }, STATUS_NOT_A_REPARSE_POINT);\n        });\n\n        test(\"Try to query reparse point\", [&]() {\n            exp_status([&]() {\n                query_reparse_point_guid(h.get());\n            }, STATUS_NOT_A_REPARSE_POINT);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\reparse13\", FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_EA,\n                        0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Clear archive flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY\", fbi.FileAttributes);\n        });\n\n        test(\"Set with Microsoft reparse tag\", [&]() {\n            static const string_view data = \"hello\";\n\n            set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT, span((uint8_t*)data.data(), data.size()));\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT))\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT\", fbi.FileAttributes);\n        });\n\n        test(\"Query reparse point\", [&]() {\n            auto buf = query_reparse_point(h.get());\n            auto& rdb = *static_cast<REPARSE_DATA_BUFFER*>(buf);\n\n            if (rdb.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT\", rdb.ReparseTag);\n\n            auto sv = string_view((char*)rdb.GenericReparseBuffer.DataBuffer, rdb.ReparseDataLength);\n\n            if (sv != \"hello\")\n                throw runtime_error(\"Reparse point data was not as expected\");\n        });\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != 0)\n                throw formatted_error(\"EaSize was {:08x}, expected 0\", feai.EaSize);\n        });\n\n        // needs FILE_READ_ATTRIBUTES\n        test(\"Query FileStatInformation\", [&]() {\n            auto fsi = query_information<FILE_STAT_INFORMATION>(h.get());\n\n            if (fsi.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT\", fsi.ReparseTag);\n        });\n\n        // needs FILE_READ_EA as well\n        test(\"Query FileStatLxInformation\", [&]() {\n            auto fsli = query_information<FILE_STAT_LX_INFORMATION>(h.get());\n\n            if (fsli.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT\", fsli.ReparseTag);\n        });\n\n        test(\"Query FileAttributeTagInformation\", [&]() {\n            auto fati = query_information<FILE_ATTRIBUTE_TAG_INFORMATION>(h.get());\n\n            if (fati.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT\", fati.ReparseTag);\n        });\n\n        h.reset();\n    }\n\n    test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"reparse13\", IO_REPARSE_TAG_FAKE_MICROSOFT);\n    });\n\n    test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"reparse13\", IO_REPARSE_TAG_FAKE_MICROSOFT);\n    });\n\n    test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"reparse13\", IO_REPARSE_TAG_FAKE_MICROSOFT);\n    });\n\n    test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"reparse13\", IO_REPARSE_TAG_FAKE_MICROSOFT);\n    });\n\n    test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"reparse13\", IO_REPARSE_TAG_FAKE_MICROSOFT);\n    });\n\n    test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"reparse13\", IO_REPARSE_TAG_FAKE_MICROSOFT);\n    });\n\n    test(\"Open directory without FILE_OPEN_REPARSE_POINT\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\reparse13\", MAXIMUM_ALLOWED,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n        }, STATUS_IO_REPARSE_TAG_NOT_HANDLED);\n    });\n\n    test(\"Open directory with FILE_OPEN_REPARSE_POINT\", [&]() {\n        create_file(dir + u\"\\\\reparse13\", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,\n                    0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse14\", FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_EA,\n                        0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            file1id = fii.IndexNumber.QuadPart;\n        });\n\n        test(\"Clear archive flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL\", fbi.FileAttributes);\n        });\n\n        test(\"Try to set with generic reparse tag without GUID\", [&]() {\n            exp_status([&]() {\n                static const string_view data = \"hello\";\n\n                set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE, span((uint8_t*)data.data(), data.size()));\n            }, STATUS_IO_REPARSE_DATA_INVALID);\n        });\n\n        test(\"Set with generic reparse tag\", [&]() {\n            static const string_view data = \"hello\";\n\n            set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid, span((uint8_t*)data.data(), data.size()));\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_REPARSE_POINT)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT\", fbi.FileAttributes);\n        });\n\n        test(\"Query reparse point\", [&]() {\n            auto buf = query_reparse_point_guid(h.get());\n            auto& rgdb = *static_cast<REPARSE_GUID_DATA_BUFFER*>(buf);\n\n            if (rgdb.ReparseTag != IO_REPARSE_TAG_FAKE)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE\", rgdb.ReparseTag);\n\n            if (memcmp(&rgdb.ReparseGuid, reparse_guid, sizeof(rgdb.ReparseGuid)))\n                throw runtime_error(\"Returned GUID was not as expected\");\n\n            auto sv = string_view((char*)rgdb.GenericReparseBuffer.DataBuffer, rgdb.ReparseDataLength);\n\n            if (sv != \"hello\")\n                throw runtime_error(\"Reparse point data was not as expected\");\n        });\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != 0)\n                throw formatted_error(\"EaSize was {:08x}, expected 0\", feai.EaSize);\n        });\n\n        // needs FILE_READ_ATTRIBUTES\n        test(\"Query FileStatInformation\", [&]() {\n            auto fsi = query_information<FILE_STAT_INFORMATION>(h.get());\n\n            if (fsi.ReparseTag != IO_REPARSE_TAG_FAKE)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE\", fsi.ReparseTag);\n        });\n\n        // needs FILE_READ_EA as well\n        test(\"Query FileStatLxInformation\", [&]() {\n            auto fsli = query_information<FILE_STAT_LX_INFORMATION>(h.get());\n\n            if (fsli.ReparseTag != IO_REPARSE_TAG_FAKE)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE\", fsli.ReparseTag);\n        });\n\n        test(\"Query FileAttributeTagInformation\", [&]() {\n            auto fati = query_information<FILE_ATTRIBUTE_TAG_INFORMATION>(h.get());\n\n            if (fati.ReparseTag != IO_REPARSE_TAG_FAKE)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE\", fati.ReparseTag);\n        });\n\n        h.reset();\n    }\n\n    test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"reparse14\", IO_REPARSE_TAG_FAKE);\n    });\n\n    test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"reparse14\", IO_REPARSE_TAG_FAKE);\n    });\n\n    test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"reparse14\", IO_REPARSE_TAG_FAKE);\n    });\n\n    test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"reparse14\", IO_REPARSE_TAG_FAKE);\n    });\n\n    test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"reparse14\", IO_REPARSE_TAG_FAKE);\n    });\n\n    test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"reparse14\", IO_REPARSE_TAG_FAKE);\n    });\n\n    test(\"Open file without FILE_OPEN_REPARSE_POINT\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\reparse14\", MAXIMUM_ALLOWED,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n        }, STATUS_IO_REPARSE_TAG_NOT_HANDLED);\n    });\n\n    test(\"Open file with FILE_OPEN_REPARSE_POINT\", [&]() {\n        h = create_file(dir + u\"\\\\reparse14\", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,\n                        0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            if (fii.IndexNumber.QuadPart != file1id)\n                throw runtime_error(\"File ID was not as expected.\");\n        });\n\n        test(\"Set with same reparse tag\", [&]() {\n            static const string_view data = \"world\";\n\n            set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid, span((uint8_t*)data.data(), data.size()));\n        });\n\n        test(\"Set with same reparse tag but wrong GUID\", [&]() {\n            exp_status([&]() {\n                static const string_view data = \"world\";\n\n                set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, wrong_guid, span((uint8_t*)data.data(), data.size()));\n            }, STATUS_REPARSE_ATTRIBUTE_CONFLICT);\n        });\n\n        test(\"Try to set with different reparse tag\", [&]() {\n            exp_status([&]() {\n                static const string_view data = \"world\";\n\n                set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE + 1, reparse_guid, span((uint8_t*)data.data(), data.size()));\n            }, STATUS_IO_REPARSE_TAG_MISMATCH);\n        });\n\n        test(\"Clear archive flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_REPARSE_POINT)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_REPARSE_POINT\", fbi.FileAttributes);\n        });\n\n        test(\"Try to delete reparse point without GUID\", [&]() {\n            exp_status([&]() {\n                delete_reparse_point(h.get(), IO_REPARSE_TAG_FAKE);\n            }, STATUS_IO_REPARSE_DATA_INVALID);\n        });\n\n        test(\"Try to delete reparse point with wrong GUID\", [&]() {\n            exp_status([&]() {\n                delete_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, wrong_guid);\n            }, STATUS_REPARSE_ATTRIBUTE_CONFLICT);\n        });\n\n        test(\"Delete reparse point with correct GUID\", [&]() {\n            delete_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_NORMAL)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_NORMAL\", fbi.FileAttributes);\n        });\n\n        test(\"Try to delete reparse point again\", [&]() {\n            exp_status([&]() {\n                delete_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid);\n            }, STATUS_NOT_A_REPARSE_POINT);\n        });\n\n        test(\"Try to query reparse point\", [&]() {\n            exp_status([&]() {\n                query_reparse_point_guid(h.get());\n            }, STATUS_NOT_A_REPARSE_POINT);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\reparse15\", FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_READ_EA,\n                        0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Clear archive flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY\", fbi.FileAttributes);\n        });\n\n        test(\"Set with generic reparse tag\", [&]() {\n            static const string_view data = \"hello\";\n\n            set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid, span((uint8_t*)data.data(), data.size()));\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT))\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT\", fbi.FileAttributes);\n        });\n\n        test(\"Query reparse point\", [&]() {\n            auto buf = query_reparse_point_guid(h.get());\n            auto& rgdb = *static_cast<REPARSE_GUID_DATA_BUFFER*>(buf);\n\n            if (rgdb.ReparseTag != IO_REPARSE_TAG_FAKE)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE\", rgdb.ReparseTag);\n\n            if (memcmp(&rgdb.ReparseGuid, reparse_guid, sizeof(rgdb.ReparseGuid)))\n                throw runtime_error(\"Returned GUID was not as expected\");\n\n            auto sv = string_view((char*)rgdb.GenericReparseBuffer.DataBuffer, rgdb.ReparseDataLength);\n\n            if (sv != \"hello\")\n                throw runtime_error(\"Reparse point data was not as expected\");\n        });\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != 0)\n                throw formatted_error(\"EaSize was {:08x}, expected 0\", feai.EaSize);\n        });\n\n        // needs FILE_READ_ATTRIBUTES\n        test(\"Query FileStatInformation\", [&]() {\n            auto fsi = query_information<FILE_STAT_INFORMATION>(h.get());\n\n            if (fsi.ReparseTag != IO_REPARSE_TAG_FAKE)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE\", fsi.ReparseTag);\n        });\n\n        // needs FILE_READ_EA as well\n        test(\"Query FileStatLxInformation\", [&]() {\n            auto fsli = query_information<FILE_STAT_LX_INFORMATION>(h.get());\n\n            if (fsli.ReparseTag != IO_REPARSE_TAG_FAKE)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE\", fsli.ReparseTag);\n        });\n\n        test(\"Query FileAttributeTagInformation\", [&]() {\n            auto fati = query_information<FILE_ATTRIBUTE_TAG_INFORMATION>(h.get());\n\n            if (fati.ReparseTag != IO_REPARSE_TAG_FAKE)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE\", fati.ReparseTag);\n        });\n\n        h.reset();\n    }\n\n    test(\"Check directory entry (FILE_FULL_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_FULL_DIR_INFORMATION>(dir, u\"reparse15\", IO_REPARSE_TAG_FAKE);\n    });\n\n    test(\"Check directory entry (FILE_ID_FULL_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_FULL_DIR_INFORMATION>(dir, u\"reparse15\", IO_REPARSE_TAG_FAKE);\n    });\n\n    test(\"Check directory entry (FILE_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_BOTH_DIR_INFORMATION>(dir, u\"reparse15\", IO_REPARSE_TAG_FAKE);\n    });\n\n    test(\"Check directory entry (FILE_ID_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_BOTH_DIR_INFORMATION>(dir, u\"reparse15\", IO_REPARSE_TAG_FAKE);\n    });\n\n    test(\"Check directory entry (FILE_ID_EXTD_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_EXTD_DIR_INFORMATION>(dir, u\"reparse15\", IO_REPARSE_TAG_FAKE);\n    });\n\n    test(\"Check directory entry (FILE_ID_EXTD_BOTH_DIR_INFORMATION)\", [&]() {\n        check_reparse_dirent<FILE_ID_EXTD_BOTH_DIR_INFORMATION>(dir, u\"reparse15\", IO_REPARSE_TAG_FAKE);\n    });\n\n    test(\"Open directory without FILE_OPEN_REPARSE_POINT\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\reparse15\", MAXIMUM_ALLOWED,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n        }, STATUS_IO_REPARSE_TAG_NOT_HANDLED);\n    });\n\n    test(\"Open directory with FILE_OPEN_REPARSE_POINT\", [&]() {\n        create_file(dir + u\"\\\\reparse15\", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES,\n                    0, 0, FILE_OPEN, FILE_OPEN_REPARSE_POINT, FILE_OPENED);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\reparse16\", FILE_READ_ATTRIBUTES,\n                        0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to set as symlink without permissions\", [&]() {\n            exp_status([&]() {\n                set_symlink(h.get(), u\"reparse1\", u\"reparse1\", true);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file with FILE_WRITE_ATTRIBUTES\", [&]() {\n        h = create_file(dir + u\"\\\\reparse16\", FILE_WRITE_ATTRIBUTES,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Set as symlink\", [&]() {\n            set_symlink(h.get(), u\"reparse1\", u\"reparse1\", true);\n        });\n\n        test(\"Delete reparse point\", [&]() {\n            delete_reparse_point(h.get(), IO_REPARSE_TAG_SYMLINK);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file with FILE_WRITE_DATA\", [&]() {\n        h = create_file(dir + u\"\\\\reparse16\", FILE_WRITE_DATA,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Set as symlink\", [&]() {\n            set_symlink(h.get(), u\"reparse1\", u\"reparse1\", true);\n        });\n\n        test(\"Delete reparse point\", [&]() {\n            delete_reparse_point(h.get(), IO_REPARSE_TAG_SYMLINK);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        create_file(dir + u\"\\\\reparse17\", MAXIMUM_ALLOWED,\n                    0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create file within directory\", [&]() {\n        create_file(dir + u\"\\\\reparse17\\\\file\", MAXIMUM_ALLOWED,\n                    0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    test(\"Open directory\", [&]() {\n        h = create_file(dir + u\"\\\\reparse17\", FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES | FILE_READ_EA,\n                        0, 0, FILE_OPEN, FILE_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Clear archive flag\", [&]() {\n            set_basic_information(h.get(), 0, 0, 0, 0, FILE_ATTRIBUTE_NORMAL);\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_DIRECTORY)\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY\", fbi.FileAttributes);\n        });\n\n        // This works because the reparse tag has the \"D bit\" (0x10000000) set\n        test(\"Create reparse point on non-empty directory\", [&]() {\n            static const string_view data = \"hello\";\n\n            set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT_DIR, span((uint8_t*)data.data(), data.size()));\n        });\n\n        test(\"Query attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT))\n                throw formatted_error(\"FileAttributes was {:x}, expected FILE_ATTRIBUTE_DIRECTORY | FILE_ATTRIBUTE_REPARSE_POINT\", fbi.FileAttributes);\n        });\n\n        test(\"Query reparse point\", [&]() {\n            auto buf = query_reparse_point(h.get());\n            auto& rdb = *static_cast<REPARSE_DATA_BUFFER*>(buf);\n\n            if (rdb.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT_DIR)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT_DIR\", rdb.ReparseTag);\n\n            auto sv = string_view((char*)rdb.GenericReparseBuffer.DataBuffer, rdb.ReparseDataLength);\n\n            if (sv != \"hello\")\n                throw runtime_error(\"Reparse point data was not as expected\");\n        });\n\n        test(\"Query FileEaInformation\", [&]() {\n            auto feai = query_information<FILE_EA_INFORMATION>(h.get());\n\n            if (feai.EaSize != 0)\n                throw formatted_error(\"EaSize was {:08x}, expected 0\", feai.EaSize);\n        });\n\n        // needs FILE_READ_ATTRIBUTES\n        test(\"Query FileStatInformation\", [&]() {\n            auto fsi = query_information<FILE_STAT_INFORMATION>(h.get());\n\n            if (fsi.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT_DIR)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT_DIR\", fsi.ReparseTag);\n        });\n\n        // needs FILE_READ_EA as well\n        test(\"Query FileStatLxInformation\", [&]() {\n            auto fsli = query_information<FILE_STAT_LX_INFORMATION>(h.get());\n\n            if (fsli.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT_DIR)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT_DIR\", fsli.ReparseTag);\n        });\n\n        test(\"Query FileAttributeTagInformation\", [&]() {\n            auto fati = query_information<FILE_ATTRIBUTE_TAG_INFORMATION>(h.get());\n\n            if (fati.ReparseTag != IO_REPARSE_TAG_FAKE_MICROSOFT_DIR)\n                throw formatted_error(\"ReparseTag was {:08x}, expected IO_REPARSE_TAG_FAKE_MICROSOFT_DIR\", fati.ReparseTag);\n        });\n\n        h.reset();\n    }\n\n    // Succeeds rather than returning STATUS_IO_REPARSE_TAG_NOT_HANDLED, because of D bit\n    test(\"Open directory without FILE_OPEN_REPARSE_POINT\", [&]() {\n        create_file(dir + u\"\\\\reparse17\", FILE_READ_ATTRIBUTES,\n                    0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    test(\"Create stream\", [&]() {\n        h = create_file(dir + u\"\\\\reparse18:stream\", FILE_WRITE_ATTRIBUTES,\n                        0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set as symlink\", [&]() {\n            set_symlink(h.get(), u\"reparse1\", u\"reparse1\", true);\n        });\n\n        test(\"Delete reparse point\", [&]() {\n            delete_reparse_point(h.get(), IO_REPARSE_TAG_SYMLINK);\n        });\n\n        test(\"Try to set as mount point\", [&]() {\n            exp_status([&]() {\n                set_mount_point(h.get(), u\"reparse1\", u\"reparse1\");\n            }, STATUS_NOT_A_DIRECTORY);\n        });\n\n        test(\"Set with Microsoft reparse tag\", [&]() {\n            static const string_view data = \"hello\";\n\n            set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT, span((uint8_t*)data.data(), data.size()));\n        });\n\n        test(\"Delete reparse point\", [&]() {\n            delete_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT);\n        });\n\n        test(\"Set with generic reparse tag\", [&]() {\n            static const string_view data = \"hello\";\n\n            set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid, span((uint8_t*)data.data(), data.size()));\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file without FILE_OPEN_REPARSE_POINT\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\reparse18\", MAXIMUM_ALLOWED,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n        }, STATUS_IO_REPARSE_TAG_NOT_HANDLED);\n    });\n\n    test(\"Open stream without FILE_OPEN_REPARSE_POINT\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\reparse18:stream\", MAXIMUM_ALLOWED,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n        }, STATUS_IO_REPARSE_TAG_NOT_HANDLED);\n    });\n\n    test(\"Create directory\", [&]() {\n        create_file(dir + u\"\\\\reparse19\", FILE_WRITE_ATTRIBUTES,\n                    0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create stream on directory\", [&]() {\n        h = create_file(dir + u\"\\\\reparse19:stream\", FILE_WRITE_ATTRIBUTES,\n                        0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Set as symlink\", [&]() {\n            set_symlink(h.get(), u\"reparse1\", u\"reparse1\", true);\n        });\n\n        test(\"Delete reparse point\", [&]() {\n            delete_reparse_point(h.get(), IO_REPARSE_TAG_SYMLINK);\n        });\n\n        test(\"Try to set as mount point\", [&]() {\n            exp_status([&]() {\n                set_mount_point(h.get(), u\"reparse1\", u\"reparse1\");\n            }, STATUS_NOT_A_DIRECTORY);\n        });\n\n        test(\"Set with Microsoft reparse tag\", [&]() {\n            static const string_view data = \"hello\";\n\n            set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT, span((uint8_t*)data.data(), data.size()));\n        });\n\n        test(\"Delete reparse point\", [&]() {\n            delete_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT);\n        });\n\n        test(\"Set with generic reparse tag\", [&]() {\n            static const string_view data = \"hello\";\n\n            set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid, span((uint8_t*)data.data(), data.size()));\n        });\n\n        h.reset();\n    }\n\n    test(\"Open directory without FILE_OPEN_REPARSE_POINT\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\reparse19\", MAXIMUM_ALLOWED,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n        }, STATUS_IO_REPARSE_TAG_NOT_HANDLED);\n    });\n\n    test(\"Open stream without FILE_OPEN_REPARSE_POINT\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\reparse19:stream\", MAXIMUM_ALLOWED,\n                        0, 0, FILE_OPEN, 0, FILE_OPENED);\n        }, STATUS_IO_REPARSE_TAG_NOT_HANDLED);\n    });\n\n    // disable SeCreateSymbolicLinkPrivilege\n    disable_token_privileges(token);\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\reparse20\", FILE_WRITE_ATTRIBUTES,\n                        0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to set as symlink without SeCreateSymbolicLinkPrivilege\", [&]() {\n            exp_status([&]() {\n                set_symlink(h.get(), u\"reparse1\", u\"reparse1\", true);\n            }, STATUS_PRIVILEGE_NOT_HELD);\n        });\n\n        test(\"Set as mount point without SeCreateSymbolicLinkPrivilege\", [&]() {\n            set_mount_point(h.get(), u\"reparse1\", u\"reparse1\");\n        });\n\n        test(\"Delete reparse point\", [&]() {\n            delete_reparse_point(h.get(), IO_REPARSE_TAG_MOUNT_POINT);\n        });\n\n        test(\"Set with Microsoft reparse tag\", [&]() {\n            static const string_view data = \"hello\";\n\n            set_ms_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT, span((uint8_t*)data.data(), data.size()));\n        });\n\n        test(\"Delete reparse point\", [&]() {\n            delete_reparse_point(h.get(), IO_REPARSE_TAG_FAKE_MICROSOFT);\n        });\n\n        test(\"Set with generic reparse tag\", [&]() {\n            static const string_view data = \"hello\";\n\n            set_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid, span((uint8_t*)data.data(), data.size()));\n        });\n\n        test(\"Delete reparse point\", [&]() {\n            delete_reparse_point_guid(h.get(), IO_REPARSE_TAG_FAKE, reparse_guid);\n        });\n\n        h.reset();\n    }\n\n    // FIXME - FSCTL_SET_REPARSE_POINT_EX\n}\n"
  },
  {
    "path": "src/tests/security.cpp",
    "content": "#include \"test.h\"\n#include <array>\n\nusing namespace std;\n\n#ifdef _MSC_VER\n#define ThreadImpersonationToken ((THREADINFOCLASS)5)\n#endif\n\n// S-1-1-0\nstatic const uint8_t sid_everyone[] = { 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01,\n                                        0x00, 0x00, 0x00, 0x00 };\n\n// S-1-5-21-2463132441-2848149277-1773138504-1001\nstatic const uint8_t sid_test[] = { 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,\n                                    0x15, 0x00, 0x00, 0x00, 0x19, 0x6b, 0xd0, 0x92,\n                                    0x1d, 0x4f, 0xc3, 0xa9, 0x48, 0xf2, 0xaf, 0x69,\n                                    0xe9, 0x03, 0x00, 0x00 };\n\n// S-1-5-21-2463132441-2848149277-1773138504-2001\nstatic const uint8_t sid_test2[] = { 0x01, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05,\n                                     0x15, 0x00, 0x00, 0x00, 0x19, 0x6b, 0xd0, 0x92,\n                                     0x1d, 0x4f, 0xc3, 0xa9, 0x48, 0xf2, 0xaf, 0x69,\n                                     0xd1, 0x07, 0x00, 0x00 };\n\n// S-1-16-12288\nstatic const uint8_t sid_high[] = { 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\n                                    0x00, 0x30, 0x00, 0x00 };\n\n// S-1-16-8192\nstatic const uint8_t sid_medium[] = { 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10,\n                                      0x00, 0x20, 0x00, 0x00 };\n\nstatic unique_handle create_file_sd(u16string_view path, ACCESS_MASK access, ULONG atts, ULONG share,\n                                    ULONG dispo, ULONG options, ULONG_PTR exp_info, const SECURITY_DESCRIPTOR& sd) {\n    NTSTATUS Status;\n    HANDLE h;\n    IO_STATUS_BLOCK iosb;\n    UNICODE_STRING us;\n    OBJECT_ATTRIBUTES oa;\n\n    memset(&oa, 0, sizeof(oa));\n    oa.Length = sizeof(oa);\n    oa.RootDirectory = nullptr;\n\n    us.Length = us.MaximumLength = path.length() * sizeof(char16_t);\n    us.Buffer = (WCHAR*)path.data();\n    oa.ObjectName = &us;\n\n    oa.Attributes = OBJ_CASE_INSENSITIVE;\n    oa.SecurityDescriptor = (void*)&sd;\n    oa.SecurityQualityOfService = nullptr;\n\n    iosb.Information = 0xdeadbeef;\n\n    Status = NtCreateFile(&h, access, &oa, &iosb, nullptr,\n                          atts, share, dispo, options, nullptr, 0);\n\n    if (Status != STATUS_SUCCESS) {\n        if (NT_SUCCESS(Status)) // STATUS_OPLOCK_BREAK_IN_PROGRESS etc.\n            NtClose(h);\n\n        throw ntstatus_error(Status);\n    }\n\n    if (iosb.Information != exp_info)\n        throw formatted_error(\"iosb.Information was {}, expected {}\", iosb.Information, exp_info);\n\n    return unique_handle(h);\n}\n\nstatic unique_handle create_file_with_acl(u16string_view path, ACCESS_MASK access, ULONG atts, ULONG share,\n                                          ULONG dispo, ULONG options, ULONG_PTR exp_info, ACCESS_MASK ace_access,\n                                          uint8_t ace_flags) {\n    SECURITY_DESCRIPTOR sd;\n    array<uint8_t, sizeof(ACL) + offsetof(ACCESS_ALLOWED_ACE, SidStart) + sizeof(sid_everyone)> aclbuf;\n\n    if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))\n        throw formatted_error(\"InitializeSecurityDescriptor failed (error {})\", GetLastError());\n\n    auto& acl = *(ACL*)aclbuf.data();\n\n    if (!InitializeAcl(&acl, aclbuf.size(), ACL_REVISION))\n        throw formatted_error(\"InitializeAcl failed (error {})\", GetLastError());\n\n    acl.AceCount = 1;\n\n    auto& ace = *(ACCESS_ALLOWED_ACE*)((uint8_t*)aclbuf.data() + sizeof(ACL));\n\n    ace.Header.AceType = ACCESS_ALLOWED_ACE_TYPE;\n    ace.Header.AceFlags = ace_flags;\n    ace.Header.AceSize = offsetof(ACCESS_ALLOWED_ACE, SidStart) + sizeof(sid_everyone);\n    ace.Mask = ace_access;\n    memcpy(&ace.SidStart, sid_everyone, sizeof(sid_everyone));\n\n    if (!SetSecurityDescriptorDacl(&sd, true, &acl, false))\n        throw formatted_error(\"SetSecurityDescriptorDacl failed (error {})\", GetLastError());\n\n    return create_file_sd(path, access, atts, share, dispo, options, exp_info, sd);\n}\n\nvoid set_dacl(HANDLE h, ACCESS_MASK access) {\n    NTSTATUS Status;\n    SECURITY_DESCRIPTOR sd;\n    array<uint8_t, sizeof(ACL) + offsetof(ACCESS_ALLOWED_ACE, SidStart) + sizeof(sid_everyone)> aclbuf;\n\n    if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))\n        throw formatted_error(\"InitializeSecurityDescriptor failed (error {})\", GetLastError());\n\n    auto& acl = *(ACL*)aclbuf.data();\n\n    if (!InitializeAcl(&acl, aclbuf.size(), ACL_REVISION))\n        throw formatted_error(\"InitializeAcl failed (error {})\", GetLastError());\n\n    if (access != 0) {\n        acl.AceCount = 1;\n\n        auto& ace = *(ACCESS_ALLOWED_ACE*)((uint8_t*)aclbuf.data() + sizeof(ACL));\n\n        ace.Header.AceType = ACCESS_ALLOWED_ACE_TYPE;\n        ace.Header.AceFlags = 0;\n        ace.Header.AceSize = offsetof(ACCESS_ALLOWED_ACE, SidStart) + sizeof(sid_everyone);\n        ace.Mask = access;\n        memcpy(&ace.SidStart, sid_everyone, sizeof(sid_everyone));\n    }\n\n    if (!SetSecurityDescriptorDacl(&sd, true, &acl, false))\n        throw formatted_error(\"SetSecurityDescriptorDacl failed (error {})\", GetLastError());\n\n    Status = NtSetSecurityObject(h, DACL_SECURITY_INFORMATION, &sd);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic vector<varbuf<ACE_HEADER>> get_acl(HANDLE h, unsigned int type) {\n    NTSTATUS Status;\n    ULONG needed = 0;\n    vector<uint8_t> buf;\n    vector<varbuf<ACE_HEADER>> ret;\n\n    Status = NtQuerySecurityObject(h, type, nullptr, 0, &needed);\n\n    if (Status != STATUS_BUFFER_TOO_SMALL)\n        throw ntstatus_error(Status);\n\n    buf.resize(needed);\n\n    Status = NtQuerySecurityObject(h, type, buf.data(), buf.size(), &needed);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (buf.size() < sizeof(SECURITY_DESCRIPTOR_RELATIVE))\n        throw formatted_error(\"SD was {} bytes, expected at least {}\", buf.size(), sizeof(SECURITY_DESCRIPTOR_RELATIVE));\n\n    auto& sd = *(SECURITY_DESCRIPTOR_RELATIVE*)buf.data();\n\n    if (sd.Revision != 1)\n        throw formatted_error(\"SD revision was {}, expected 1\", sd.Revision);\n\n    auto off = type == DACL_SECURITY_INFORMATION ? sd.Dacl : sd.Sacl;\n\n    if (off == 0)\n        return {};\n\n    if (off + sizeof(ACL) > buf.size())\n        throw runtime_error(\"ACL extended beyond end of SD\");\n\n    auto& acl = *(ACL*)(buf.data() + off);\n\n    if (acl.AclRevision != ACL_REVISION)\n        throw formatted_error(\"ACL revision was {}, expected {}\", acl.AclRevision, ACL_REVISION);\n\n    if (acl.AclSize < sizeof(ACL))\n        throw formatted_error(\"ACL size was {}, expected at least {}\", acl.AclSize, sizeof(ACL));\n\n    ret.resize(acl.AceCount);\n\n    auto aclsp = span<const uint8_t>((uint8_t*)&acl + sizeof(ACL), acl.AclSize - sizeof(ACL));\n\n    for (unsigned int i = 0; i < acl.AceCount; i++) {\n        auto& ace = *(ACE_HEADER*)aclsp.data();\n\n        if (aclsp.size() < sizeof(ACE_HEADER))\n            throw formatted_error(\"Not enough bytes left for ACE ({} < {})\", aclsp.size(), sizeof(ACE_HEADER));\n\n        if (aclsp.size() < ace.AceSize)\n            throw formatted_error(\"ACE overflowed end of SD ({} < {})\", aclsp.size(), ace.AceSize);\n\n        auto& b = ret[i].buf;\n\n        b.resize(ace.AceSize);\n        memcpy(b.data(), &ace, ace.AceSize);\n\n        aclsp = aclsp.subspan(ace.AceSize);\n    }\n\n    return ret;\n}\n\nstatic string sid_to_string(span<const uint8_t> sid) {\n    string s;\n    auto& ss = *(SID*)sid.data();\n\n    if (sid.size() < offsetof(SID, SubAuthority) || ss.Revision != SID_REVISION || sid.size() < offsetof(SID, SubAuthority) + (ss.SubAuthorityCount * sizeof(ULONG))) {\n        for (auto b : sid) {\n            if (!s.empty())\n                s += \" \";\n\n            s += std::format(\"{:02x}\", b);\n        }\n\n        return \"Malformed SID (\" + s + \")\";\n    }\n\n    uint64_t auth;\n\n    auth = (uint64_t)sid[2] << 40;\n    auth |= (uint64_t)sid[3] << 32;\n    auth |= (uint64_t)sid[4] << 24;\n    auth |= (uint64_t)sid[5] << 16;\n    auth |= (uint64_t)sid[6] << 8;\n    auth |= sid[7];\n\n    s = std::format(\"S-1-{}\", auth);\n\n    auto sub = span<const ULONG>(ss.SubAuthority, ss.SubAuthorityCount);\n\n    for (auto n : sub) {\n        s += std::format(\"-{}\", n);\n    }\n\n    return s;\n}\n\nstatic bool compare_sid(span<const uint8_t> sid1, span<const uint8_t> sid2) {\n    if (sid1.size() < offsetof(SID, SubAuthority) || sid2.size() < offsetof(SID, SubAuthority))\n        throw runtime_error(\"Malformed SID\");\n\n    auto& ss1 = *(SID*)sid1.data();\n    auto& ss2 = *(SID*)sid2.data();\n\n    if (ss1.Revision != 1 || ss2.Revision != 1)\n        throw runtime_error(\"Unknown SID revision\");\n\n    auto len1 = offsetof(SID, SubAuthority) + (ss1.SubAuthorityCount * sizeof(ULONG));\n    auto len2 = offsetof(SID, SubAuthority) + (ss2.SubAuthorityCount * sizeof(ULONG));\n\n    if (len1 != len2)\n        return false;\n\n    return !memcmp(sid1.data(), sid2.data(), len1);\n}\n\nstatic void set_owner(HANDLE h, span<const uint8_t> sid) {\n    NTSTATUS Status;\n    SECURITY_DESCRIPTOR sd;\n\n    if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))\n        throw formatted_error(\"InitializeSecurityDescriptor failed (error {})\", GetLastError());\n\n    if (!SetSecurityDescriptorOwner(&sd, (PSID)sid.data(), false))\n        throw formatted_error(\"SetSecurityDescriptorOwner failed (error {})\", GetLastError());\n\n    Status = NtSetSecurityObject(h, OWNER_SECURITY_INFORMATION, &sd);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic vector<uint8_t> get_owner(HANDLE h) {\n    NTSTATUS Status;\n    ULONG needed = 0;\n    vector<uint8_t> buf;\n\n    Status = NtQuerySecurityObject(h, OWNER_SECURITY_INFORMATION, nullptr, 0, &needed);\n\n    if (Status != STATUS_BUFFER_TOO_SMALL)\n        throw ntstatus_error(Status);\n\n    buf.resize(needed);\n\n    Status = NtQuerySecurityObject(h, OWNER_SECURITY_INFORMATION, buf.data(), buf.size(), &needed);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (buf.size() < sizeof(SECURITY_DESCRIPTOR_RELATIVE))\n        throw formatted_error(\"SD was {} bytes, expected at least {}\", buf.size(), sizeof(SECURITY_DESCRIPTOR_RELATIVE));\n\n    auto& sd = *(SECURITY_DESCRIPTOR_RELATIVE*)buf.data();\n\n    if (sd.Revision != 1)\n        throw formatted_error(\"SD revision was {}, expected 1\", sd.Revision);\n\n    if (sd.Owner == 0)\n        throw runtime_error(\"No owner returned\");\n\n    if (sd.Owner + offsetof(SID, SubAuthority) > buf.size())\n        throw runtime_error(\"SID extended beyond end of SD\");\n\n    auto& sid = *(SID*)(buf.data() + sd.Owner);\n\n    if (sid.Revision != SID_REVISION)\n        throw formatted_error(\"SID revision was {}, expected {}\", sid.Revision, SID_REVISION);\n\n    auto sp = span(buf.data() + sd.Owner, offsetof(SID, SubAuthority) + (sizeof(ULONG) * sid.SubAuthorityCount));\n\n    vector<uint8_t> ret;\n\n    ret.resize(sp.size());\n    memcpy(ret.data(), sp.data(), sp.size());\n\n    return ret;\n}\n\nstatic void set_group(HANDLE h, span<const uint8_t> sid) {\n    NTSTATUS Status;\n    SECURITY_DESCRIPTOR sd;\n\n    if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))\n        throw formatted_error(\"InitializeSecurityDescriptor failed (error {})\", GetLastError());\n\n    if (!SetSecurityDescriptorGroup(&sd, (PSID)sid.data(), false))\n        throw formatted_error(\"SetSecurityDescriptorGroup failed (error {})\", GetLastError());\n\n    Status = NtSetSecurityObject(h, GROUP_SECURITY_INFORMATION, &sd);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic vector<uint8_t> get_group(HANDLE h) {\n    NTSTATUS Status;\n    ULONG needed = 0;\n    vector<uint8_t> buf;\n\n    Status = NtQuerySecurityObject(h, GROUP_SECURITY_INFORMATION, nullptr, 0, &needed);\n\n    if (Status != STATUS_BUFFER_TOO_SMALL)\n        throw ntstatus_error(Status);\n\n    buf.resize(needed);\n\n    Status = NtQuerySecurityObject(h, GROUP_SECURITY_INFORMATION, buf.data(), buf.size(), &needed);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (buf.size() < sizeof(SECURITY_DESCRIPTOR_RELATIVE))\n        throw formatted_error(\"SD was {} bytes, expected at least {}\", buf.size(), sizeof(SECURITY_DESCRIPTOR_RELATIVE));\n\n    auto& sd = *(SECURITY_DESCRIPTOR_RELATIVE*)buf.data();\n\n    if (sd.Revision != 1)\n        throw formatted_error(\"SD revision was {}, expected 1\", sd.Revision);\n\n    if (sd.Group == 0)\n        throw runtime_error(\"No group returned\");\n\n    if (sd.Group + offsetof(SID, SubAuthority) > buf.size())\n        throw runtime_error(\"SID extended beyond end of SD\");\n\n    auto& sid = *(SID*)(buf.data() + sd.Group);\n\n    if (sid.Revision != SID_REVISION)\n        throw formatted_error(\"SID revision was {}, expected {}\", sid.Revision, SID_REVISION);\n\n    auto sp = span(buf.data() + sd.Group, offsetof(SID, SubAuthority) + (sizeof(ULONG) * sid.SubAuthorityCount));\n\n    vector<uint8_t> ret;\n\n    ret.resize(sp.size());\n    memcpy(ret.data(), sp.data(), sp.size());\n\n    return ret;\n}\n\ntemplate<size_t N>\nstatic void set_audit(HANDLE h, ACCESS_MASK access, span<const uint8_t, N> sid) {\n    NTSTATUS Status;\n    SECURITY_DESCRIPTOR sd;\n    array<uint8_t, sizeof(ACL) + offsetof(SYSTEM_AUDIT_ACE, SidStart) + sid.size()> aclbuf;\n\n    if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))\n        throw formatted_error(\"InitializeSecurityDescriptor failed (error {})\", GetLastError());\n\n    auto& acl = *(ACL*)aclbuf.data();\n\n    if (!InitializeAcl(&acl, aclbuf.size(), ACL_REVISION))\n        throw formatted_error(\"InitializeAcl failed (error {})\", GetLastError());\n\n    if (access != 0) {\n        acl.AceCount = 1;\n\n        auto& ace = *(SYSTEM_AUDIT_ACE*)((uint8_t*)aclbuf.data() + sizeof(ACL));\n\n        ace.Header.AceType = SYSTEM_AUDIT_ACE_TYPE;\n        ace.Header.AceFlags = 0;\n        ace.Header.AceSize = offsetof(SYSTEM_AUDIT_ACE, SidStart) + sid.size();\n        ace.Mask = access;\n        memcpy(&ace.SidStart, sid.data(), sid.size());\n    }\n\n    if (!SetSecurityDescriptorSacl(&sd, true, &acl, false))\n        throw formatted_error(\"SetSecurityDescriptorSacl failed (error {})\", GetLastError());\n\n    Status = NtSetSecurityObject(h, SACL_SECURITY_INFORMATION, &sd);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\ntemplate<size_t N>\nstatic void set_mandatory_access(HANDLE h, ACCESS_MASK access, span<const uint8_t, N> sid) {\n    NTSTATUS Status;\n    SECURITY_DESCRIPTOR sd;\n    array<uint8_t, sizeof(ACL) + offsetof(SYSTEM_MANDATORY_LABEL_ACE, SidStart) + sid.size()> aclbuf;\n\n    if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))\n        throw formatted_error(\"InitializeSecurityDescriptor failed (error {})\", GetLastError());\n\n    auto& acl = *(ACL*)aclbuf.data();\n\n    if (!InitializeAcl(&acl, aclbuf.size(), ACL_REVISION))\n        throw formatted_error(\"InitializeAcl failed (error {})\", GetLastError());\n\n    if (access != 0) {\n        acl.AceCount = 1;\n\n        auto& ace = *(SYSTEM_MANDATORY_LABEL_ACE*)((uint8_t*)aclbuf.data() + sizeof(ACL));\n\n        ace.Header.AceType = SYSTEM_MANDATORY_LABEL_ACE_TYPE;\n        ace.Header.AceFlags = 0;\n        ace.Header.AceSize = offsetof(SYSTEM_MANDATORY_LABEL_ACE, SidStart) + sid.size();\n        ace.Mask = access;\n        memcpy(&ace.SidStart, sid.data(), sid.size());\n    }\n\n    if (!SetSecurityDescriptorSacl(&sd, true, &acl, false))\n        throw formatted_error(\"SetSecurityDescriptorSacl failed (error {})\", GetLastError());\n\n    Status = NtSetSecurityObject(h, LABEL_SECURITY_INFORMATION, &sd);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic unique_handle duplicate_token(HANDLE token) {\n    NTSTATUS Status;\n    OBJECT_ATTRIBUTES oa;\n    HANDLE h;\n    SECURITY_QUALITY_OF_SERVICE qos;\n\n    memset(&qos, 0, sizeof(qos));\n    qos.Length = sizeof(qos);\n    qos.ImpersonationLevel = SecurityImpersonation;\n\n    memset(&oa, 0, sizeof(oa));\n    oa.Length = sizeof(oa);\n    oa.SecurityQualityOfService = &qos;\n\n    Status = NtDuplicateToken(token, MAXIMUM_ALLOWED, &oa, false,\n                              TokenImpersonation, &h);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    return unique_handle{h};\n}\n\nstatic void adjust_token_level(HANDLE token, const void* sid) {\n    TOKEN_MANDATORY_LABEL label;\n    NTSTATUS Status;\n\n    label.Label.Sid = (PSID)sid;\n    label.Label.Attributes = 0;\n\n    Status = NtSetInformationToken(token, TokenIntegrityLevel, &label, sizeof(label));\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstatic void set_thread_token(HANDLE token) {\n    NTSTATUS Status;\n\n    Status = NtSetInformationThread(NtCurrentThread(), ThreadImpersonationToken,\n                                    &token, sizeof(token));\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nvoid test_security(HANDLE token, const u16string& dir) {\n    unique_handle h, medium_token;\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\sec1\", GENERIC_READ, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Query FileAccessInformation\", [&]() {\n            auto fai = query_information<FILE_ACCESS_INFORMATION>(h.get());\n\n            ACCESS_MASK exp = SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES |\n                              FILE_READ_EA | FILE_READ_DATA;\n\n            if (fai.AccessFlags != exp)\n                throw formatted_error(\"AccessFlags was {:x}, expected {:x}\", fai.AccessFlags, exp);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\sec1\", GENERIC_WRITE, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Query FileAccessInformation\", [&]() {\n            auto fai = query_information<FILE_ACCESS_INFORMATION>(h.get());\n\n            ACCESS_MASK exp = SYNCHRONIZE | READ_CONTROL | FILE_WRITE_ATTRIBUTES |\n                              FILE_WRITE_EA | FILE_APPEND_DATA | FILE_WRITE_DATA;\n\n            if (fai.AccessFlags != exp)\n                throw formatted_error(\"AccessFlags was {:x}, expected {:x}\", fai.AccessFlags, exp);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\sec1\", GENERIC_EXECUTE, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Query FileAccessInformation\", [&]() {\n            auto fai = query_information<FILE_ACCESS_INFORMATION>(h.get());\n\n            ACCESS_MASK exp = SYNCHRONIZE | READ_CONTROL | FILE_READ_ATTRIBUTES |\n                              FILE_EXECUTE;\n\n            if (fai.AccessFlags != exp)\n                throw formatted_error(\"AccessFlags was {:x}, expected {:x}\", fai.AccessFlags, exp);\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\sec1\", READ_CONTROL | WRITE_DAC, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        ACCESS_MASK access = SYNCHRONIZE | WRITE_OWNER | WRITE_DAC | READ_CONTROL | DELETE |\n                             FILE_WRITE_ATTRIBUTES | FILE_READ_ATTRIBUTES | FILE_DELETE_CHILD |\n                             FILE_EXECUTE | FILE_WRITE_EA | FILE_READ_EA | FILE_APPEND_DATA |\n                             FILE_WRITE_DATA | FILE_READ_DATA;\n\n        test(\"Set DACL to maximum for Everyone\", [&]() {\n            set_dacl(h.get(), access);\n        });\n\n        test(\"Query DACL\", [&]() {\n            auto items = get_acl(h.get(), DACL_SECURITY_INFORMATION);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} items returned, expected 1\", items.size());\n\n            auto& ace = *static_cast<ACE_HEADER*>(items.front());\n\n            if (ace.AceType != ACCESS_ALLOWED_ACE_TYPE)\n                throw formatted_error(\"ACE type was {}, expected ACCESS_ALLOWED_ACE_TYPE\", ace.AceType);\n\n            if (ace.AceFlags != 0)\n                throw formatted_error(\"AceFlags was {:x}, expected 0\", ace.AceFlags);\n\n            auto& aaa = *reinterpret_cast<ACCESS_ALLOWED_ACE*>(&ace);\n\n            if (aaa.Mask != access)\n                throw formatted_error(\"Mask was {:x}, expected {:x}\", aaa.Mask, access);\n\n            auto sid = span<const uint8_t>((uint8_t*)&aaa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart));\n\n            if (!compare_sid(sid, sid_everyone))\n                throw formatted_error(\"SID was {}, expected {}\", sid_to_string(sid), sid_to_string(sid_everyone));\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\sec1\", READ_CONTROL | WRITE_OWNER, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Try to set owner without SeRestorePrivilege\", [&]() {\n            exp_status([&]() {\n                set_owner(h.get(), sid_test);\n            }, STATUS_INVALID_OWNER);\n        });\n\n        test(\"Set group\", [&]() {\n            set_group(h.get(), sid_test2);\n        });\n\n        test(\"Query group\", [&]() {\n            auto sid = get_group(h.get());\n\n            if (!compare_sid(sid, sid_test2))\n                throw formatted_error(\"SID was {}, expected {}\", sid_to_string(sid), sid_to_string(sid_test2));\n        });\n\n        h.reset();\n    }\n\n    test(\"Add SeRestorePrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_RESTORE_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\sec1\", READ_CONTROL | WRITE_OWNER, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Set owner\", [&]() {\n            set_owner(h.get(), sid_test);\n        });\n\n        test(\"Query owner\", [&]() {\n            auto sid = get_owner(h.get());\n\n            if (!compare_sid(sid, sid_test))\n                throw formatted_error(\"SID was {}, expected {}\", sid_to_string(sid), sid_to_string(sid_test));\n        });\n\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n\n    test(\"Try to open file with ACCESS_SYSTEM_SECURITY without SeSecurityPrivilege\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\sec1\", ACCESS_SYSTEM_SECURITY, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n        }, STATUS_PRIVILEGE_NOT_HELD);\n    });\n\n    test(\"Add SeSecurityPrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_SECURITY_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\sec1\", ACCESS_SYSTEM_SECURITY, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Set audit\", [&]() {\n            set_audit(h.get(), SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG, span(sid_everyone));\n        });\n\n        test(\"Query SACL\", [&]() {\n            auto items = get_acl(h.get(), SACL_SECURITY_INFORMATION);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} items returned, expected 1\", items.size());\n\n            auto& ace = *static_cast<ACE_HEADER*>(items.front());\n\n            if (ace.AceType != SYSTEM_AUDIT_ACE_TYPE)\n                throw formatted_error(\"ACE type was {}, expected SYSTEM_AUDIT_ACE_TYPE\", ace.AceType);\n\n            if (ace.AceFlags != 0)\n                throw formatted_error(\"AceFlags was {:x}, expected 0\", ace.AceFlags);\n\n            auto& saa = *reinterpret_cast<SYSTEM_AUDIT_ACE*>(&ace);\n\n            if (saa.Mask != (SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG))\n                throw formatted_error(\"Mask was {:x}, expected {:x}\", saa.Mask, SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG);\n\n            auto sid = span<const uint8_t>((uint8_t*)&saa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart));\n\n            if (!compare_sid(sid, sid_everyone))\n                throw formatted_error(\"SID was {}, expected {}\", sid_to_string(sid), sid_to_string(sid_everyone));\n        });\n\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\sec1\", READ_CONTROL | WRITE_OWNER, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Set mandatory access label\", [&]() {\n            set_mandatory_access(h.get(), SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, span(sid_high));\n        });\n\n        test(\"Query label\", [&]() {\n            auto items = get_acl(h.get(), LABEL_SECURITY_INFORMATION);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} items returned, expected 1\", items.size());\n\n            auto& ace = *static_cast<ACE_HEADER*>(items.front());\n\n            if (ace.AceType != SYSTEM_MANDATORY_LABEL_ACE_TYPE)\n                throw formatted_error(\"ACE type was {}, expected SYSTEM_MANDATORY_LABEL_ACE_TYPE\", ace.AceType);\n\n            if (ace.AceFlags != 0)\n                throw formatted_error(\"AceFlags was {:x}, expected 0\", ace.AceFlags);\n\n            auto& smla = *reinterpret_cast<SYSTEM_MANDATORY_LABEL_ACE*>(&ace);\n\n            if (smla.Mask != SYSTEM_MANDATORY_LABEL_NO_WRITE_UP)\n                throw formatted_error(\"Mask was {:x}, expected {:x}\", smla.Mask, SYSTEM_MANDATORY_LABEL_NO_WRITE_UP);\n\n            auto sid = span<const uint8_t>((uint8_t*)&smla.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart));\n\n            if (!compare_sid(sid, sid_high))\n                throw formatted_error(\"SID was {}, expected {}\", sid_to_string(sid), sid_to_string(sid_high));\n        });\n\n        h.reset();\n    }\n\n    test(\"Add SeSecurityPrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_SECURITY_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\sec1\", ACCESS_SYSTEM_SECURITY, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Check SACL still there\", [&]() {\n            auto items = get_acl(h.get(), SACL_SECURITY_INFORMATION);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} items returned, expected 1\", items.size());\n\n            auto& ace = *static_cast<ACE_HEADER*>(items.front());\n\n            if (ace.AceType != SYSTEM_AUDIT_ACE_TYPE)\n                throw formatted_error(\"ACE type was {}, expected SYSTEM_AUDIT_ACE_TYPE\", ace.AceType);\n\n            if (ace.AceFlags != 0)\n                throw formatted_error(\"AceFlags was {:x}, expected 0\", ace.AceFlags);\n\n            auto& saa = *reinterpret_cast<SYSTEM_AUDIT_ACE*>(&ace);\n\n            if (saa.Mask != (SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG))\n                throw formatted_error(\"Mask was {:x}, expected {:x}\", saa.Mask, SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG);\n\n            auto sid = span<const uint8_t>((uint8_t*)&saa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart));\n\n            if (!compare_sid(sid, sid_everyone))\n                throw formatted_error(\"SID was {}, expected {}\", sid_to_string(sid), sid_to_string(sid_everyone));\n        });\n\n        h.reset();\n    }\n\n    disable_token_privileges(token);\n\n    test(\"Duplicate token\", [&]() {\n        medium_token = duplicate_token(token);\n    });\n\n    if (medium_token) {\n        test(\"Adjust token label\", [&]() {\n            adjust_token_level(medium_token.get(), sid_medium);\n        });\n\n        test(\"Switch to new token\", [&]() {\n            set_thread_token(medium_token.get());\n        });\n    }\n\n    test(\"Try to open file for writing with medium label\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\sec1\", FILE_WRITE_DATA, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n        }, STATUS_ACCESS_DENIED);\n    });\n\n    test(\"Open file with MAXIMUM_ALLOWED\", [&]() {\n        h = create_file(dir + u\"\\\\sec1\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN, 0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Query FileAccessInformation\", [&]() {\n            auto fai = query_information<FILE_ACCESS_INFORMATION>(h.get());\n\n            ACCESS_MASK exp = SYNCHRONIZE | READ_CONTROL | DELETE |\n                              FILE_READ_ATTRIBUTES | FILE_EXECUTE |\n                              FILE_READ_EA | FILE_READ_DATA;\n\n            if (fai.AccessFlags != exp)\n                throw formatted_error(\"AccessFlags was {:x}, expected {:x}\", fai.AccessFlags, exp);\n        });\n\n        h.reset();\n    }\n\n    if (medium_token) {\n        test(\"Switch back to old token\", [&]() {\n            set_thread_token(nullptr);\n        });\n\n        medium_token.reset();\n    }\n\n    test(\"Create file with SD\", [&]() {\n        h = create_file_with_acl(dir + u\"\\\\sec2\", READ_CONTROL, 0, 0, FILE_CREATE,\n                                 0, FILE_CREATED, FILE_READ_DATA, 0);\n    });\n\n    if (h) {\n        test(\"Query FileAccessInformation\", [&]() {\n            auto fai = query_information<FILE_ACCESS_INFORMATION>(h.get());\n\n            ACCESS_MASK exp = READ_CONTROL;\n\n            if (fai.AccessFlags != exp)\n                throw formatted_error(\"AccessFlags was {:x}, expected {:x}\", fai.AccessFlags, exp);\n        });\n\n        test(\"Query DACL\", [&]() {\n            auto items = get_acl(h.get(), DACL_SECURITY_INFORMATION);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} items returned, expected 1\", items.size());\n\n            auto& ace = *static_cast<ACE_HEADER*>(items.front());\n\n            if (ace.AceType != ACCESS_ALLOWED_ACE_TYPE)\n                throw formatted_error(\"ACE type was {}, expected ACCESS_ALLOWED_ACE_TYPE\", ace.AceType);\n\n            if (ace.AceFlags != 0)\n                throw formatted_error(\"AceFlags was {:x}, expected 0\", ace.AceFlags);\n\n            auto& aaa = *reinterpret_cast<ACCESS_ALLOWED_ACE*>(&ace);\n\n            if (aaa.Mask != FILE_READ_DATA)\n                throw formatted_error(\"Mask was {:x}, expected FILE_READ_DATA\", aaa.Mask);\n\n            auto sid = span<const uint8_t>((uint8_t*)&aaa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart));\n\n            if (!compare_sid(sid, sid_everyone))\n                throw formatted_error(\"SID was {}, expected {}\", sid_to_string(sid), sid_to_string(sid_everyone));\n        });\n\n        h.reset();\n    }\n\n    test(\"Try to create file with other user as owner\", [&]() {\n        SECURITY_DESCRIPTOR sd;\n\n        if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))\n            throw formatted_error(\"InitializeSecurityDescriptor failed (error {})\", GetLastError());\n\n        if (!SetSecurityDescriptorOwner(&sd, (PSID)sid_test, false))\n            throw formatted_error(\"SetSecurityDescriptorOwner failed (error {})\", GetLastError());\n\n        exp_status([&]() {\n            create_file_sd(dir + u\"\\\\sec3\", READ_CONTROL, 0, 0, FILE_CREATE,\n                           0, FILE_CREATED, sd);\n        }, STATUS_INVALID_OWNER);\n    });\n\n    test(\"Create directory with OBJECT_INHERIT_ACE\", [&]() {\n        create_file_with_acl(dir + u\"\\\\sec4\", READ_CONTROL, 0, 0, FILE_CREATE,\n                             FILE_DIRECTORY_FILE, FILE_CREATED,\n                             FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY,\n                             OBJECT_INHERIT_ACE);\n    });\n\n    // READ_CONTROL gets given because we're the owner\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\sec4\\\\file\", READ_CONTROL, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Query DACL\", [&]() {\n            auto items = get_acl(h.get(), DACL_SECURITY_INFORMATION);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} items returned, expected 1\", items.size());\n\n            auto& ace = *static_cast<ACE_HEADER*>(items.front());\n\n            if (ace.AceType != ACCESS_ALLOWED_ACE_TYPE)\n                throw formatted_error(\"ACE type was {}, expected ACCESS_ALLOWED_ACE_TYPE\", ace.AceType);\n\n            if (ace.AceFlags != 0)\n                throw formatted_error(\"AceFlags was {:x}, expected 0\", ace.AceFlags);\n\n            auto& aaa = *reinterpret_cast<ACCESS_ALLOWED_ACE*>(&ace);\n\n            if (aaa.Mask != (FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY))\n                throw formatted_error(\"Mask was {:x}, expected FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY\", aaa.Mask);\n\n            auto sid = span<const uint8_t>((uint8_t*)&aaa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart));\n\n            if (!compare_sid(sid, sid_everyone))\n                throw formatted_error(\"SID was {}, expected {}\", sid_to_string(sid), sid_to_string(sid_everyone));\n        });\n\n        h.reset();\n    }\n\n    test(\"Create subdirectory\", [&]() {\n        h = create_file(dir + u\"\\\\sec4\\\\dir\", READ_CONTROL, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Query DACL\", [&]() {\n            auto items = get_acl(h.get(), DACL_SECURITY_INFORMATION);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} items returned, expected 1\", items.size());\n\n            auto& ace = *static_cast<ACE_HEADER*>(items.front());\n\n            if (ace.AceType != ACCESS_ALLOWED_ACE_TYPE)\n                throw formatted_error(\"ACE type was {}, expected ACCESS_ALLOWED_ACE_TYPE\", ace.AceType);\n\n            if (ace.AceFlags != (INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE))\n                throw formatted_error(\"AceFlags was {:x}, expected INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE\", ace.AceFlags);\n\n            auto& aaa = *reinterpret_cast<ACCESS_ALLOWED_ACE*>(&ace);\n\n            if (aaa.Mask != (FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY))\n                throw formatted_error(\"Mask was {:x}, expected FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY\", aaa.Mask);\n\n            auto sid = span<const uint8_t>((uint8_t*)&aaa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart));\n\n            if (!compare_sid(sid, sid_everyone))\n                throw formatted_error(\"SID was {}, expected {}\", sid_to_string(sid), sid_to_string(sid_everyone));\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory with CONTAINER_INHERIT_ACE\", [&]() {\n        create_file_with_acl(dir + u\"\\\\sec5\", READ_CONTROL, 0, 0, FILE_CREATE,\n                             FILE_DIRECTORY_FILE, FILE_CREATED,\n                             FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY,\n                             OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\sec5\\\\file\", READ_CONTROL, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Query DACL\", [&]() {\n            auto items = get_acl(h.get(), DACL_SECURITY_INFORMATION);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} items returned, expected 1\", items.size());\n\n            auto& ace = *static_cast<ACE_HEADER*>(items.front());\n\n            if (ace.AceType != ACCESS_ALLOWED_ACE_TYPE)\n                throw formatted_error(\"ACE type was {}, expected ACCESS_ALLOWED_ACE_TYPE\", ace.AceType);\n\n            if (ace.AceFlags != 0)\n                throw formatted_error(\"AceFlags was {:x}, expected 0\", ace.AceFlags);\n\n            auto& aaa = *reinterpret_cast<ACCESS_ALLOWED_ACE*>(&ace);\n\n            if (aaa.Mask != (FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY))\n                throw formatted_error(\"Mask was {:x}, expected FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY\", aaa.Mask);\n\n            auto sid = span<const uint8_t>((uint8_t*)&aaa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart));\n\n            if (!compare_sid(sid, sid_everyone))\n                throw formatted_error(\"SID was {}, expected {}\", sid_to_string(sid), sid_to_string(sid_everyone));\n        });\n\n        h.reset();\n    }\n\n    test(\"Create subdirectory\", [&]() {\n        h = create_file(dir + u\"\\\\sec5\\\\dir\", READ_CONTROL, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Query DACL\", [&]() {\n            auto items = get_acl(h.get(), DACL_SECURITY_INFORMATION);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} items returned, expected 1\", items.size());\n\n            auto& ace = *static_cast<ACE_HEADER*>(items.front());\n\n            if (ace.AceType != ACCESS_ALLOWED_ACE_TYPE)\n                throw formatted_error(\"ACE type was {}, expected ACCESS_ALLOWED_ACE_TYPE\", ace.AceType);\n\n            if (ace.AceFlags != (OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE))\n                throw formatted_error(\"AceFlags was {:x}, expected OBJECT_INHERIT_ACE | CONTAINER_INHERIT_ACE\", ace.AceFlags);\n\n            auto& aaa = *reinterpret_cast<ACCESS_ALLOWED_ACE*>(&ace);\n\n            if (aaa.Mask != (FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY))\n                throw formatted_error(\"Mask was {:x}, expected FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY\", aaa.Mask);\n\n            auto sid = span<const uint8_t>((uint8_t*)&aaa.SidStart, items.front().buf.size() - offsetof(ACCESS_ALLOWED_ACE, SidStart));\n\n            if (!compare_sid(sid, sid_everyone))\n                throw formatted_error(\"SID was {}, expected {}\", sid_to_string(sid), sid_to_string(sid_everyone));\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory without FILE_TRAVERSE\", [&]() {\n        create_file_with_acl(dir + u\"\\\\sec6\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                             FILE_DIRECTORY_FILE, FILE_CREATED,\n                             FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | WRITE_DAC,\n                             0);\n    });\n\n    test(\"Try to create file within directory\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\sec6\\\\file\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n        }, STATUS_ACCESS_DENIED);\n    });\n\n    test(\"Open directory\", [&]() {\n        h = create_file(dir + u\"\\\\sec6\", WRITE_DAC, 0, 0, FILE_OPEN,\n                        0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Add FILE_TRAVERSE\", [&]() {\n            set_dacl(h.get(), FILE_TRAVERSE | FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | WRITE_DAC);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file within directory\", [&]() {\n        create_file(dir + u\"\\\\sec6\\\\file\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    0, FILE_CREATED);\n    });\n\n    test(\"Open directory\", [&]() {\n        h = create_file(dir + u\"\\\\sec6\", WRITE_DAC, 0, 0, FILE_OPEN,\n                        0, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Remove FILE_TRAVERSE\", [&]() {\n            set_dacl(h.get(), FILE_ADD_FILE | FILE_ADD_SUBDIRECTORY | WRITE_DAC);\n        });\n\n        h.reset();\n    }\n\n    test(\"Add SeChangeNotifyPrivilege to token\", [&]() {\n        LUID_AND_ATTRIBUTES laa;\n\n        laa.Luid.LowPart = SE_CHANGE_NOTIFY_PRIVILEGE;\n        laa.Luid.HighPart = 0;\n        laa.Attributes = SE_PRIVILEGE_ENABLED;\n\n        adjust_token_privileges(token, laa);\n    });\n\n    test(\"Create another file within directory\", [&]() {\n        create_file(dir + u\"\\\\sec6\\\\file2\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    0, FILE_CREATED);\n    });\n\n    disable_token_privileges(token);\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\sec7\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to query owner\", [&]() {\n            exp_status([&]() {\n                get_owner(h.get());\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Try to query group\", [&]() {\n            exp_status([&]() {\n                get_group(h.get());\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Try to query SACL\", [&]() {\n            exp_status([&]() {\n                get_acl(h.get(), SACL_SECURITY_INFORMATION);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Try to query DACL\", [&]() {\n            exp_status([&]() {\n                get_acl(h.get(), DACL_SECURITY_INFORMATION);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Try to query label\", [&]() {\n            exp_status([&]() {\n                get_acl(h.get(), LABEL_SECURITY_INFORMATION);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Try to set owner\", [&]() {\n            exp_status([&]() {\n                set_owner(h.get(), sid_test);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Try to set group\", [&]() {\n            exp_status([&]() {\n                set_group(h.get(), sid_test);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Try to set SACL\", [&]() {\n            exp_status([&]() {\n                set_audit(h.get(), SUCCESSFUL_ACCESS_ACE_FLAG | FAILED_ACCESS_ACE_FLAG, span(sid_everyone));\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Try to set DACL\", [&]() {\n            exp_status([&]() {\n                set_dacl(h.get(), FILE_READ_DATA);\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        test(\"Try to set label\", [&]() {\n            exp_status([&]() {\n                set_mandatory_access(h.get(), SYSTEM_MANDATORY_LABEL_NO_WRITE_UP, span(sid_high));\n            }, STATUS_ACCESS_DENIED);\n        });\n\n        h.reset();\n    }\n}\n"
  },
  {
    "path": "src/tests/streams.cpp",
    "content": "#include \"test.h\"\n\nusing namespace std;\n\n#ifdef _MSC_VER\ntypedef struct _FILE_STREAM_INFORMATION {\n    ULONG NextEntryOffset;\n    ULONG StreamNameLength;\n    LARGE_INTEGER StreamSize;\n    LARGE_INTEGER StreamAllocationSize;\n    WCHAR StreamName[1];\n} FILE_STREAM_INFORMATION, *PFILE_STREAM_INFORMATION;\n#endif\n\nstatic vector<varbuf<FILE_STREAM_INFORMATION>> query_streams(HANDLE h) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf(4096);\n    vector<varbuf<FILE_STREAM_INFORMATION>> ret;\n\n    while (true) {\n        Status = NtQueryInformationFile(h, &iosb, buf.data(), buf.size(), FileStreamInformation);\n\n        if (Status == STATUS_BUFFER_OVERFLOW) {\n            buf.resize(buf.size() + 4096);\n            continue;\n        }\n\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        break;\n    }\n\n    auto ptr = (FILE_STREAM_INFORMATION*)buf.data();\n\n    do {\n        varbuf<FILE_STREAM_INFORMATION> item;\n\n        item.buf.resize(offsetof(FILE_STREAM_INFORMATION, StreamName) + ptr->StreamNameLength);\n\n        memcpy(item.buf.data(), ptr, item.buf.size());\n\n        ret.emplace_back(item);\n\n        if (ptr->NextEntryOffset == 0)\n            break;\n\n        ptr = (FILE_STREAM_INFORMATION*)((uint8_t*)ptr + ptr->NextEntryOffset);\n    } while (true);\n\n    return ret;\n}\n\nvoid test_streams(const u16string& dir) {\n    unique_handle h;\n    int64_t fileid;\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\stream1\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            fileid = fii.IndexNumber.QuadPart;\n        });\n\n        test(\"Query streams\", [&]() {\n            auto items = query_streams(h.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& fsi = *static_cast<FILE_STREAM_INFORMATION*>(items.front());\n\n            if (fsi.StreamSize.QuadPart != 0)\n                throw formatted_error(\"StreamSize was {}, expected 0\", fsi.StreamSize.QuadPart);\n\n            if (fsi.StreamAllocationSize.QuadPart != 0)\n                throw formatted_error(\"StreamAllocationSize was {}, expected 0\", fsi.StreamAllocationSize.QuadPart);\n\n            auto name = u16string_view((char16_t*)fsi.StreamName, fsi.StreamNameLength / sizeof(char16_t));\n\n            if (name != u\"::$DATA\")\n                throw formatted_error(\"StreamName was {}, expected ::$DATA\", u16string_to_string(name));\n        });\n\n        test(\"Check FILE_STANDARD_INFORMATION_EX\", [&]() {\n            auto fsix = query_information<FILE_STANDARD_INFORMATION_EX>(h.get());\n\n            if (fsix.AlternateStream)\n                throw runtime_error(\"AlternateStream was true, expected false\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create stream\", [&]() {\n        h = create_file(dir + u\"\\\\stream1:stream\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Get file ID\", [&]() {\n            auto fii = query_information<FILE_INTERNAL_INFORMATION>(h.get());\n\n            if (fii.IndexNumber.QuadPart != fileid)\n                throw runtime_error(\"File IDs did not match.\");\n        });\n\n        test(\"Check FILE_STANDARD_INFORMATION_EX\", [&]() {\n            auto fsix = query_information<FILE_STANDARD_INFORMATION_EX>(h.get());\n\n            if (!fsix.AlternateStream)\n                throw runtime_error(\"AlternateStream was false, expected true\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\stream1\", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN,\n                        FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Query streams\", [&]() {\n            auto items = query_streams(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2\", items.size());\n\n            auto& fsi1 = *static_cast<FILE_STREAM_INFORMATION*>(items[0]);\n\n            if (fsi1.StreamSize.QuadPart != 0)\n                throw formatted_error(\"StreamSize was {}, expected 0\", fsi1.StreamSize.QuadPart);\n\n            if (fsi1.StreamAllocationSize.QuadPart != 0)\n                throw formatted_error(\"StreamAllocationSize was {}, expected 0\", fsi1.StreamAllocationSize.QuadPart);\n\n            auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t));\n\n            if (name1 != u\"::$DATA\")\n                throw formatted_error(\"StreamName was {}, expected ::$DATA\", u16string_to_string(name1));\n\n            auto& fsi2 = *static_cast<FILE_STREAM_INFORMATION*>(items[1]);\n\n            if (fsi2.StreamSize.QuadPart != 0)\n                throw formatted_error(\"StreamSize was {}, expected 0\", fsi2.StreamSize.QuadPart);\n\n            if (fsi2.StreamAllocationSize.QuadPart != 0)\n                throw formatted_error(\"StreamAllocationSize was {}, expected 0\", fsi2.StreamAllocationSize.QuadPart);\n\n            auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t));\n\n            if (name2 != u\":stream:$DATA\")\n                throw formatted_error(\"StreamName was {}, expected :stream:$DATA\", u16string_to_string(name2));\n        });\n\n        h.reset();\n    }\n\n    test(\"Create stream on non-existent file\", [&]() {\n        h = create_file(dir + u\"\\\\stream2:stream\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Query streams\", [&]() {\n            auto items = query_streams(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2\", items.size());\n\n            auto& fsi1 = *static_cast<FILE_STREAM_INFORMATION*>(items[0]);\n\n            if (fsi1.StreamSize.QuadPart != 0)\n                throw formatted_error(\"StreamSize was {}, expected 0\", fsi1.StreamSize.QuadPart);\n\n            if (fsi1.StreamAllocationSize.QuadPart != 0)\n                throw formatted_error(\"StreamAllocationSize was {}, expected 0\", fsi1.StreamAllocationSize.QuadPart);\n\n            auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t));\n\n            if (name1 != u\"::$DATA\")\n                throw formatted_error(\"StreamName was {}, expected ::$DATA\", u16string_to_string(name1));\n\n            auto& fsi2 = *static_cast<FILE_STREAM_INFORMATION*>(items[1]);\n\n            if (fsi2.StreamSize.QuadPart != 0)\n                throw formatted_error(\"StreamSize was {}, expected 0\", fsi2.StreamSize.QuadPart);\n\n            if (fsi2.StreamAllocationSize.QuadPart != 0)\n                throw formatted_error(\"StreamAllocationSize was {}, expected 0\", fsi2.StreamAllocationSize.QuadPart);\n\n            auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t));\n\n            if (name2 != u\":stream:$DATA\")\n                throw formatted_error(\"StreamName was {}, expected :stream:$DATA\", u16string_to_string(name2));\n        });\n\n        h.reset();\n    }\n\n    test(\"Check file created for stream\", [&]() {\n        create_file(dir + u\"\\\\stream2\", FILE_READ_ATTRIBUTES, 0, 0, FILE_OPEN,\n                    FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    test(\"Try to create stream with FILE_DIRECTORY_FILE\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\stream2:stream\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n        }, STATUS_NOT_A_DIRECTORY);\n    });\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\stream3\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Query streams\", [&]() {\n            auto items = query_streams(h.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& fsi = *static_cast<FILE_STREAM_INFORMATION*>(items.front());\n\n            if (fsi.StreamSize.QuadPart != 0)\n                throw formatted_error(\"StreamSize was {}, expected 0\", fsi.StreamSize.QuadPart);\n\n            if (fsi.StreamAllocationSize.QuadPart != 0)\n                throw formatted_error(\"StreamAllocationSize was {}, expected 0\", fsi.StreamAllocationSize.QuadPart);\n\n            auto name = u16string_view((char16_t*)fsi.StreamName, fsi.StreamNameLength / sizeof(char16_t));\n\n            if (name != u\"\")\n                throw formatted_error(\"StreamName was {}, expected empty string\", u16string_to_string(name));\n        });\n\n        test(\"Check FILE_STANDARD_INFORMATION_EX\", [&]() {\n            auto fsix = query_information<FILE_STANDARD_INFORMATION_EX>(h.get());\n\n            if (fsix.AlternateStream)\n                throw runtime_error(\"AlternateStream was true, expected false\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Try to create stream with FILE_DIRECTORY_FILE\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\stream3:stream\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n        }, STATUS_NOT_A_DIRECTORY);\n    });\n\n    test(\"Create stream on directory\", [&]() {\n        h = create_file(dir + u\"\\\\stream3:stream\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Query streams\", [&]() {\n            auto items = query_streams(h.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& fsi = *static_cast<FILE_STREAM_INFORMATION*>(items.front());\n\n            if (fsi.StreamSize.QuadPart != 0)\n                throw formatted_error(\"StreamSize was {}, expected 0\", fsi.StreamSize.QuadPart);\n\n            if (fsi.StreamAllocationSize.QuadPart != 0)\n                throw formatted_error(\"StreamAllocationSize was {}, expected 0\", fsi.StreamAllocationSize.QuadPart);\n\n            auto name = u16string_view((char16_t*)fsi.StreamName, fsi.StreamNameLength / sizeof(char16_t));\n\n            if (name != u\":stream:$DATA\")\n                throw formatted_error(\"StreamName was {}, expected :stream:$DATA\", u16string_to_string(name));\n        });\n\n        test(\"Check FILE_STANDARD_INFORMATION_EX\", [&]() {\n            auto fsix = query_information<FILE_STANDARD_INFORMATION_EX>(h.get());\n\n            if (!fsix.AlternateStream)\n                throw runtime_error(\"AlternateStream was false, expected true\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Try to create ::$DATA stream on directory\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\stream3::$DATA\", FILE_READ_ATTRIBUTES, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n        }, STATUS_FILE_IS_A_DIRECTORY);\n    });\n\n    test(\"Create stream\", [&]() {\n        h = create_file(dir + u\"\\\\stream4:stream\", SYNCHRONIZE | FILE_READ_DATA | FILE_WRITE_DATA, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, FILE_CREATED);\n    });\n\n    if (h) {\n        static const string_view data = \"hello\";\n\n        test(\"Write to stream\", [&]() {\n            write_file(h.get(), span((uint8_t*)data.data(), data.size()));\n        });\n\n        test(\"Read from stream\", [&]() {\n            auto buf = read_file(h.get(), data.size(), 0);\n\n            if (buf.size() != data.size() || memcmp(buf.data(), data.data(), data.size()))\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        test(\"Query streams\", [&]() {\n            auto items = query_streams(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2\", items.size());\n\n            auto& fsi1 = *static_cast<FILE_STREAM_INFORMATION*>(items[0]);\n\n            if (fsi1.StreamSize.QuadPart != 0)\n                throw formatted_error(\"StreamSize was {}, expected 0\", fsi1.StreamSize.QuadPart);\n\n            if (fsi1.StreamAllocationSize.QuadPart != 0)\n                throw formatted_error(\"StreamAllocationSize was {}, expected 0\", fsi1.StreamAllocationSize.QuadPart);\n\n            auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t));\n\n            if (name1 != u\"::$DATA\")\n                throw formatted_error(\"StreamName was {}, expected ::$DATA\", u16string_to_string(name1));\n\n            auto& fsi2 = *static_cast<FILE_STREAM_INFORMATION*>(items[1]);\n\n            if ((size_t)fsi2.StreamSize.QuadPart != data.size())\n                throw formatted_error(\"StreamSize was {}, expected {}\", fsi2.StreamSize.QuadPart, data.size());\n\n            if (fsi2.StreamAllocationSize.QuadPart < fsi2.StreamSize.QuadPart) {\n                throw formatted_error(\"StreamAllocationSize was less than StreamSize ({} < {})\",\n                                      fsi2.StreamAllocationSize.QuadPart,\n                                      fsi2.StreamSize.QuadPart);\n            }\n\n            auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t));\n\n            if (name2 != u\":stream:$DATA\")\n                throw formatted_error(\"StreamName was {}, expected :stream:$DATA\", u16string_to_string(name2));\n        });\n\n        test(\"Set zero data\", [&]() {\n            set_zero_data(h.get(), 2, 4);\n        });\n\n        test(\"Read from stream\", [&]() {\n            static const string_view exp(\"he\\0\\0o\", 5);\n\n            auto buf = read_file(h.get(), data.size(), 0);\n\n            if (buf.size() != exp.size() || memcmp(buf.data(), exp.data(), exp.size()))\n                throw runtime_error(\"Data read was not as expected\");\n        });\n\n        test(\"Query streams\", [&]() {\n            auto items = query_streams(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2\", items.size());\n\n            auto& fsi1 = *static_cast<FILE_STREAM_INFORMATION*>(items[0]);\n\n            if (fsi1.StreamSize.QuadPart != 0)\n                throw formatted_error(\"StreamSize was {}, expected 0\", fsi1.StreamSize.QuadPart);\n\n            if (fsi1.StreamAllocationSize.QuadPart != 0)\n                throw formatted_error(\"StreamAllocationSize was {}, expected 0\", fsi1.StreamAllocationSize.QuadPart);\n\n            auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t));\n\n            if (name1 != u\"::$DATA\")\n                throw formatted_error(\"StreamName was {}, expected ::$DATA\", u16string_to_string(name1));\n\n            auto& fsi2 = *static_cast<FILE_STREAM_INFORMATION*>(items[1]);\n\n            if ((size_t)fsi2.StreamSize.QuadPart != data.size())\n                throw formatted_error(\"StreamSize was {}, expected {}\", fsi2.StreamSize.QuadPart, data.size());\n\n            if (fsi2.StreamAllocationSize.QuadPart < fsi2.StreamSize.QuadPart) {\n                throw formatted_error(\"StreamAllocationSize was less than StreamSize ({} < {})\",\n                                      fsi2.StreamAllocationSize.QuadPart,\n                                      fsi2.StreamSize.QuadPart);\n            }\n\n            auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t));\n\n            if (name2 != u\":stream:$DATA\")\n                throw formatted_error(\"StreamName was {}, expected :stream:$DATA\", u16string_to_string(name2));\n        });\n\n        test(\"Truncate stream\", [&]() {\n            set_end_of_file(h.get(), 3);\n        });\n\n        test(\"Read from stream\", [&]() {\n            static const string_view exp(\"he\\0\", 3);\n\n            auto buf = read_file(h.get(), data.size(), 0);\n\n            if (buf.size() != exp.size() || memcmp(buf.data(), exp.data(), exp.size()))\n                throw runtime_error(\"Data read was not as expected\");\n        });\n\n        test(\"Query streams\", [&]() {\n            auto items = query_streams(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2\", items.size());\n\n            auto& fsi1 = *static_cast<FILE_STREAM_INFORMATION*>(items[0]);\n\n            if (fsi1.StreamSize.QuadPart != 0)\n                throw formatted_error(\"StreamSize was {}, expected 0\", fsi1.StreamSize.QuadPart);\n\n            if (fsi1.StreamAllocationSize.QuadPart != 0)\n                throw formatted_error(\"StreamAllocationSize was {}, expected 0\", fsi1.StreamAllocationSize.QuadPart);\n\n            auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t));\n\n            if (name1 != u\"::$DATA\")\n                throw formatted_error(\"StreamName was {}, expected ::$DATA\", u16string_to_string(name1));\n\n            auto& fsi2 = *static_cast<FILE_STREAM_INFORMATION*>(items[1]);\n\n            if (fsi2.StreamSize.QuadPart != 3)\n                throw formatted_error(\"StreamSize was {}, expected 3\", fsi2.StreamSize.QuadPart);\n\n            if (fsi2.StreamAllocationSize.QuadPart < fsi2.StreamSize.QuadPart) {\n                throw formatted_error(\"StreamAllocationSize was less than StreamSize ({} < {})\",\n                                      fsi2.StreamAllocationSize.QuadPart,\n                                      fsi2.StreamSize.QuadPart);\n            }\n\n            auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t));\n\n            if (name2 != u\":stream:$DATA\")\n                throw formatted_error(\"StreamName was {}, expected :stream:$DATA\", u16string_to_string(name2));\n        });\n\n        h.reset();\n    }\n\n    test(\"Create stream\", [&]() {\n        create_file(dir + u\"\\\\stream5:stream\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Open stream (FILE_OPEN)\", [&]() {\n        create_file(dir + u\"\\\\stream5:stream\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                    FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    test(\"Open stream (FILE_OPEN_IF)\", [&]() {\n        create_file(dir + u\"\\\\stream5:stream\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN_IF,\n                    FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    test(\"Overwrite stream (FILE_OVERWRITE)\", [&]() {\n        create_file(dir + u\"\\\\stream5:stream\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE,\n                    FILE_NON_DIRECTORY_FILE, FILE_OVERWRITTEN);\n    });\n\n    test(\"Overwrite stream (FILE_OVERWRITE_IF)\", [&]() {\n        create_file(dir + u\"\\\\stream5:stream\", MAXIMUM_ALLOWED, 0, 0, FILE_OVERWRITE_IF,\n                    FILE_NON_DIRECTORY_FILE, FILE_OVERWRITTEN);\n    });\n\n    test(\"Supersede stream\", [&]() {\n        create_file(dir + u\"\\\\stream5:stream\", MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE,\n                    FILE_NON_DIRECTORY_FILE, FILE_SUPERSEDED);\n    });\n\n    test(\"Create stream\", [&]() {\n        h = create_file(dir + u\"\\\\stream6:stream\", DELETE, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Delete stream\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry for file\", [&]() {\n            u16string_view name = u\"stream6\";\n\n            auto items = query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1.\", items.size());\n\n            auto& fdi = *static_cast<const FILE_DIRECTORY_INFORMATION*>(items.front());\n\n            if (fdi.FileNameLength != name.size() * sizeof(char16_t))\n                throw formatted_error(\"FileNameLength was {}, expected {}.\", fdi.FileNameLength, name.size() * sizeof(char16_t));\n\n            if (name != u16string_view((char16_t*)fdi.FileName, fdi.FileNameLength / sizeof(char16_t)))\n                throw runtime_error(\"FileName did not match.\");\n        });\n    }\n\n    test(\"Create stream\", [&]() {\n        create_file(dir + u\"\\\\stream7:stream\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\stream7\", DELETE, 0, 0, FILE_OPEN,\n                        FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Delete file\", [&]() {\n            set_disposition_information(h.get(), true);\n        });\n\n        h.reset();\n\n        test(\"Check directory entry for file gone\", [&]() {\n            exp_status([&]() {\n                u16string_view name = u\"stream7\";\n\n                query_dir<FILE_DIRECTORY_INFORMATION>(dir, name);\n            }, STATUS_NO_SUCH_FILE);\n        });\n    }\n\n    test(\"Create stream\", [&]() {\n        h = create_file(dir + u\"\\\\stream8:stream\", FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Write to stream\", [&]() {\n            static const string_view data = \"hello\";\n\n            write_file_wait(h.get(), span((uint8_t*)data.data(), data.size()), 0);\n        });\n\n        test(\"Check FILE_STANDARD_INFORMATION_EX\", [&]() {\n            auto fsix = query_information<FILE_STANDARD_INFORMATION_EX>(h.get());\n\n            if (!fsix.AlternateStream)\n                throw runtime_error(\"AlternateStream was false, expected true\");\n        });\n\n        test(\"Try to rename stream using full path\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, dir + u\"\\\\stream8:stream2\");\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Rename stream\", [&]() {\n            set_rename_information(h.get(), false, nullptr, u\":stream2\");\n        });\n\n        test(\"Check FILE_STANDARD_INFORMATION_EX\", [&]() {\n            auto fsix = query_information<FILE_STANDARD_INFORMATION_EX>(h.get());\n\n            if (!fsix.AlternateStream)\n                throw runtime_error(\"AlternateStream was false, expected true\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Open stream\", [&]() {\n        h = create_file(dir + u\"\\\\stream8:stream2\", FILE_READ_DATA, 0, 0, FILE_OPEN,\n                        FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    if (h) {\n        static const string_view data = \"hello\";\n\n        test(\"Read from stream\", [&]() {\n            auto buf = read_file_wait(h.get(), data.size(), 0);\n\n            if (buf.size() != data.size() || memcmp(buf.data(), data.data(), data.size()))\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        test(\"Query streams\", [&]() {\n            auto items = query_streams(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2\", items.size());\n\n            auto& fsi1 = *static_cast<FILE_STREAM_INFORMATION*>(items[0]);\n\n            if (fsi1.StreamSize.QuadPart != 0)\n                throw formatted_error(\"StreamSize was {}, expected 0\", fsi1.StreamSize.QuadPart);\n\n            if (fsi1.StreamAllocationSize.QuadPart != 0)\n                throw formatted_error(\"StreamAllocationSize was {}, expected 0\", fsi1.StreamAllocationSize.QuadPart);\n\n            auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t));\n\n            if (name1 != u\"::$DATA\")\n                throw formatted_error(\"StreamName was {}, expected ::$DATA\", u16string_to_string(name1));\n\n            auto& fsi2 = *static_cast<FILE_STREAM_INFORMATION*>(items[1]);\n\n            if ((size_t)fsi2.StreamSize.QuadPart != data.size())\n                throw formatted_error(\"StreamSize was {}, expected {}\", fsi2.StreamSize.QuadPart, data.size());\n\n            if (fsi2.StreamAllocationSize.QuadPart < fsi2.StreamSize.QuadPart) {\n                throw formatted_error(\"StreamAllocationSize was less than StreamSize ({} < {})\",\n                                      fsi2.StreamAllocationSize.QuadPart,\n                                      fsi2.StreamSize.QuadPart);\n            }\n\n            auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t));\n\n            if (name2 != u\":stream2:$DATA\")\n                throw formatted_error(\"StreamName was {}, expected :stream2:$DATA\", u16string_to_string(name2));\n        });\n\n        h.reset();\n    }\n\n    test(\"Create stream\", [&]() {\n        h = create_file(dir + u\"\\\\stream9:stream\", FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        static const string_view data = \"hello\";\n\n        test(\"Write to stream\", [&]() {\n            write_file_wait(h.get(), span((uint8_t*)data.data(), data.size()), 0);\n        });\n\n        test(\"Check FILE_STANDARD_INFORMATION_EX\", [&]() {\n            auto fsix = query_information<FILE_STANDARD_INFORMATION_EX>(h.get());\n\n            if (!fsix.AlternateStream)\n                throw runtime_error(\"AlternateStream was false, expected true\");\n        });\n\n        test(\"Rename stream to ::$DATA without ReplaceIfExists\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, u\"::$DATA\");\n            }, STATUS_OBJECT_NAME_COLLISION);\n        });\n\n        test(\"Rename stream to ::$DATA with ReplaceIfExists\", [&]() {\n            set_rename_information(h.get(), true, nullptr, u\"::$DATA\");\n        });\n\n        test(\"Check FILE_STANDARD_INFORMATION_EX\", [&]() {\n            auto fsix = query_information<FILE_STANDARD_INFORMATION_EX>(h.get());\n\n            if (fsix.AlternateStream)\n                throw runtime_error(\"AlternateStream was true, expected false\");\n        });\n\n        test(\"Query streams\", [&]() {\n            auto items = query_streams(h.get());\n\n            if (items.size() != 1)\n                throw formatted_error(\"{} entries returned, expected 1\", items.size());\n\n            auto& fsi = *static_cast<FILE_STREAM_INFORMATION*>(items.front());\n\n            if ((size_t)fsi.StreamSize.QuadPart != data.size())\n                throw formatted_error(\"StreamSize was {}, expected {}\", fsi.StreamSize.QuadPart, data.size());\n\n            if (fsi.StreamAllocationSize.QuadPart < fsi.StreamSize.QuadPart) {\n                throw formatted_error(\"StreamAllocationSize was less than StreamSize ({} < {})\",\n                                      fsi.StreamAllocationSize.QuadPart,\n                                      fsi.StreamSize.QuadPart);\n            }\n\n            auto name = u16string_view((char16_t*)fsi.StreamName, fsi.StreamNameLength / sizeof(char16_t));\n\n            if (name != u\"::$DATA\")\n                throw formatted_error(\"StreamName was {}, expected ::$DATA\", u16string_to_string(name));\n        });\n\n        h.reset();\n    }\n\n    test(\"Open file\", [&]() {\n        h = create_file(dir + u\"\\\\stream9\", FILE_READ_DATA, 0, 0, FILE_OPEN,\n                        FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Read from file\", [&]() {\n            static const string_view data = \"hello\";\n\n            auto buf = read_file_wait(h.get(), data.size(), 0);\n\n            if (buf.size() != data.size() || memcmp(buf.data(), data.data(), data.size()))\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        create_file(dir + u\"\\\\stream10\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create stream\", [&]() {\n        h = create_file(dir + u\"\\\\stream10:stream\", FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to rename stream on directory to ::$DATA\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), true, nullptr, u\"::$DATA\");\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\stream11\", FILE_WRITE_DATA | DELETE, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        static const string_view data = \"hello\";\n\n        test(\"Write to file\", [&]() {\n            write_file_wait(h.get(), span((uint8_t*)data.data(), data.size()), 0);\n        });\n\n        test(\"Check FILE_STANDARD_INFORMATION_EX\", [&]() {\n            auto fsix = query_information<FILE_STANDARD_INFORMATION_EX>(h.get());\n\n            if (fsix.AlternateStream)\n                throw runtime_error(\"AlternateStream was true, expected false\");\n        });\n\n        test(\"Rename file to :stream\", [&]() {\n            set_rename_information(h.get(), false, nullptr, u\":stream\");\n        });\n\n        test(\"Query streams\", [&]() {\n            auto items = query_streams(h.get());\n\n            if (items.size() != 2)\n                throw formatted_error(\"{} entries returned, expected 2\", items.size());\n\n            auto& fsi1 = *static_cast<FILE_STREAM_INFORMATION*>(items[0]);\n\n            if (fsi1.StreamSize.QuadPart != 0)\n                throw formatted_error(\"StreamSize was {}, expected 0\", fsi1.StreamSize.QuadPart);\n\n            if (fsi1.StreamAllocationSize.QuadPart != 0)\n                throw formatted_error(\"StreamAllocationSize was {}, expected 0\", fsi1.StreamAllocationSize.QuadPart);\n\n            auto name1 = u16string_view((char16_t*)fsi1.StreamName, fsi1.StreamNameLength / sizeof(char16_t));\n\n            if (name1 != u\"::$DATA\")\n                throw formatted_error(\"StreamName was {}, expected ::$DATA\", u16string_to_string(name1));\n\n            auto& fsi2 = *static_cast<FILE_STREAM_INFORMATION*>(items[1]);\n\n            if ((size_t)fsi2.StreamSize.QuadPart != data.size())\n                throw formatted_error(\"StreamSize was {}, expected {}\", fsi2.StreamSize.QuadPart, data.size());\n\n            if (fsi2.StreamAllocationSize.QuadPart < fsi2.StreamSize.QuadPart) {\n                throw formatted_error(\"StreamAllocationSize was less than StreamSize ({} < {})\",\n                                      fsi2.StreamAllocationSize.QuadPart,\n                                      fsi2.StreamSize.QuadPart);\n            }\n\n            auto name2 = u16string_view((char16_t*)fsi2.StreamName, fsi2.StreamNameLength / sizeof(char16_t));\n\n            if (name2 != u\":stream:$DATA\")\n                throw formatted_error(\"StreamName was {}, expected :stream:$DATA\", u16string_to_string(name2));\n        });\n\n        test(\"Check FILE_STANDARD_INFORMATION_EX\", [&]() {\n            auto fsix = query_information<FILE_STANDARD_INFORMATION_EX>(h.get());\n\n            if (!fsix.AlternateStream)\n                throw runtime_error(\"AlternateStream was false, expected true\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Open stream\", [&]() {\n        h = create_file(dir + u\"\\\\stream11:stream\", FILE_READ_DATA, 0, 0, FILE_OPEN,\n                        FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    if (h) {\n        test(\"Read from file\", [&]() {\n            static const string_view data = \"hello\";\n\n            auto buf = read_file_wait(h.get(), data.size(), 0);\n\n            if (buf.size() != data.size() || memcmp(buf.data(), data.data(), data.size()))\n                throw runtime_error(\"Data read did not match data written\");\n        });\n\n        h.reset();\n    }\n\n    test(\"Create directory\", [&]() {\n        h = create_file(dir + u\"\\\\stream12\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Try to rename directory to :stream\", [&]() {\n            exp_status([&]() {\n                set_rename_information(h.get(), true, nullptr, u\":stream\");\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        h.reset();\n    }\n\n    test(\"Create file\", [&]() {\n        create_file(dir + u\"\\\\stream13\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Open file with ::$DATA suffix\", [&]() {\n        create_file(dir + u\"\\\\stream13::$DATA\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                    FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    test(\"Open file with ::$data suffix\", [&]() {\n        create_file(dir + u\"\\\\stream13::$data\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                    FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    test(\"Create stream\", [&]() {\n        create_file(dir + u\"\\\\stream13:stream\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Open stream with ::$DATA suffix\", [&]() {\n        create_file(dir + u\"\\\\stream13:stream:$DATA\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                    FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    test(\"Open stream with ::$data suffix\", [&]() {\n        create_file(dir + u\"\\\\stream13:stream:$data\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                    FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n\n    test(\"Create file\", [&]() {\n        create_file(dir + u\"\\\\stream14\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Create stream with long name\", [&]() {\n        u16string longname(256, u'x');\n\n        exp_status([&]() {\n            create_file(dir + u\"\\\\stream14:\" + longname, MAXIMUM_ALLOWED, 0, 0,\n                        FILE_CREATE, 0, FILE_CREATED);\n        }, STATUS_OBJECT_NAME_INVALID);\n    });\n\n    test(\"Create stream with emoji\", [&]() {\n        create_file(dir + u\"\\\\stream14:\\U0001f525\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n    });\n\n    bool is_ntfs = fstype == fs_type::ntfs;\n\n    test(\"Create stream with more than 255 UTF-8 characters\", [&]() {\n        auto fn = dir + u\"\\\\stream14:\";\n\n        for (unsigned int i = 0; i < 64; i++) {\n            fn += u\"\\U0001f525\";\n        }\n\n        exp_status([&]() {\n            create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n    });\n\n    test(\"Create stream with WTF-16 (1)\", [&]() {\n        auto fn = dir + u\"\\\\stream14:\";\n\n        fn += (char16_t)0xd83d;\n\n        exp_status([&]() {\n            create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n    });\n\n    test(\"Create stream with WTF-16 (2)\", [&]() {\n        auto fn = dir + u\"\\\\stream14:\";\n\n        fn += (char16_t)0xdd25;\n\n        exp_status([&]() {\n            create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n    });\n\n    test(\"Create stream with WTF-16 (3)\", [&]() {\n        auto fn = dir + u\"\\\\stream14:\";\n\n        fn += (char16_t)0xdd25;\n        fn += (char16_t)0xd83d;\n\n        exp_status([&]() {\n            create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n        }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n    });\n\n    struct {\n        u16string name;\n        string desc;\n        bool valid;\n    } unusual_names[] = {\n        { u\"/\", \"slash\", false },\n        { u\":\", \"colon\", false },\n        { u\"<\", \"less than\", true },\n        { u\">\", \"greater than\", true },\n        { u\"\\\"\", \"quote\", true },\n        { u\"|\", \"pipe\", true },\n        { u\"?\", \"question mark\", true },\n        { u\"*\", \"asterisk\", true }\n    };\n\n    for (const auto& n : unusual_names) {\n        test(\"Create stream with unusual name (\" + n.desc + \")\", [&]() {\n            auto fn = dir + u\"\\\\stream14:\" + n.name;\n\n            exp_status([&]() {\n                create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n            }, n.valid ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n        });\n    }\n\n    struct {\n        u16string name;\n        string desc;\n    } btrfs_reserved[] = {\n        { u\"DOSATTRIB\", \"DOSATTRIB\" },\n        { u\"reparse\", \"reparse\" },\n        { u\"EA\", \"EA\" },\n        { u\"casesensitive\", \"casesensitive\" }\n    };\n\n    for (const auto& n : btrfs_reserved) {\n        test(\"Create stream with reserved name (\" + n.desc + \")\", [&]() {\n            auto fn = dir + u\"\\\\stream14:\" + n.name;\n\n            exp_status([&]() {\n                create_file(fn, MAXIMUM_ALLOWED, 0, 0, FILE_CREATE, 0, FILE_CREATED);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_OBJECT_NAME_INVALID);\n        });\n    }\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\stream15\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                        FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Rename to stream with long name\", [&]() {\n            u16string longname(256, u'x');\n\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, u\":\" + longname);\n            }, STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Rename to stream with emoji\", [&]() {\n            set_rename_information(h.get(), false, nullptr, u\":\\U0001f525\");\n        });\n\n        test(\"Rename to stream with more than 255 UTF-8 characters\", [&]() {\n            u16string fn = u\":\";\n\n            for (unsigned int i = 0; i < 64; i++) {\n                fn += u\"\\U0001f525\";\n            }\n\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, fn);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Rename to stream with WTF-16 (1)\", [&]() {\n            u16string fn = u\":\";\n\n            fn += (char16_t)0xd83d;\n\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, fn);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Rename to stream with WTF-16 (2)\", [&]() {\n            u16string fn = u\":\";\n\n            fn += (char16_t)0xdd25;\n\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, fn);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER);\n        });\n\n        test(\"Rename to stream with WTF-16 (3)\", [&]() {\n            u16string fn = u\":\";\n\n            fn += (char16_t)0xdd25;\n            fn += (char16_t)0xd83d;\n\n            exp_status([&]() {\n                set_rename_information(h.get(), false, nullptr, fn);\n            }, is_ntfs ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER);\n        });\n\n        for (const auto& n : unusual_names) {\n            test(\"Rename to stream with unusual name (\" + n.desc + \")\", [&]() {\n                auto fn = u\":\" + n.name;\n\n                exp_status([&]() {\n                    set_rename_information(h.get(), false, nullptr, fn);\n                }, n.valid ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER);\n            });\n        }\n\n        for (const auto& n : btrfs_reserved) {\n            test(\"Rename to stream with reserved name (\" + n.desc + \")\", [&]() {\n                auto fn = u\":\" + n.name;\n\n                exp_status([&]() {\n                    set_rename_information(h.get(), false, nullptr, fn);\n                }, is_ntfs ? STATUS_SUCCESS : STATUS_INVALID_PARAMETER);\n            });\n        }\n\n        h.reset();\n    }\n\n    test(\"Create stream\", [&]() {\n        create_file(dir + u\"\\\\stream16:stream\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                    FILE_NON_DIRECTORY_FILE, FILE_CREATED);\n    });\n\n    test(\"Open stream using wrong case\", [&]() {\n        create_file(dir + u\"\\\\STREAM16:STREAM\", MAXIMUM_ALLOWED, 0, 0, FILE_OPEN,\n                    FILE_NON_DIRECTORY_FILE, FILE_OPENED);\n    });\n}\n"
  },
  {
    "path": "src/tests/supersede.cpp",
    "content": "#include \"test.h\"\n\nusing namespace std;\n\nvoid test_supersede(const u16string& dir) {\n    unique_handle h;\n\n    test(\"Create file by FILE_SUPERSEDE\", [&]() {\n        h = create_file(dir + u\"\\\\supersede\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_READONLY, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_SUPERSEDE, 0, FILE_CREATED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != (FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_ARCHIVE)) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_ARCHIVE\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        test(\"Try superseding open file\", [&]() {\n            create_file(dir + u\"\\\\supersede\", MAXIMUM_ALLOWED, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,\n                        FILE_SUPERSEDE, 0, FILE_SUPERSEDED);\n        });\n\n        h.reset();\n    }\n\n    test(\"Supersede file\", [&]() {\n        h = create_file(dir + u\"\\\\supersede\", MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE,\n                        0, FILE_SUPERSEDED);\n    });\n\n    if (h) {\n        test(\"Check attributes\", [&]() {\n            auto fbi = query_information<FILE_BASIC_INFORMATION>(h.get());\n\n            if (fbi.FileAttributes != FILE_ATTRIBUTE_ARCHIVE) {\n                throw formatted_error(\"attributes were {:x}, expected FILE_ATTRIBUTE_ARCHIVE\",\n                                      fbi.FileAttributes);\n            }\n        });\n\n        h.reset();\n    }\n\n    test(\"Supersede adding hidden flag\", [&]() {\n        create_file(dir + u\"\\\\supersede\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN, 0,\n                    FILE_SUPERSEDE, 0, FILE_SUPERSEDED);\n    });\n\n    test(\"Try superseding while clearing hidden flag\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\supersede\", MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE,\n                        0, FILE_SUPERSEDED);\n        }, STATUS_ACCESS_DENIED);\n    });\n\n    test(\"Supersede adding system flag\", [&]() {\n        create_file(dir + u\"\\\\supersede\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM, 0,\n                    FILE_SUPERSEDE, 0, FILE_SUPERSEDED);\n    });\n\n    test(\"Try superseding while clearing system flag\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\supersede\", MAXIMUM_ALLOWED, FILE_ATTRIBUTE_HIDDEN, 0,\n                        FILE_SUPERSEDE, 0, FILE_SUPERSEDED);\n        }, STATUS_ACCESS_DENIED);\n    });\n\n    test(\"Try creating directory by FILE_SUPERSEDE\", [&]() {\n        exp_status([&]() {\n            create_file(dir + u\"\\\\supersededir\", MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE,\n                        FILE_DIRECTORY_FILE, FILE_CREATED);\n        }, STATUS_INVALID_PARAMETER);\n    });\n\n    test(\"Create file\", [&]() {\n        h = create_file(dir + u\"\\\\supersede2\", MAXIMUM_ALLOWED, 0, 0, FILE_CREATE,\n                        0, FILE_CREATED);\n    });\n\n    if (h) {\n        h.reset();\n\n        test(\"Supersede file with different case\", [&]() {\n            h = create_file(dir + u\"\\\\SUPERSEDE2\", MAXIMUM_ALLOWED, 0, 0, FILE_SUPERSEDE,\n                            0, FILE_SUPERSEDED);\n        });\n\n        if (h) {\n            test(\"Check name\", [&]() {\n                auto fn = query_file_name_information(h.get());\n\n                static const u16string_view ends_with = u\"\\\\supersede2\";\n\n                if (fn.size() < ends_with.size() || fn.substr(fn.size() - ends_with.size()) != ends_with)\n                    throw runtime_error(\"Name did not end with \\\"\\\\supersede2\\\".\");\n            });\n        }\n    }\n}\n"
  },
  {
    "path": "src/tests/test.cpp",
    "content": "/* Copyright (c) Mark Harmstone 2021\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"test.h\"\n\n#define NOGDI\n#include <wincon.h>\n\n#include <winsvc.h>\n#include <winver.h>\n#include <functional>\n#include <iostream>\n\nusing namespace std;\n\nenum fs_type fstype;\n\nstatic unsigned int num_tests_run, num_tests_passed;\n\nunique_handle create_file(u16string_view path, ACCESS_MASK access, ULONG atts, ULONG share,\n                          ULONG dispo, ULONG options, ULONG_PTR exp_info, optional<uint64_t> allocation) {\n    NTSTATUS Status;\n    HANDLE h;\n    IO_STATUS_BLOCK iosb;\n    UNICODE_STRING us;\n    OBJECT_ATTRIBUTES oa;\n    LARGE_INTEGER alloc_size;\n\n    memset(&oa, 0, sizeof(oa));\n    oa.Length = sizeof(oa);\n    oa.RootDirectory = nullptr; // FIXME - test\n\n    us.Length = us.MaximumLength = path.length() * sizeof(char16_t);\n    us.Buffer = (WCHAR*)path.data();\n    oa.ObjectName = &us;\n\n    oa.Attributes = OBJ_CASE_INSENSITIVE;\n    oa.SecurityDescriptor = nullptr;\n    oa.SecurityQualityOfService = nullptr;\n\n    if (allocation)\n        alloc_size.QuadPart = allocation.value();\n\n    iosb.Information = 0xdeadbeef;\n\n    Status = NtCreateFile(&h, access, &oa, &iosb, allocation ? &alloc_size : nullptr,\n                          atts, share, dispo, options, nullptr, 0);\n\n    if (Status != STATUS_SUCCESS) {\n        if (NT_SUCCESS(Status)) // STATUS_OPLOCK_BREAK_IN_PROGRESS etc.\n            NtClose(h);\n\n        throw ntstatus_error(Status);\n    }\n\n    if (iosb.Information != exp_info)\n        throw formatted_error(\"iosb.Information was {}, expected {}\", iosb.Information, exp_info);\n\n    return unique_handle(h);\n}\n\nvarbuf<FILE_ALL_INFORMATION> query_all_information(HANDLE h) {\n    IO_STATUS_BLOCK iosb;\n    NTSTATUS Status;\n    FILE_ALL_INFORMATION fai;\n\n    fai.NameInformation.FileNameLength = 0;\n\n    Status = NtQueryInformationFile(h, &iosb, &fai, sizeof(fai), FileAllInformation);\n\n    if (Status != STATUS_SUCCESS && Status != STATUS_BUFFER_OVERFLOW)\n        throw ntstatus_error(Status);\n\n    varbuf<FILE_ALL_INFORMATION> ret;\n\n    ret.buf.resize(offsetof(FILE_ALL_INFORMATION, NameInformation.FileName) + fai.NameInformation.FileNameLength);\n\n    auto& fai2 = *reinterpret_cast<FILE_ALL_INFORMATION*>(ret.buf.data());\n\n    fai2.NameInformation.FileNameLength = ret.buf.size() - offsetof(FILE_ALL_INFORMATION, NameInformation.FileName);\n\n    Status = NtQueryInformationFile(h, &iosb, &fai2, ret.buf.size(), FileAllInformation);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != ret.buf.size())\n        throw formatted_error(\"iosb.Information was {}, expected {}\", iosb.Information, ret.buf.size());\n\n    return ret;\n}\n\ntemplate<typename T>\nT query_information(HANDLE h) {\n    IO_STATUS_BLOCK iosb;\n    NTSTATUS Status;\n    T t;\n    FILE_INFORMATION_CLASS fic;\n\n    if constexpr (is_same_v<T, FILE_BASIC_INFORMATION>)\n        fic = FileBasicInformation;\n    else if constexpr (is_same_v<T, FILE_STANDARD_INFORMATION>)\n        fic = FileStandardInformation;\n    else if constexpr (is_same_v<T, FILE_ACCESS_INFORMATION>)\n        fic = FileAccessInformation;\n    else if constexpr (is_same_v<T, FILE_MODE_INFORMATION>)\n        fic = FileModeInformation;\n    else if constexpr (is_same_v<T, FILE_ALIGNMENT_INFORMATION>)\n        fic = FileAlignmentInformation;\n    else if constexpr (is_same_v<T, FILE_POSITION_INFORMATION>)\n        fic = FilePositionInformation;\n    else if constexpr (is_same_v<T, FILE_INTERNAL_INFORMATION>)\n        fic = FileInternalInformation;\n    else if constexpr (is_same_v<T, FILE_CASE_SENSITIVE_INFORMATION>)\n        fic = FileCaseSensitiveInformation;\n    else if constexpr (is_same_v<T, FILE_EA_INFORMATION>)\n        fic = FileEaInformation;\n    else if constexpr (is_same_v<T, FILE_STAT_INFORMATION>)\n        fic = FileStatInformation;\n    else if constexpr (is_same_v<T, FILE_STAT_LX_INFORMATION>)\n        fic = FileStatLxInformation;\n    else if constexpr (is_same_v<T, FILE_ATTRIBUTE_TAG_INFORMATION>)\n        fic = FileAttributeTagInformation;\n    else if constexpr (is_same_v<T, FILE_COMPRESSION_INFORMATION>)\n        fic = FileCompressionInformation;\n    else if constexpr (is_same_v<T, FILE_NETWORK_OPEN_INFORMATION>)\n        fic = FileNetworkOpenInformation;\n    else if constexpr (is_same_v<T, FILE_STANDARD_LINK_INFORMATION>)\n        fic = FileStandardLinkInformation;\n    else if constexpr (is_same_v<T, FILE_ID_INFORMATION>)\n        fic = FileIdInformation;\n    else if constexpr (is_same_v<T, FILE_STANDARD_INFORMATION_EX>)\n        fic = FileStandardInformation;\n    else\n        throw runtime_error(\"Unrecognized file information class.\");\n\n    Status = NtQueryInformationFile(h, &iosb, &t, sizeof(t), fic);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != sizeof(t))\n        throw formatted_error(\"iosb.Information was {}, expected {}\", iosb.Information, sizeof(t));\n\n    return t;\n}\n\ntemplate FILE_BASIC_INFORMATION query_information<FILE_BASIC_INFORMATION>(HANDLE h);\ntemplate FILE_STANDARD_INFORMATION query_information<FILE_STANDARD_INFORMATION>(HANDLE h);\ntemplate FILE_ACCESS_INFORMATION query_information<FILE_ACCESS_INFORMATION>(HANDLE h);\ntemplate FILE_MODE_INFORMATION query_information<FILE_MODE_INFORMATION>(HANDLE h);\ntemplate FILE_ALIGNMENT_INFORMATION query_information<FILE_ALIGNMENT_INFORMATION>(HANDLE h);\ntemplate FILE_POSITION_INFORMATION query_information<FILE_POSITION_INFORMATION>(HANDLE h);\ntemplate FILE_INTERNAL_INFORMATION query_information<FILE_INTERNAL_INFORMATION>(HANDLE h);\ntemplate FILE_CASE_SENSITIVE_INFORMATION query_information<FILE_CASE_SENSITIVE_INFORMATION>(HANDLE h);\ntemplate FILE_EA_INFORMATION query_information<FILE_EA_INFORMATION>(HANDLE h);\ntemplate FILE_STAT_INFORMATION query_information<FILE_STAT_INFORMATION>(HANDLE h);\ntemplate FILE_STAT_LX_INFORMATION query_information<FILE_STAT_LX_INFORMATION>(HANDLE h);\ntemplate FILE_ATTRIBUTE_TAG_INFORMATION query_information<FILE_ATTRIBUTE_TAG_INFORMATION>(HANDLE h);\ntemplate FILE_COMPRESSION_INFORMATION query_information<FILE_COMPRESSION_INFORMATION>(HANDLE h);\ntemplate FILE_NETWORK_OPEN_INFORMATION query_information<FILE_NETWORK_OPEN_INFORMATION>(HANDLE h);\ntemplate FILE_STANDARD_LINK_INFORMATION query_information<FILE_STANDARD_LINK_INFORMATION>(HANDLE h);\ntemplate FILE_ID_INFORMATION query_information<FILE_ID_INFORMATION>(HANDLE h);\ntemplate FILE_STANDARD_INFORMATION_EX query_information<FILE_STANDARD_INFORMATION_EX>(HANDLE h);\n\ntemplate<typename T>\nvector<varbuf<T>> query_dir(const u16string& dir, u16string_view filter) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    unique_handle dh;\n    vector<uint8_t> buf(sizeof(T) + 7);\n    bool first = true;\n    vector<varbuf<T>> ret;\n    FILE_INFORMATION_CLASS fic;\n    UNICODE_STRING us;\n    size_t off;\n\n    if constexpr (is_same_v<T, FILE_DIRECTORY_INFORMATION>)\n        fic = FileDirectoryInformation;\n    else if constexpr (is_same_v<T, FILE_BOTH_DIR_INFORMATION>)\n        fic = FileBothDirectoryInformation;\n    else if constexpr (is_same_v<T, FILE_FULL_DIR_INFORMATION>)\n        fic = FileFullDirectoryInformation;\n    else if constexpr (is_same_v<T, FILE_ID_BOTH_DIR_INFORMATION>)\n        fic = FileIdBothDirectoryInformation;\n    else if constexpr (is_same_v<T, FILE_ID_FULL_DIR_INFORMATION>)\n        fic = FileIdFullDirectoryInformation;\n    else if constexpr (is_same_v<T, FILE_ID_EXTD_DIR_INFORMATION>)\n        fic = FileIdExtdDirectoryInformation;\n    else if constexpr (is_same_v<T, FILE_ID_EXTD_BOTH_DIR_INFORMATION>)\n        fic = FileIdExtdBothDirectoryInformation;\n    else if constexpr (is_same_v<T, FILE_NAMES_INFORMATION>)\n        fic = FileNamesInformation;\n    else if constexpr (is_same_v<T, FILE_REPARSE_POINT_INFORMATION>)\n        fic = FileReparsePointInformation;\n    else\n        throw runtime_error(\"Unrecognized file information class.\");\n\n    // buffer needs to be aligned to 8 bytes\n    off = 8 - ((uintptr_t)buf.data() % 8);\n\n    if (off == 8)\n        off = 0;\n\n    if (!filter.empty()) {\n        us.Buffer = (WCHAR*)filter.data();\n        us.Length = us.MaximumLength = filter.size() * sizeof(char16_t);\n    }\n\n    dh = create_file(dir, SYNCHRONIZE | FILE_LIST_DIRECTORY, 0, 0, FILE_OPEN,\n                     FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT,\n                     FILE_OPENED);\n\n    while (true) {\n        Status = NtQueryDirectoryFile(dh.get(), nullptr, nullptr, nullptr, &iosb,\n                                      buf.data() + off, buf.size() - off, fic, false,\n                                      !filter.empty() ? &us : nullptr, first);\n\n        if constexpr (!is_same_v<T, FILE_REPARSE_POINT_INFORMATION>) {\n            if (Status == STATUS_BUFFER_OVERFLOW) {\n                size_t new_size;\n\n                new_size = offsetof(T, FileName) + (256 * sizeof(WCHAR));\n                new_size += ((T*)(buf.data() + off))->FileNameLength * sizeof(WCHAR);\n\n                buf.resize(new_size + 7);\n\n                off = 8 - ((uintptr_t)buf.data() % 8);\n\n                if (off == 8)\n                    off = 0;\n\n                Status = NtQueryDirectoryFile(dh.get(), nullptr, nullptr, nullptr, &iosb,\n                                              buf.data() + off, buf.size() - off, fic, false,\n                                              !filter.empty() ? &us : nullptr, first);\n            }\n        }\n\n        if (Status == STATUS_NO_MORE_FILES)\n            break;\n\n        if (Status != STATUS_SUCCESS)\n            throw ntstatus_error(Status);\n\n        auto ptr = (T*)buf.data();\n\n        do {\n            varbuf<T> item;\n\n            if constexpr (is_same_v<T, FILE_REPARSE_POINT_INFORMATION>)\n                item.buf.resize(sizeof(T));\n            else\n                item.buf.resize(offsetof(T, FileName) + (ptr->FileNameLength * sizeof(WCHAR)));\n\n            memcpy(item.buf.data(), ptr, item.buf.size());\n\n            ret.emplace_back(item);\n\n            if constexpr (is_same_v<T, FILE_REPARSE_POINT_INFORMATION>)\n                break;\n            else {\n                if (ptr->NextEntryOffset == 0)\n                    break;\n\n                ptr = (T*)((uint8_t*)ptr + ptr->NextEntryOffset);\n            }\n        } while (true);\n\n        first = false;\n    }\n\n    return ret;\n}\n\ntemplate vector<varbuf<FILE_DIRECTORY_INFORMATION>> query_dir(const u16string& dir, u16string_view filter);\ntemplate vector<varbuf<FILE_BOTH_DIR_INFORMATION>> query_dir(const u16string& dir, u16string_view filter);\ntemplate vector<varbuf<FILE_FULL_DIR_INFORMATION>> query_dir(const u16string& dir, u16string_view filter);\ntemplate vector<varbuf<FILE_ID_BOTH_DIR_INFORMATION>> query_dir(const u16string& dir, u16string_view filter);\ntemplate vector<varbuf<FILE_ID_FULL_DIR_INFORMATION>> query_dir(const u16string& dir, u16string_view filter);\ntemplate vector<varbuf<FILE_ID_EXTD_DIR_INFORMATION>> query_dir(const u16string& dir, u16string_view filter);\ntemplate vector<varbuf<FILE_ID_EXTD_BOTH_DIR_INFORMATION>> query_dir(const u16string& dir, u16string_view filter);\ntemplate vector<varbuf<FILE_NAMES_INFORMATION>> query_dir(const u16string& dir, u16string_view filter);\ntemplate vector<varbuf<FILE_REPARSE_POINT_INFORMATION>> query_dir(const u16string& dir, u16string_view filter);\n\ntemplate<typename... Args>\nvoid print(string_view s, Args&&... args) {\n    auto msg = std::vformat(s, std::make_format_args(args...));\n\n    cout << msg;\n}\n\nvoid test(const string& msg, const function<void()>& func) {\n    string err;\n    CONSOLE_SCREEN_BUFFER_INFO csbi;\n\n    num_tests_run++;\n\n    try {\n        func();\n    } catch (const exception& e) {\n        err = e.what();\n    } catch (...) {\n        err = \"Uncaught exception.\";\n    }\n\n    // FIXME - aligned output?\n\n    print(\"{}, \", msg);\n\n    auto col = GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);\n\n    if (col)\n        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), err.empty() ? FOREGROUND_GREEN : (FOREGROUND_RED | FOREGROUND_INTENSITY));\n\n    print(\"{}\", err.empty() ? \"PASS\" : \"FAIL\");\n\n    if (col)\n        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), csbi.wAttributes);\n\n    if (!err.empty())\n        print(\" ({})\", err);\n    else\n        num_tests_passed++;\n\n    print(\"\\n\");\n}\n\nvoid exp_status(const function<void()>& func, NTSTATUS Status) {\n    try {\n        func();\n    } catch (const ntstatus_error& e) {\n        if (e.Status != Status)\n            throw formatted_error(\"Status was {}, expected {}\", ntstatus_to_string(e.Status), ntstatus_to_string(Status));\n        else\n            return;\n    }\n\n    if (Status != STATUS_SUCCESS)\n        throw formatted_error(\"Status was STATUS_SUCCESS, expected {}\", ntstatus_to_string(Status));\n}\n\nu16string query_file_name_information(HANDLE h, bool normalized) {\n    IO_STATUS_BLOCK iosb;\n    NTSTATUS Status;\n    FILE_NAME_INFORMATION fni;\n\n    fni.FileNameLength = 0;\n\n    Status = NtQueryInformationFile(h, &iosb, &fni, sizeof(fni),\n                                    normalized ? FileNormalizedNameInformation : FileNameInformation);\n\n    if (Status != STATUS_SUCCESS && Status != STATUS_BUFFER_OVERFLOW)\n        throw ntstatus_error(Status);\n\n    vector<uint8_t> buf(offsetof(FILE_NAME_INFORMATION, FileName) + fni.FileNameLength);\n\n    auto& fni2 = *reinterpret_cast<FILE_NAME_INFORMATION*>(buf.data());\n\n    fni2.FileNameLength = buf.size() - offsetof(FILE_NAME_INFORMATION, FileName);\n\n    Status = NtQueryInformationFile(h, &iosb, &fni2, buf.size(),\n                                    normalized ? FileNormalizedNameInformation : FileNameInformation);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    if (iosb.Information != buf.size())\n        throw formatted_error(\"iosb.Information was {}, expected {}\", iosb.Information, buf.size());\n\n    u16string ret;\n\n    ret.resize(fni.FileNameLength / sizeof(char16_t));\n\n    memcpy(ret.data(), fni2.FileName, fni.FileNameLength);\n\n    return ret;\n}\n\nstatic unique_handle open_process_token(HANDLE process, ACCESS_MASK access) {\n    NTSTATUS Status;\n    HANDLE h;\n\n    Status = NtOpenProcessToken(process, access, &h);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    return unique_handle(h);\n}\n\nvoid disable_token_privileges(HANDLE token) {\n    NTSTATUS Status;\n\n    Status = NtAdjustPrivilegesToken(token, true, nullptr, 0, nullptr, nullptr);\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n}\n\nstring u16string_to_string(u16string_view sv) {\n    if (sv.empty())\n        return \"\";\n\n    auto len = WideCharToMultiByte(CP_ACP, 0, (WCHAR*)sv.data(), sv.length(), nullptr, 0, nullptr, nullptr);\n    if (len == 0)\n        throw formatted_error(\"WideCharToMultiByte failed (error {})\", GetLastError());\n\n    string s(len, 0);\n\n    if (WideCharToMultiByte(CP_ACP, 0, (WCHAR*)sv.data(), sv.length(), s.data(), s.length(), nullptr, nullptr) == 0)\n        throw formatted_error(\"WideCharToMultiByte failed (error {})\", GetLastError());\n\n    return s;\n}\n\nstatic void do_tests(u16string_view name, const u16string& dir) {\n    auto token = open_process_token(NtCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_ADJUST_DEFAULT |\n                                                        TOKEN_DUPLICATE | TOKEN_QUERY);\n\n    disable_token_privileges(token.get());\n\n    static const struct {\n        u16string_view name;\n        function<void()> func;\n    } testfuncs[] = {\n        { u\"create\", [&]() { test_create(token.get(), dir); } },\n        { u\"supersede\", [&]() { test_supersede(dir); } },\n        { u\"overwrite\", [&]() { test_overwrite(dir); } },\n        { u\"open_id\", [&]() { test_open_id(token.get(), dir); } },\n        { u\"io\", [&]() { test_io(token.get(), dir); } },\n        { u\"mmap\", [&]() { test_mmap(dir); } },\n        { u\"rename\", [&]() { test_rename(dir); } },\n        { u\"rename_ex\", [&]() { test_rename_ex(token.get(), dir); } },\n        { u\"delete\", [&]() { test_delete(dir); } },\n        { u\"delete_ex\", [&]() { test_delete_ex(token.get(), dir); } },\n        { u\"links\", [&]() { test_links(token.get(), dir); } },\n        { u\"links_ex\", [&]() { test_links_ex(token.get(), dir); } },\n        { u\"oplock_i\", [&]() { test_oplocks_i(token.get(), dir); } },\n        { u\"oplock_ii\", [&]() { test_oplocks_ii(token.get(), dir); } },\n        { u\"oplock_batch\", [&]() { test_oplocks_batch(token.get(), dir); } },\n        { u\"oplock_filter\", [&]() { test_oplocks_filter(token.get(), dir); } },\n        { u\"oplock_r\", [&]() { test_oplocks_r(token.get(), dir); } },\n        { u\"oplock_rw\", [&]() { test_oplocks_rw(token.get(), dir); } },\n        { u\"oplock_rh\", [&]() { test_oplocks_rh(token.get(), dir); } },\n        { u\"oplock_rwh\", [&]() { test_oplocks_rwh(token.get(), dir); } },\n        { u\"cs\", [&]() { test_cs(dir); } },\n        { u\"reparse\", [&]() { test_reparse(token.get(), dir); } },\n        { u\"streams\", [&]() { test_streams(dir); } },\n        { u\"ea\", [&]() { test_ea(dir); } },\n        { u\"fileinfo\", [&]() { test_fileinfo(dir); } },\n        { u\"security\", [&]() { test_security(token.get(), dir); } }\n    };\n\n    bool first = true;\n    unsigned int total_tests_run = 0, total_tests_passed = 0;\n\n    for (const auto& tf : testfuncs) {\n        if (name == u\"all\" || tf.name == name) {\n            CONSOLE_SCREEN_BUFFER_INFO csbi;\n\n            auto col = GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);\n\n            if (col) {\n                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),\n                                        FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE | FOREGROUND_INTENSITY);\n            }\n\n            if (!first)\n                print(\"\\n\");\n\n            print(\"Running test {}\\n\", u16string_to_string(tf.name));\n\n            if (col)\n                SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), csbi.wAttributes);\n\n            num_tests_run = 0;\n            num_tests_passed = 0;\n\n            tf.func();\n\n            total_tests_run += num_tests_run;\n            total_tests_passed += num_tests_passed;\n\n            print(\"Passed {}/{}\\n\", num_tests_passed, num_tests_run);\n\n            first = false;\n\n            if (name != u\"all\")\n                break;\n        }\n    }\n\n    // FIXME - test that FILE_SYNCHRONOUS_IO_NONALERT and FILE_SYNCHRONOUS_IO_ALERT need SYNCHRONIZE\n    // FIXME - test opening file with RootDirectory handle\n\n    // FIXME - querying directory (inc. specific files)\n    // FIXME - NtQueryDirectoryFileEx\n    // FIXME - directory notifications\n\n    // FIXME - IOCTLs and FSCTLs\n\n    // FIXME - querying volume info\n    // FIXME - setting volume label\n\n    // FIXME - locking\n\n    // FIXME - object IDs\n\n    // FIXME - IO completions?\n\n    // FIXME - share access\n\n    // FIXME - reflink copies\n    // FIXME - creating subvols\n    // FIXME - snapshots\n    // FIXME - sending and receiving(?)\n    // FIXME - using mknod etc. to test mapping between Linux and Windows concepts?\n\n    if (name != u\"all\" && first)\n        throw runtime_error(\"Test not supported.\");\n\n    if (name == u\"all\")\n        print(\"\\nTotal passed {}/{}\\n\", total_tests_passed, total_tests_run);\n}\n\nstatic u16string to_u16string(time_t n) {\n    u16string s;\n\n    while (n > 0) {\n        s += (n % 10) + u'0';\n        n /= 10;\n    }\n\n    return u16string(s.rbegin(), s.rend());\n}\n\nstatic bool fs_driver_path(HANDLE h, u16string_view driver) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    vector<uint8_t> buf(offsetof(FILE_FS_DRIVER_PATH_INFORMATION, DriverName) + (driver.size() * sizeof(char16_t)));\n\n    auto& ffdpi = *(FILE_FS_DRIVER_PATH_INFORMATION*)buf.data();\n\n    ffdpi.DriverInPath = false;\n    ffdpi.DriverNameLength = driver.size() * sizeof(char16_t);\n    memcpy(&ffdpi.DriverName, driver.data(), ffdpi.DriverNameLength);\n\n    Status = NtQueryVolumeInformationFile(h, &iosb, &ffdpi, buf.size(), FileFsDriverPathInformation);\n\n    if (Status == STATUS_OBJECT_NAME_NOT_FOUND) // driver not loaded\n        return false;\n\n    if (Status != STATUS_SUCCESS)\n        throw ntstatus_error(Status);\n\n    return ffdpi.DriverInPath;\n}\n\nclass sc_handle_closer {\npublic:\n    typedef SC_HANDLE pointer;\n\n    void operator()(SC_HANDLE h) {\n        CloseServiceHandle(h);\n    }\n};\n\nstatic optional<u16string> get_environment_variable(const u16string& name) {\n   auto len = GetEnvironmentVariableW((WCHAR*)name.c_str(), nullptr, 0);\n\n   if (len == 0) {\n       if (GetLastError() == ERROR_ENVVAR_NOT_FOUND)\n           return nullopt;\n\n       return u\"\";\n   }\n\n   u16string ret(len, 0);\n\n   if (GetEnvironmentVariableW((WCHAR*)name.c_str(), (WCHAR*)ret.data(), len) == 0)\n       throw formatted_error(\"GetEnvironmentVariable failed (error {})\", GetLastError());\n\n   while (!ret.empty() && ret.back() == 0) {\n       ret.pop_back();\n   }\n\n   return ret;\n}\n\nstatic u16string get_driver_path(const u16string& driver) {\n    unique_ptr<SC_HANDLE, sc_handle_closer> sc_manager, service;\n\n    sc_manager.reset(OpenSCManagerW(nullptr, nullptr, SC_MANAGER_CONNECT));\n    if (!sc_manager)\n        throw formatted_error(\"OpenSCManager failed (error {})\", GetLastError());\n\n    service.reset(OpenServiceW(sc_manager.get(), (WCHAR*)driver.c_str(), SERVICE_QUERY_CONFIG));\n    if (!service)\n        throw formatted_error(\"OpenService failed (error {})\", GetLastError());\n\n    vector<uint8_t> buf(sizeof(QUERY_SERVICE_CONFIGW));\n    DWORD needed;\n\n    if (!QueryServiceConfigW(service.get(), (QUERY_SERVICE_CONFIGW*)buf.data(), buf.size(), &needed)) {\n        if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)\n            throw formatted_error(\"QueryServiceConfig failed (error {})\", GetLastError());\n\n        buf.resize(needed);\n\n        if (!QueryServiceConfigW(service.get(), (QUERY_SERVICE_CONFIGW*)buf.data(), buf.size(), &needed))\n            throw formatted_error(\"QueryServiceConfig failed (error {})\", GetLastError());\n    }\n\n    auto& qsc = *(QUERY_SERVICE_CONFIGW*)buf.data();\n\n    u16string path = (char16_t*)qsc.lpBinaryPathName;\n\n    if (path.empty()) // if the bootloader has sorted it out\n        path = u\"\\\\SystemRoot\\\\System32\\\\drivers\\\\\" + driver + u\".sys\";\n\n    if (path.substr(0, 12) == u\"\\\\SystemRoot\\\\\") { // FIXME - case-insensitive?\n        auto sr = get_environment_variable(u\"SystemRoot\");\n\n        // FIXME - get from \\\\SystemRoot symlink instead?\n\n        if (!sr.has_value())\n            throw runtime_error(\"SystemRoot environment variable not set.\");\n\n        path = sr.value() + u\"\\\\\" + path.substr(12);\n    }\n\n    if (path.substr(0, 4) == u\"\\\\??\\\\\")\n        path = path.substr(4);\n\n    return path;\n}\n\nstatic string get_version(const u16string& fn) {\n    DWORD dummy;\n\n    auto len = GetFileVersionInfoSizeW((WCHAR*)fn.c_str(), &dummy);\n    if (len == 0)\n        throw formatted_error(\"GetFileVersionInfoSize failed (error {})\", GetLastError());\n\n    vector<uint8_t> buf(len);\n\n    if (!GetFileVersionInfoW((WCHAR*)fn.c_str(), 0, buf.size(), buf.data()))\n        throw formatted_error(\"GetFileVersionInfo failed (error {})\", GetLastError());\n\n    VS_FIXEDFILEINFO* ver;\n    UINT verlen;\n\n    if (!VerQueryValueW(buf.data(), L\"\\\\\", (void**)&ver, &verlen))\n        throw runtime_error(\"VerQueryValue failed\");\n\n    return std::format(\"{}.{}.{}.{}\", ver->dwFileVersionMS >> 16, ver->dwFileVersionMS & 0xffff,\n                       ver->dwFileVersionLS >> 16, ver->dwFileVersionLS & 0xffff);\n}\n\nstatic string driver_string(const u16string& driver) {\n    try {\n        auto path = get_driver_path(driver);\n\n        auto version = get_version(path);\n\n        return u16string_to_string(path) + \", \" + version;\n    } catch (const exception& e) {\n        return e.what();\n    }\n}\n\nint wmain(int argc, wchar_t* argv[]) {\n    if (argc < 2) {\n        cerr << \"Usage: test.exe <dir>\\n       test.exe <test> <dir>\";\n        return 1;\n    }\n\n    try {\n        u16string_view dirarg = (char16_t*)(argc < 3 ? argv[1] : argv[2]);\n\n        while (!dirarg.empty() && dirarg.back() == u'\\\\') {\n            dirarg.remove_suffix(1);\n        }\n\n        u16string ntdir = u\"\\\\??\\\\\"s + u16string(dirarg);\n        ntdir += u\"\\\\\" + to_u16string(time(nullptr));\n\n        unique_handle dirh;\n\n        try {\n            dirh = create_file(ntdir, GENERIC_WRITE, 0, 0, FILE_CREATE, FILE_DIRECTORY_FILE, FILE_CREATED);\n        } catch (const exception& e) {\n            throw runtime_error(\"Error creating directory: \"s + e.what());\n        }\n\n        bool type_lookup_failed = false;\n\n        fstype = fs_type::unknown;\n\n        try {\n            /* See lie_about_fs_type() for why we can't use FileFsAttributeInformation. */\n\n            if (fs_driver_path(dirh.get(), u\"\\\\FileSystem\\\\NTFS\"))\n                fstype = fs_type::ntfs;\n            else if (fs_driver_path(dirh.get(), u\"\\\\Driver\\\\btrfs\"))\n                fstype = fs_type::btrfs;\n        } catch (const exception& e) {\n            cerr << \"Error getting filesystem type: \" << e.what() << endl;\n            type_lookup_failed = true;\n        }\n\n        dirh.reset();\n\n        if (!type_lookup_failed) {\n            switch (fstype) {\n                case fs_type::ntfs:\n                    print(\"Testing on NTFS ({}).\\n\", driver_string(u\"ntfs\"));\n                    break;\n\n                case fs_type::btrfs:\n                    print(\"Testing on Btrfs ({}).\\n\", driver_string(u\"btrfs\"));\n                    break;\n\n                default:\n                    print(\"Testing on unknown filesystem.\\n\");\n                    break;\n            }\n        }\n\n        u16string_view testarg = argc < 3 ? u\"all\" : (char16_t*)argv[1];\n\n        do_tests(testarg, ntdir);\n    } catch (const exception& e) {\n        cerr << e.what() << endl;\n        return 1;\n    }\n\n    return 0;\n}\n"
  },
  {
    "path": "src/tests/test.h",
    "content": "/* Copyright (c) Mark Harmstone 2021\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#pragma once\n\n#include <stdio.h>\n#include <stdarg.h>\n#include <stdlib.h>\n#include <stdbool.h>\n#include <ntstatus.h>\n#define WIN32_NO_STATUS\n#include <windef.h>\n#include <winbase.h>\n#include <winternl.h>\n#include <devioctl.h>\n#include <ntdddisk.h>\n#include <stringapiset.h>\n#include <memory>\n#include <stdexcept>\n#include <string>\n#include <optional>\n#include <span>\n#include <vector>\n#include <functional>\n#include <format>\n\nenum class fs_type {\n    unknown,\n    ntfs,\n    btrfs\n};\n\nextern \"C\"\nNTSTATUS __stdcall NtQueryDirectoryFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine,\n                                        PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock,\n                                        PVOID FileInformation, ULONG Length,\n                                        FILE_INFORMATION_CLASS FileInformationClass,\n                                        BOOLEAN ReturnSingleEntry, PUNICODE_STRING FileName,\n                                        BOOLEAN RestartScan);\n\nextern \"C\"\nNTSTATUS __stdcall NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine,\n                              PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer,\n                              ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key);\n\nextern \"C\"\nNTSTATUS __stdcall NtWriteFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine,\n                               PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer,\n                               ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key);\n\nextern \"C\"\nNTSTATUS __stdcall NtOpenProcessToken(HANDLE ProcessHandle, ACCESS_MASK DesiredAccess,\n                                      PHANDLE TokenHandle);\n\nextern \"C\"\nNTSTATUS __stdcall NtAdjustPrivilegesToken(HANDLE TokenHandle, BOOLEAN DisableAllPrivileges,\n                                           PTOKEN_PRIVILEGES NewState, ULONG BufferLength,\n                                           PTOKEN_PRIVILEGES PreviousState, PULONG ReturnLength);\n\ntypedef enum _EVENT_TYPE {\n    NotificationEvent,\n    SynchronizationEvent\n} EVENT_TYPE;\n\nextern \"C\"\nNTSTATUS __stdcall NtCreateEvent(PHANDLE EventHandle, ACCESS_MASK DesiredAccess,\n                                 POBJECT_ATTRIBUTES ObjectAttributes, EVENT_TYPE EventType,\n                                 BOOLEAN InitialState);\n\nextern \"C\"\nNTSTATUS __stdcall NtCreateSection(PHANDLE SectionHandle, ACCESS_MASK DesiredAccess,\n                                   POBJECT_ATTRIBUTES ObjectAttributes, PLARGE_INTEGER MaximumSize,\n                                   ULONG SectionPageProtection, ULONG AllocationAttributes,\n                                   HANDLE FileHandle);\n\ntypedef enum _SECTION_INHERIT {\n    ViewShare = 1,\n    ViewUnmap\n} SECTION_INHERIT;\n\nextern \"C\"\nNTSTATUS __stdcall NtMapViewOfSection(HANDLE SectionHandle, HANDLE ProcessHandle, PVOID* BaseAddress,\n                                      ULONG_PTR ZeroBits, SIZE_T CommitSize, PLARGE_INTEGER SectionOffset,\n                                      PSIZE_T ViewSize, SECTION_INHERIT InheritDisposition,\n                                      ULONG AllocationType, ULONG Protect);\n\nextern \"C\"\nNTSTATUS __stdcall NtUnmapViewOfSection(HANDLE ProcessHandle, PVOID BaseAddress);\n\nextern \"C\"\nNTSTATUS __stdcall NtLockFile(HANDLE FileHandle, HANDLE Event OPTIONAL, PIO_APC_ROUTINE ApcRoutine,\n                              PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER ByteOffset,\n                              PLARGE_INTEGER Length, ULONG Key, BOOLEAN FailImmediately,\n                              BOOLEAN ExclusiveLock);\n\nextern \"C\"\nNTSTATUS __stdcall NtUnlockFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PLARGE_INTEGER ByteOffset,\n                                PLARGE_INTEGER Length, ULONG Key);\n\nextern \"C\"\nNTSTATUS __stdcall NtQuerySecurityObject(HANDLE Handle, SECURITY_INFORMATION SecurityInformation,\n                                         PSECURITY_DESCRIPTOR SecurityDescriptor, ULONG Length,\n                                         PULONG LengthNeeded);\n\nextern \"C\"\nNTSTATUS __stdcall NtSetSecurityObject(HANDLE Handle, SECURITY_INFORMATION SecurityInformation,\n                                       PSECURITY_DESCRIPTOR SecurityDescriptor);\n\ntypedef enum _EVENT_INFORMATION_CLASS {\n    EventBasicInformation\n} EVENT_INFORMATION_CLASS, *PEVENT_INFORMATION_CLASS;\n\ntypedef struct _EVENT_BASIC_INFORMATION {\n    EVENT_TYPE EventType;\n    LONG EventState;\n} EVENT_BASIC_INFORMATION, *PEVENT_BASIC_INFORMATION;\n\nextern \"C\"\nNTSTATUS __stdcall NtQueryEvent(HANDLE EventHandle, EVENT_INFORMATION_CLASS EventInformationClass,\n                                PVOID EventInformation, ULONG EventInformationLength,\n                                PULONG ReturnLength);\n\nextern \"C\"\nNTSTATUS __stdcall NtQueryEaFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer,\n                                 ULONG Length, BOOLEAN ReturnSingleEntry, PVOID EaList,\n                                 ULONG EaListLength, PULONG EaIndex, BOOLEAN RestartScan);\n\nextern \"C\"\nNTSTATUS __stdcall NtSetEaFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer,\n                               ULONG Length);\n\nextern \"C\"\nNTSTATUS __stdcall NtSetInformationToken(HANDLE TokenHandle, TOKEN_INFORMATION_CLASS TokenInformationClass,\n                                         PVOID TokenInformation, ULONG TokenInformationLength);\n\nextern \"C\"\nNTSTATUS __stdcall NtDuplicateToken(HANDLE ExistingTokenHandle, ACCESS_MASK DesiredAccess,\n                                    POBJECT_ATTRIBUTES ObjectAttributes, BOOLEAN EffectiveOnly,\n                                    TOKEN_TYPE TokenType, PHANDLE NewTokenHandle);\n\nextern \"C\"\nNTSTATUS __stdcall NtSetInformationThread(HANDLE ThreadHandle, THREADINFOCLASS ThreadInformationClass,\n                                          PVOID ThreadInformation, ULONG ThreadInformationLength);\n\n#define FILE_NEED_EA    0x00000080\n\ntypedef struct _FILE_GET_EA_INFORMATION {\n    ULONG NextEntryOffset;\n    UCHAR EaNameLength;\n    CHAR EaName[1];\n} FILE_GET_EA_INFORMATION, *PFILE_GET_EA_INFORMATION;\n\n#define NtCurrentThread() ((HANDLE)(LONG_PTR) -2)\n#define NtCurrentProcess() ((HANDLE)(LONG_PTR) -1)\n\n#define FileIdInformation ((FILE_INFORMATION_CLASS)59)\n#define FileIdExtdDirectoryInformation ((FILE_INFORMATION_CLASS)60)\n#define FileIdExtdBothDirectoryInformation ((FILE_INFORMATION_CLASS)63)\n#define FileDispositionInformationEx ((FILE_INFORMATION_CLASS)64)\n#define FileRenameInformationEx ((FILE_INFORMATION_CLASS)65)\n#define FileCaseSensitiveInformation ((FILE_INFORMATION_CLASS)71)\n#define FileLinkInformationEx ((FILE_INFORMATION_CLASS)72)\n\n#define FILE_WORD_ALIGNMENT 0x00000001\n\n#define SE_SECURITY_PRIVILEGE               8\n#define SE_RESTORE_PRIVILEGE                18\n#define SE_CHANGE_NOTIFY_PRIVILEGE          23\n#define SE_MANAGE_VOLUME_PRIVILEGE          28\n#define SE_CREATE_SYMBOLIC_LINK_PRIVILEGE   35\n\n#define FILE_USE_FILE_POINTER_POSITION 0xfffffffe\n#define FILE_WRITE_TO_END_OF_FILE 0xffffffff\n\n#define FILE_RENAME_REPLACE_IF_EXISTS         0x00000001\n#define FILE_RENAME_POSIX_SEMANTICS           0x00000002\n#define FILE_RENAME_IGNORE_READONLY_ATTRIBUTE 0x00000040\n\n#define FILE_LINK_REPLACE_IF_EXISTS           0x00000001\n#define FILE_LINK_POSIX_SEMANTICS             0x00000002\n#define FILE_LINK_IGNORE_READONLY_ATTRIBUTE   0x00000040\n\ntypedef struct _FILE_FS_DRIVER_PATH_INFORMATION {\n    BOOLEAN DriverInPath;\n    ULONG DriverNameLength;\n    WCHAR DriverName[1];\n} FILE_FS_DRIVER_PATH_INFORMATION, *PFILE_FS_DRIVER_PATH_INFORMATION;\n\n// should be called FILE_RENAME_INFORMATION, version in mingw is outdated\ntypedef struct _FILE_RENAME_INFORMATION_EX {\n    union {\n        BOOLEAN ReplaceIfExists;\n        ULONG Flags;\n    };\n    HANDLE RootDirectory;\n    ULONG FileNameLength;\n    WCHAR FileName[1];\n} FILE_RENAME_INFORMATION_EX, *PFILE_RENAME_INFORMATION_EX;\n\n// should be called FILE_RENAME_INFORMATION_EX, version in mingw is outdated\ntypedef struct _FILE_LINK_INFORMATION_EX {\n    union {\n        BOOLEAN ReplaceIfExists;\n        ULONG Flags;\n    };\n    HANDLE RootDirectory;\n    ULONG FileNameLength;\n    WCHAR FileName[1];\n} FILE_LINK_INFORMATION_EX, *PFILE_LINK_INFORMATION_EX;\n\ntypedef struct _FILE_LINK_ENTRY_INFORMATION {\n    ULONG NextEntryOffset;\n    LONGLONG ParentFileId;\n    ULONG FileNameLength;\n    WCHAR FileName[1];\n} FILE_LINK_ENTRY_INFORMATION, *PFILE_LINK_ENTRY_INFORMATION;\n\ntypedef struct _FILE_LINKS_INFORMATION {\n    ULONG BytesNeeded;\n    ULONG EntriesReturned;\n    FILE_LINK_ENTRY_INFORMATION Entry;\n} FILE_LINKS_INFORMATION, *PFILE_LINKS_INFORMATION;\n\ntypedef struct _FILE_DISPOSITION_INFORMATION_EX {\n    ULONG Flags;\n} FILE_DISPOSITION_INFORMATION_EX, *PFILE_DISPOSITION_INFORMATION_EX;\n\n#define FILE_DISPOSITION_DO_NOT_DELETE              0x00000000\n#define FILE_DISPOSITION_DELETE                     0x00000001\n#define FILE_DISPOSITION_POSIX_SEMANTICS            0x00000002\n#define FILE_DISPOSITION_FORCE_IMAGE_SECTION_CHECK  0x00000004\n#define FILE_DISPOSITION_ON_CLOSE                   0x00000008\n#define FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE  0x00000010\n\ntypedef struct _FILE_ZERO_DATA_INFORMATION {\n    LARGE_INTEGER FileOffset;\n    LARGE_INTEGER BeyondFinalZero;\n} FILE_ZERO_DATA_INFORMATION,*PFILE_ZERO_DATA_INFORMATION;\n\ntypedef struct _FILE_OBJECTID_BUFFER {\n    BYTE ObjectId[16];\n    union {\n        struct {\n            BYTE BirthVolumeId[16];\n            BYTE BirthObjectId[16];\n            BYTE DomainId[16];\n        };\n        BYTE ExtendedInfo[48];\n    };\n} FILE_OBJECTID_BUFFER, *PFILE_OBJECTID_BUFFER;\n\n#define FILE_CS_FLAG_CASE_SENSITIVE_DIR             0x00000001\n\ntypedef struct _FILE_CASE_SENSITIVE_INFORMATION {\n    ULONG Flags;\n} FILE_CASE_SENSITIVE_INFORMATION, *PFILE_CASE_SENSITIVE_INFORMATION;\n\ntypedef struct _REPARSE_DATA_BUFFER {\n    ULONG ReparseTag;\n    USHORT ReparseDataLength;\n    USHORT Reserved;\n    union {\n        struct {\n            USHORT SubstituteNameOffset;\n            USHORT SubstituteNameLength;\n            USHORT PrintNameOffset;\n            USHORT PrintNameLength;\n            ULONG Flags;\n            WCHAR PathBuffer[1];\n        } SymbolicLinkReparseBuffer;\n        struct {\n            USHORT SubstituteNameOffset;\n            USHORT SubstituteNameLength;\n            USHORT PrintNameOffset;\n            USHORT PrintNameLength;\n            WCHAR PathBuffer[1];\n        } MountPointReparseBuffer;\n        struct {\n            UCHAR DataBuffer[1];\n        } GenericReparseBuffer;\n    };\n} REPARSE_DATA_BUFFER, *PREPARSE_DATA_BUFFER;\n\ntypedef struct _FILE_COMPRESSION_INFORMATION {\n    LARGE_INTEGER CompressedFileSize;\n    USHORT CompressionFormat;\n    UCHAR CompressionUnitShift;\n    UCHAR ChunkShift;\n    UCHAR ClusterShift;\n    UCHAR Reserved[3];\n} FILE_COMPRESSION_INFORMATION, *PFILE_COMPRESSION_INFORMATION;\n\ntypedef struct _FILE_STANDARD_INFORMATION_EX {\n    LARGE_INTEGER AllocationSize;\n    LARGE_INTEGER EndOfFile;\n    ULONG NumberOfLinks;\n    BOOLEAN DeletePending;\n    BOOLEAN Directory;\n    BOOLEAN AlternateStream;\n    BOOLEAN MetadataAttribute;\n} FILE_STANDARD_INFORMATION_EX, *PFILE_STANDARD_INFORMATION_EX;\n\nextern \"C\"\nNTSTATUS __stdcall NtDelayExecution(BOOLEAN Alertable, PLARGE_INTEGER DelayInterval);\n\n#ifdef _MSC_VER\n#define FileDirectoryInformation ((FILE_INFORMATION_CLASS)1)\n#define FileFullDirectoryInformation ((FILE_INFORMATION_CLASS)2)\n#define FileBothDirectoryInformation ((FILE_INFORMATION_CLASS)3)\n#define FileBasicInformation ((FILE_INFORMATION_CLASS)4)\n#define FileStandardInformation ((FILE_INFORMATION_CLASS)5)\n#define FileInternalInformation ((FILE_INFORMATION_CLASS)6)\n#define FileEaInformation ((FILE_INFORMATION_CLASS)7)\n#define FileAccessInformation ((FILE_INFORMATION_CLASS)8)\n#define FileNameInformation ((FILE_INFORMATION_CLASS)9)\n#define FileRenameInformation ((FILE_INFORMATION_CLASS)10)\n#define FileLinkInformation ((FILE_INFORMATION_CLASS)11)\n#define FileNamesInformation ((FILE_INFORMATION_CLASS)12)\n#define FileDispositionInformation ((FILE_INFORMATION_CLASS)13)\n#define FilePositionInformation ((FILE_INFORMATION_CLASS)14)\n#define FileFullEaInformation ((FILE_INFORMATION_CLASS)15)\n#define FileModeInformation ((FILE_INFORMATION_CLASS)16)\n#define FileAlignmentInformation ((FILE_INFORMATION_CLASS)17)\n#define FileAllInformation ((FILE_INFORMATION_CLASS)18)\n#define FileAllocationInformation ((FILE_INFORMATION_CLASS)19)\n#define FileEndOfFileInformation ((FILE_INFORMATION_CLASS)20)\n#define FileAlternateNameInformation ((FILE_INFORMATION_CLASS)21)\n#define FileStreamInformation ((FILE_INFORMATION_CLASS)22)\n#define FilePipeInformation ((FILE_INFORMATION_CLASS)23)\n#define FilePipeLocalInformation ((FILE_INFORMATION_CLASS)24)\n#define FilePipeRemoteInformation ((FILE_INFORMATION_CLASS)25)\n#define FileMailslotQueryInformation ((FILE_INFORMATION_CLASS)26)\n#define FileMailslotSetInformation ((FILE_INFORMATION_CLASS)27)\n#define FileCompressionInformation ((FILE_INFORMATION_CLASS)28)\n#define FileObjectIdInformation ((FILE_INFORMATION_CLASS)29)\n#define FileCompletionInformation ((FILE_INFORMATION_CLASS)30)\n#define FileMoveClusterInformation ((FILE_INFORMATION_CLASS)31)\n#define FileQuotaInformation ((FILE_INFORMATION_CLASS)32)\n#define FileReparsePointInformation ((FILE_INFORMATION_CLASS)33)\n#define FileNetworkOpenInformation ((FILE_INFORMATION_CLASS)34)\n#define FileAttributeTagInformation ((FILE_INFORMATION_CLASS)35)\n#define FileTrackingInformation ((FILE_INFORMATION_CLASS)36)\n#define FileIdBothDirectoryInformation ((FILE_INFORMATION_CLASS)37)\n#define FileIdFullDirectoryInformation ((FILE_INFORMATION_CLASS)38)\n#define FileValidDataLengthInformation ((FILE_INFORMATION_CLASS)39)\n#define FileHardLinkInformation ((FILE_INFORMATION_CLASS)46)\n#define FileNormalizedNameInformation ((FILE_INFORMATION_CLASS)48)\n#define FileStandardLinkInformation ((FILE_INFORMATION_CLASS)54)\n\nextern \"C\"\nNTSTATUS NTAPI NtQueryInformationFile(HANDLE hFile, PIO_STATUS_BLOCK io, PVOID ptr,\n                                      ULONG len, FILE_INFORMATION_CLASS FileInformationClass);\n\ntypedef struct _FILE_BASIC_INFORMATION {\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    ULONG FileAttributes;\n} FILE_BASIC_INFORMATION, *PFILE_BASIC_INFORMATION;\n\ntypedef struct _FILE_STANDARD_INFORMATION {\n    LARGE_INTEGER AllocationSize;\n    LARGE_INTEGER EndOfFile;\n    ULONG NumberOfLinks;\n    BOOLEAN DeletePending;\n    BOOLEAN Directory;\n} FILE_STANDARD_INFORMATION, *PFILE_STANDARD_INFORMATION;\n\ntypedef struct _FILE_NAME_INFORMATION {\n    ULONG FileNameLength;\n    WCHAR FileName[1];\n} FILE_NAME_INFORMATION, *PFILE_NAME_INFORMATION;\n\ntypedef struct _FILE_DIRECTORY_INFORMATION {\n    ULONG NextEntryOffset;\n    ULONG FileIndex;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER EndOfFile;\n    LARGE_INTEGER AllocationSize;\n    ULONG FileAttributes;\n    ULONG FileNameLength;\n    WCHAR FileName[ANYSIZE_ARRAY];\n} FILE_DIRECTORY_INFORMATION, *PFILE_DIRECTORY_INFORMATION;\n\ntypedef struct _FILE_FULL_DIR_INFORMATION {\n    ULONG NextEntryOffset;\n    ULONG FileIndex;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER EndOfFile;\n    LARGE_INTEGER AllocationSize;\n    ULONG FileAttributes;\n    ULONG FileNameLength;\n    ULONG EaSize;\n    WCHAR FileName[ANYSIZE_ARRAY];\n} FILE_FULL_DIR_INFORMATION, *PFILE_FULL_DIR_INFORMATION;\n\ntypedef struct _FILE_ID_FULL_DIR_INFORMATION {\n    ULONG NextEntryOffset;\n    ULONG FileIndex;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER EndOfFile;\n    LARGE_INTEGER AllocationSize;\n    ULONG FileAttributes;\n    ULONG FileNameLength;\n    ULONG EaSize;\n    LARGE_INTEGER FileId;\n    WCHAR FileName[ANYSIZE_ARRAY];\n} FILE_ID_FULL_DIR_INFORMATION, *PFILE_ID_FULL_DIR_INFORMATION;\n\ntypedef struct _FILE_BOTH_DIR_INFORMATION {\n    ULONG NextEntryOffset;\n    ULONG FileIndex;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER EndOfFile;\n    LARGE_INTEGER AllocationSize;\n    ULONG FileAttributes;\n    ULONG FileNameLength;\n    ULONG EaSize;\n    CHAR ShortNameLength;\n    WCHAR ShortName[12];\n    WCHAR FileName[ANYSIZE_ARRAY];\n} FILE_BOTH_DIR_INFORMATION, *PFILE_BOTH_DIR_INFORMATION;\n\ntypedef struct _FILE_ID_BOTH_DIR_INFORMATION {\n    ULONG NextEntryOffset;\n    ULONG FileIndex;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER EndOfFile;\n    LARGE_INTEGER AllocationSize;\n    ULONG FileAttributes;\n    ULONG FileNameLength;\n    ULONG EaSize;\n    CHAR ShortNameLength;\n    WCHAR ShortName[12];\n    LARGE_INTEGER FileId;\n    WCHAR FileName[ANYSIZE_ARRAY];\n} FILE_ID_BOTH_DIR_INFORMATION, *PFILE_ID_BOTH_DIR_INFORMATION;\n\ntypedef struct _FILE_NAMES_INFORMATION {\n    ULONG NextEntryOffset;\n    ULONG FileIndex;\n    ULONG FileNameLength;\n    WCHAR FileName[1];\n} FILE_NAMES_INFORMATION, *PFILE_NAMES_INFORMATION;\n\ntypedef struct _OBJECT_BASIC_INFORMATION {\n    ULONG Attributes;\n    ACCESS_MASK GrantedAccess;\n    ULONG HandleCount;\n    ULONG PointerCount;\n    ULONG PagedPoolUsage;\n    ULONG NonPagedPoolUsage;\n    ULONG Reserved[3];\n    ULONG NameInformationLength;\n    ULONG TypeInformationLength;\n    ULONG SecurityDescriptorLength;\n    LARGE_INTEGER CreateTime;\n} OBJECT_BASIC_INFORMATION, *POBJECT_BASIC_INFORMATION;\n\ntypedef struct _FILE_INTERNAL_INFORMATION {\n    LARGE_INTEGER IndexNumber;\n} FILE_INTERNAL_INFORMATION, *PFILE_INTERNAL_INFORMATION;\n\ntypedef struct _FILE_EA_INFORMATION {\n    ULONG EaSize;\n} FILE_EA_INFORMATION, *PFILE_EA_INFORMATION;\n\ntypedef struct _FILE_ACCESS_INFORMATION {\n    ACCESS_MASK AccessFlags;\n} FILE_ACCESS_INFORMATION, *PFILE_ACCESS_INFORMATION;\n\ntypedef struct _FILE_POSITION_INFORMATION {\n    LARGE_INTEGER CurrentByteOffset;\n} FILE_POSITION_INFORMATION, *PFILE_POSITION_INFORMATION;\n\ntypedef struct _FILE_MODE_INFORMATION {\n    ULONG Mode;\n} FILE_MODE_INFORMATION, *PFILE_MODE_INFORMATION;\n\ntypedef struct _FILE_ALIGNMENT_INFORMATION {\n    ULONG AlignmentRequirement;\n} FILE_ALIGNMENT_INFORMATION, *PFILE_ALIGNMENT_INFORMATION;\n\ntypedef struct _FILE_ALL_INFORMATION {\n    FILE_BASIC_INFORMATION BasicInformation;\n    FILE_STANDARD_INFORMATION StandardInformation;\n    FILE_INTERNAL_INFORMATION InternalInformation;\n    FILE_EA_INFORMATION EaInformation;\n    FILE_ACCESS_INFORMATION AccessInformation;\n    FILE_POSITION_INFORMATION PositionInformation;\n    FILE_MODE_INFORMATION ModeInformation;\n    FILE_ALIGNMENT_INFORMATION AlignmentInformation;\n    FILE_NAME_INFORMATION NameInformation;\n} FILE_ALL_INFORMATION, *PFILE_ALL_INFORMATION;\n\ntypedef struct _FILE_ATTRIBUTE_TAG_INFORMATION {\n    ULONG FileAttributes;\n    ULONG ReparseTag;\n} FILE_ATTRIBUTE_TAG_INFORMATION, *PFILE_ATTRIBUTE_TAG_INFORMATION;\n\ntypedef struct _FILE_NETWORK_OPEN_INFORMATION {\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER AllocationSize;\n    LARGE_INTEGER EndOfFile;\n    ULONG FileAttributes;\n} FILE_NETWORK_OPEN_INFORMATION, *PFILE_NETWORK_OPEN_INFORMATION;\n\ntypedef enum _FSINFOCLASS {\n    FileFsVolumeInformation = 1,\n    FileFsLabelInformation,\n    FileFsSizeInformation,\n    FileFsDeviceInformation,\n    FileFsAttributeInformation,\n    FileFsControlInformation,\n    FileFsFullSizeInformation,\n    FileFsObjectIdInformation,\n    FileFsDriverPathInformation,\n    FileFsVolumeFlagsInformation,\n    FileFsSectorSizeInformation,\n    FileFsDataCopyInformation,\n    FileFsMetadataSizeInformation,\n    FileFsFullSizeInformationEx,\n    FileFsMaximumInformation\n} FS_INFORMATION_CLASS, *PFS_INFORMATION_CLASS;\n\ntypedef struct _FILE_DISPOSITION_INFORMATION {\n    BOOLEAN DoDeleteFile;\n} FILE_DISPOSITION_INFORMATION, *PFILE_DISPOSITION_INFORMATION;\n\ntypedef struct _FILE_ALLOCATION_INFORMATION {\n    LARGE_INTEGER AllocationSize;\n} FILE_ALLOCATION_INFORMATION, *PFILE_ALLOCATION_INFORMATION;\n\ntypedef struct _FILE_END_OF_FILE_INFORMATION {\n    LARGE_INTEGER EndOfFile;\n} FILE_END_OF_FILE_INFORMATION, *PFILE_END_OF_FILE_INFORMATION;\n\ntypedef struct _FILE_RENAME_INFORMATION {\n    BOOLEAN ReplaceIfExists;\n    HANDLE RootDirectory;\n    ULONG FileNameLength;\n    WCHAR FileName[1];\n} FILE_RENAME_INFORMATION, *PFILE_RENAME_INFORMATION;\n\ntypedef struct _FILE_LINK_INFORMATION {\n    BOOLEAN ReplaceIfExists;\n    HANDLE RootDirectory;\n    ULONG FileNameLength;\n    WCHAR FileName[1];\n} FILE_LINK_INFORMATION, *PFILE_LINK_INFORMATION;\n\nextern \"C\"\nNTSTATUS __stdcall NtQueryVolumeInformationFile(HANDLE FileHandle, PIO_STATUS_BLOCK IoStatusBlock,\n                                                PVOID FsInformation, ULONG Length,\n                                                FS_INFORMATION_CLASS FsInformationClass);\n\nextern \"C\"\nNTSTATUS __stdcall NtFsControlFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine,\n                                   PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock,\n                                   ULONG FsControlCode, PVOID InputBuffer, ULONG InputBufferLength,\n                                   PVOID OutputBuffer, ULONG OutputBufferLength);\n\nextern \"C\"\nNTSTATUS __stdcall NtSetInformationFile(HANDLE hFile, PIO_STATUS_BLOCK io, PVOID ptr, ULONG len,\n                                        FILE_INFORMATION_CLASS FileInformationClass);\n#endif\n\ntypedef struct _FILE_ID_EXTD_DIR_INFORMATION {\n    ULONG NextEntryOffset;\n    ULONG FileIndex;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER EndOfFile;\n    LARGE_INTEGER AllocationSize;\n    ULONG FileAttributes;\n    ULONG FileNameLength;\n    ULONG EaSize;\n    ULONG ReparsePointTag;\n    FILE_ID_128 FileId;\n    WCHAR FileName[1];\n} FILE_ID_EXTD_DIR_INFORMATION, *PFILE_ID_EXTD_DIR_INFORMATION;\n\ntypedef struct _FILE_ID_EXTD_BOTH_DIR_INFORMATION {\n    ULONG NextEntryOffset;\n    ULONG FileIndex;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER EndOfFile;\n    LARGE_INTEGER AllocationSize;\n    ULONG FileAttributes;\n    ULONG FileNameLength;\n    ULONG EaSize;\n    ULONG ReparsePointTag;\n    FILE_ID_128 FileId;\n    CCHAR ShortNameLength;\n    WCHAR ShortName[12];\n    WCHAR FileName[1];\n} FILE_ID_EXTD_BOTH_DIR_INFORMATION, *PFILE_ID_EXTD_BOTH_DIR_INFORMATION;\n\ntypedef struct _FILE_VALID_DATA_LENGTH_INFORMATION {\n    LARGE_INTEGER ValidDataLength;\n} FILE_VALID_DATA_LENGTH_INFORMATION, *PFILE_VALID_DATA_LENGTH_INFORMATION;\n\ntypedef struct _FILE_REPARSE_POINT_INFORMATION {\n    LONGLONG FileReference;\n    ULONG Tag;\n} FILE_REPARSE_POINT_INFORMATION, *PFILE_REPARSE_POINT_INFORMATION;\n\ntypedef struct _REQUEST_OPLOCK_INPUT_BUFFER {\n    WORD StructureVersion;\n    WORD StructureLength;\n    DWORD RequestedOplockLevel;\n    DWORD Flags;\n} REQUEST_OPLOCK_INPUT_BUFFER, *PREQUEST_OPLOCK_INPUT_BUFFER;\n\ntypedef struct _REQUEST_OPLOCK_OUTPUT_BUFFER {\n    WORD StructureVersion;\n    WORD StructureLength;\n    DWORD OriginalOplockLevel;\n    DWORD NewOplockLevel;\n    DWORD Flags;\n    ACCESS_MASK AccessMode;\n    WORD ShareMode;\n} REQUEST_OPLOCK_OUTPUT_BUFFER, *PREQUEST_OPLOCK_OUTPUT_BUFFER;\n\n#define FileStatInformation ((FILE_INFORMATION_CLASS)68)\n\ntypedef struct _FILE_STAT_INFORMATION {\n    LARGE_INTEGER FileId;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER AllocationSize;\n    LARGE_INTEGER EndOfFile;\n    ULONG FileAttributes;\n    ULONG ReparseTag;\n    ULONG NumberOfLinks;\n    ACCESS_MASK EffectiveAccess;\n} FILE_STAT_INFORMATION, *PFILE_STAT_INFORMATION;\n\n#define FileStatLxInformation ((FILE_INFORMATION_CLASS)70)\n\ntypedef struct _FILE_STAT_LX_INFORMATION {\n    LARGE_INTEGER FileId;\n    LARGE_INTEGER CreationTime;\n    LARGE_INTEGER LastAccessTime;\n    LARGE_INTEGER LastWriteTime;\n    LARGE_INTEGER ChangeTime;\n    LARGE_INTEGER AllocationSize;\n    LARGE_INTEGER EndOfFile;\n    ULONG FileAttributes;\n    ULONG ReparseTag;\n    ULONG NumberOfLinks;\n    ACCESS_MASK EffectiveAccess;\n    ULONG LxFlags;\n    ULONG LxUid;\n    ULONG LxGid;\n    ULONG LxMode;\n    ULONG LxDeviceIdMajor;\n    ULONG LxDeviceIdMinor;\n} FILE_STAT_LX_INFORMATION, *PFILE_STAT_LX_INFORMATION;\n\ntypedef struct _FILE_STANDARD_LINK_INFORMATION {\n    ULONG NumberOfAccessibleLinks;\n    ULONG TotalNumberOfLinks;\n    BOOLEAN DeletePending;\n    BOOLEAN Directory;\n} FILE_STANDARD_LINK_INFORMATION, *PFILE_STANDARD_LINK_INFORMATION;\n\ntypedef struct _FILE_ID_INFORMATION {\n    ULONGLONG VolumeSerialNumber;\n    FILE_ID_128 FileId;\n} FILE_ID_INFORMATION, *PFILE_ID_INFORMATION;\n\nclass handle_closer {\npublic:\n    typedef HANDLE pointer;\n\n    void operator()(HANDLE h) {\n        if (h == INVALID_HANDLE_VALUE)\n            return;\n\n        NtClose(h);\n    }\n};\n\ntypedef std::unique_ptr<HANDLE, handle_closer> unique_handle;\n\nstatic __inline std::string ntstatus_to_string(NTSTATUS s) {\n    switch (s) {\n        case STATUS_KERNEL_APC:\n            return \"STATUS_KERNEL_APC\";\n        case STATUS_DEVICE_POWER_FAILURE:\n            return \"STATUS_DEVICE_POWER_FAILURE\";\n        case STATUS_ABIOS_NOT_PRESENT:\n            return \"STATUS_ABIOS_NOT_PRESENT\";\n        case STATUS_ABIOS_LID_NOT_EXIST:\n            return \"STATUS_ABIOS_LID_NOT_EXIST\";\n        case STATUS_ABIOS_LID_ALREADY_OWNED:\n            return \"STATUS_ABIOS_LID_ALREADY_OWNED\";\n        case STATUS_ABIOS_NOT_LID_OWNER:\n            return \"STATUS_ABIOS_NOT_LID_OWNER\";\n        case STATUS_ABIOS_INVALID_COMMAND:\n            return \"STATUS_ABIOS_INVALID_COMMAND\";\n        case STATUS_ABIOS_INVALID_LID:\n            return \"STATUS_ABIOS_INVALID_LID\";\n        case STATUS_ABIOS_SELECTOR_NOT_AVAILABLE:\n            return \"STATUS_ABIOS_SELECTOR_NOT_AVAILABLE\";\n        case STATUS_ABIOS_INVALID_SELECTOR:\n            return \"STATUS_ABIOS_INVALID_SELECTOR\";\n        case STATUS_MULTIPLE_FAULT_VIOLATION:\n            return \"STATUS_MULTIPLE_FAULT_VIOLATION\";\n        case STATUS_SUCCESS:\n            return \"STATUS_SUCCESS\";\n        case STATUS_WAIT_1:\n            return \"STATUS_WAIT_1\";\n        case STATUS_WAIT_2:\n            return \"STATUS_WAIT_2\";\n        case STATUS_WAIT_3:\n            return \"STATUS_WAIT_3\";\n        case STATUS_WAIT_63:\n            return \"STATUS_WAIT_63\";\n        case STATUS_ABANDONED:\n            return \"STATUS_ABANDONED\";\n        case STATUS_ABANDONED_WAIT_63:\n            return \"STATUS_ABANDONED_WAIT_63\";\n        case STATUS_USER_APC:\n            return \"STATUS_USER_APC\";\n        case STATUS_ALERTED:\n            return \"STATUS_ALERTED\";\n        case STATUS_TIMEOUT:\n            return \"STATUS_TIMEOUT\";\n        case STATUS_PENDING:\n            return \"STATUS_PENDING\";\n        case STATUS_REPARSE:\n            return \"STATUS_REPARSE\";\n        case STATUS_MORE_ENTRIES:\n            return \"STATUS_MORE_ENTRIES\";\n        case STATUS_NOT_ALL_ASSIGNED:\n            return \"STATUS_NOT_ALL_ASSIGNED\";\n        case STATUS_SOME_NOT_MAPPED:\n            return \"STATUS_SOME_NOT_MAPPED\";\n        case STATUS_OPLOCK_BREAK_IN_PROGRESS:\n            return \"STATUS_OPLOCK_BREAK_IN_PROGRESS\";\n        case STATUS_VOLUME_MOUNTED:\n            return \"STATUS_VOLUME_MOUNTED\";\n        case STATUS_RXACT_COMMITTED:\n            return \"STATUS_RXACT_COMMITTED\";\n        case STATUS_NOTIFY_CLEANUP:\n            return \"STATUS_NOTIFY_CLEANUP\";\n        case STATUS_NOTIFY_ENUM_DIR:\n            return \"STATUS_NOTIFY_ENUM_DIR\";\n        case STATUS_NO_QUOTAS_FOR_ACCOUNT:\n            return \"STATUS_NO_QUOTAS_FOR_ACCOUNT\";\n        case STATUS_PRIMARY_TRANSPORT_CONNECT_FAILED:\n            return \"STATUS_PRIMARY_TRANSPORT_CONNECT_FAILED\";\n        case STATUS_PAGE_FAULT_TRANSITION:\n            return \"STATUS_PAGE_FAULT_TRANSITION\";\n        case STATUS_PAGE_FAULT_DEMAND_ZERO:\n            return \"STATUS_PAGE_FAULT_DEMAND_ZERO\";\n        case STATUS_PAGE_FAULT_COPY_ON_WRITE:\n            return \"STATUS_PAGE_FAULT_COPY_ON_WRITE\";\n        case STATUS_PAGE_FAULT_GUARD_PAGE:\n            return \"STATUS_PAGE_FAULT_GUARD_PAGE\";\n        case STATUS_PAGE_FAULT_PAGING_FILE:\n            return \"STATUS_PAGE_FAULT_PAGING_FILE\";\n        case STATUS_CACHE_PAGE_LOCKED:\n            return \"STATUS_CACHE_PAGE_LOCKED\";\n        case STATUS_CRASH_DUMP:\n            return \"STATUS_CRASH_DUMP\";\n        case STATUS_BUFFER_ALL_ZEROS:\n            return \"STATUS_BUFFER_ALL_ZEROS\";\n        case STATUS_REPARSE_OBJECT:\n            return \"STATUS_REPARSE_OBJECT\";\n        case STATUS_RESOURCE_REQUIREMENTS_CHANGED:\n            return \"STATUS_RESOURCE_REQUIREMENTS_CHANGED\";\n        case STATUS_TRANSLATION_COMPLETE:\n            return \"STATUS_TRANSLATION_COMPLETE\";\n        case STATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY:\n            return \"STATUS_DS_MEMBERSHIP_EVALUATED_LOCALLY\";\n        case STATUS_NOTHING_TO_TERMINATE:\n            return \"STATUS_NOTHING_TO_TERMINATE\";\n        case STATUS_PROCESS_NOT_IN_JOB:\n            return \"STATUS_PROCESS_NOT_IN_JOB\";\n        case STATUS_PROCESS_IN_JOB:\n            return \"STATUS_PROCESS_IN_JOB\";\n        case STATUS_VOLSNAP_HIBERNATE_READY:\n            return \"STATUS_VOLSNAP_HIBERNATE_READY\";\n        case STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY:\n            return \"STATUS_FSFILTER_OP_COMPLETED_SUCCESSFULLY\";\n        case STATUS_INTERRUPT_VECTOR_ALREADY_CONNECTED:\n            return \"STATUS_INTERRUPT_VECTOR_ALREADY_CONNECTED\";\n        case STATUS_INTERRUPT_STILL_CONNECTED:\n            return \"STATUS_INTERRUPT_STILL_CONNECTED\";\n        case STATUS_PROCESS_CLONED:\n            return \"STATUS_PROCESS_CLONED\";\n        case STATUS_FILE_LOCKED_WITH_ONLY_READERS:\n            return \"STATUS_FILE_LOCKED_WITH_ONLY_READERS\";\n        case STATUS_FILE_LOCKED_WITH_WRITERS:\n            return \"STATUS_FILE_LOCKED_WITH_WRITERS\";\n        case STATUS_RESOURCEMANAGER_READ_ONLY:\n            return \"STATUS_RESOURCEMANAGER_READ_ONLY\";\n        case STATUS_WAIT_FOR_OPLOCK:\n            return \"STATUS_WAIT_FOR_OPLOCK\";\n        case DBG_EXCEPTION_HANDLED:\n            return \"DBG_EXCEPTION_HANDLED\";\n        case DBG_CONTINUE:\n            return \"DBG_CONTINUE\";\n        case STATUS_FLT_IO_COMPLETE:\n            return \"STATUS_FLT_IO_COMPLETE\";\n        case STATUS_FILE_NOT_AVAILABLE:\n            return \"STATUS_FILE_NOT_AVAILABLE\";\n        case STATUS_OBJECT_NAME_EXISTS:\n            return \"STATUS_OBJECT_NAME_EXISTS\";\n        case STATUS_THREAD_WAS_SUSPENDED:\n            return \"STATUS_THREAD_WAS_SUSPENDED\";\n        case STATUS_WORKING_SET_LIMIT_RANGE:\n            return \"STATUS_WORKING_SET_LIMIT_RANGE\";\n        case STATUS_IMAGE_NOT_AT_BASE:\n            return \"STATUS_IMAGE_NOT_AT_BASE\";\n        case STATUS_RXACT_STATE_CREATED:\n            return \"STATUS_RXACT_STATE_CREATED\";\n        case STATUS_SEGMENT_NOTIFICATION:\n            return \"STATUS_SEGMENT_NOTIFICATION\";\n        case STATUS_LOCAL_USER_SESSION_KEY:\n            return \"STATUS_LOCAL_USER_SESSION_KEY\";\n        case STATUS_BAD_CURRENT_DIRECTORY:\n            return \"STATUS_BAD_CURRENT_DIRECTORY\";\n        case STATUS_SERIAL_MORE_WRITES:\n            return \"STATUS_SERIAL_MORE_WRITES\";\n        case STATUS_REGISTRY_RECOVERED:\n            return \"STATUS_REGISTRY_RECOVERED\";\n        case STATUS_FT_READ_RECOVERY_FROM_BACKUP:\n            return \"STATUS_FT_READ_RECOVERY_FROM_BACKUP\";\n        case STATUS_FT_WRITE_RECOVERY:\n            return \"STATUS_FT_WRITE_RECOVERY\";\n        case STATUS_SERIAL_COUNTER_TIMEOUT:\n            return \"STATUS_SERIAL_COUNTER_TIMEOUT\";\n        case STATUS_NULL_LM_PASSWORD:\n            return \"STATUS_NULL_LM_PASSWORD\";\n        case STATUS_IMAGE_MACHINE_TYPE_MISMATCH:\n            return \"STATUS_IMAGE_MACHINE_TYPE_MISMATCH\";\n        case STATUS_RECEIVE_PARTIAL:\n            return \"STATUS_RECEIVE_PARTIAL\";\n        case STATUS_RECEIVE_EXPEDITED:\n            return \"STATUS_RECEIVE_EXPEDITED\";\n        case STATUS_RECEIVE_PARTIAL_EXPEDITED:\n            return \"STATUS_RECEIVE_PARTIAL_EXPEDITED\";\n        case STATUS_EVENT_DONE:\n            return \"STATUS_EVENT_DONE\";\n        case STATUS_EVENT_PENDING:\n            return \"STATUS_EVENT_PENDING\";\n        case STATUS_CHECKING_FILE_SYSTEM:\n            return \"STATUS_CHECKING_FILE_SYSTEM\";\n        case STATUS_FATAL_APP_EXIT:\n            return \"STATUS_FATAL_APP_EXIT\";\n        case STATUS_PREDEFINED_HANDLE:\n            return \"STATUS_PREDEFINED_HANDLE\";\n        case STATUS_WAS_UNLOCKED:\n            return \"STATUS_WAS_UNLOCKED\";\n        case STATUS_SERVICE_NOTIFICATION:\n            return \"STATUS_SERVICE_NOTIFICATION\";\n        case STATUS_WAS_LOCKED:\n            return \"STATUS_WAS_LOCKED\";\n        case STATUS_LOG_HARD_ERROR:\n            return \"STATUS_LOG_HARD_ERROR\";\n        case STATUS_ALREADY_WIN32:\n            return \"STATUS_ALREADY_WIN32\";\n        case STATUS_WX86_UNSIMULATE:\n            return \"STATUS_WX86_UNSIMULATE\";\n        case STATUS_WX86_CONTINUE:\n            return \"STATUS_WX86_CONTINUE\";\n        case STATUS_WX86_SINGLE_STEP:\n            return \"STATUS_WX86_SINGLE_STEP\";\n        case STATUS_WX86_BREAKPOINT:\n            return \"STATUS_WX86_BREAKPOINT\";\n        case STATUS_WX86_EXCEPTION_CONTINUE:\n            return \"STATUS_WX86_EXCEPTION_CONTINUE\";\n        case STATUS_WX86_EXCEPTION_LASTCHANCE:\n            return \"STATUS_WX86_EXCEPTION_LASTCHANCE\";\n        case STATUS_WX86_EXCEPTION_CHAIN:\n            return \"STATUS_WX86_EXCEPTION_CHAIN\";\n        case STATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE:\n            return \"STATUS_IMAGE_MACHINE_TYPE_MISMATCH_EXE\";\n        case STATUS_NO_YIELD_PERFORMED:\n            return \"STATUS_NO_YIELD_PERFORMED\";\n        case STATUS_TIMER_RESUME_IGNORED:\n            return \"STATUS_TIMER_RESUME_IGNORED\";\n        case STATUS_ARBITRATION_UNHANDLED:\n            return \"STATUS_ARBITRATION_UNHANDLED\";\n        case STATUS_CARDBUS_NOT_SUPPORTED:\n            return \"STATUS_CARDBUS_NOT_SUPPORTED\";\n        case STATUS_WX86_CREATEWX86TIB:\n            return \"STATUS_WX86_CREATEWX86TIB\";\n        case STATUS_MP_PROCESSOR_MISMATCH:\n            return \"STATUS_MP_PROCESSOR_MISMATCH\";\n        case STATUS_HIBERNATED:\n            return \"STATUS_HIBERNATED\";\n        case STATUS_RESUME_HIBERNATION:\n            return \"STATUS_RESUME_HIBERNATION\";\n        case STATUS_FIRMWARE_UPDATED:\n            return \"STATUS_FIRMWARE_UPDATED\";\n        case STATUS_DRIVERS_LEAKING_LOCKED_PAGES:\n            return \"STATUS_DRIVERS_LEAKING_LOCKED_PAGES\";\n        case STATUS_MESSAGE_RETRIEVED:\n            return \"STATUS_MESSAGE_RETRIEVED\";\n        case STATUS_SYSTEM_POWERSTATE_TRANSITION:\n            return \"STATUS_SYSTEM_POWERSTATE_TRANSITION\";\n        case STATUS_ALPC_CHECK_COMPLETION_LIST:\n            return \"STATUS_ALPC_CHECK_COMPLETION_LIST\";\n        case STATUS_SYSTEM_POWERSTATE_COMPLEX_TRANSITION:\n            return \"STATUS_SYSTEM_POWERSTATE_COMPLEX_TRANSITION\";\n        case STATUS_ACCESS_AUDIT_BY_POLICY:\n            return \"STATUS_ACCESS_AUDIT_BY_POLICY\";\n        case STATUS_ABANDON_HIBERFILE:\n            return \"STATUS_ABANDON_HIBERFILE\";\n        case STATUS_BIZRULES_NOT_ENABLED:\n            return \"STATUS_BIZRULES_NOT_ENABLED\";\n        case STATUS_WAKE_SYSTEM:\n            return \"STATUS_WAKE_SYSTEM\";\n        case STATUS_DS_SHUTTING_DOWN:\n            return \"STATUS_DS_SHUTTING_DOWN\";\n        case DBG_REPLY_LATER:\n            return \"DBG_REPLY_LATER\";\n        case DBG_UNABLE_TO_PROVIDE_HANDLE:\n            return \"DBG_UNABLE_TO_PROVIDE_HANDLE\";\n        case DBG_TERMINATE_THREAD:\n            return \"DBG_TERMINATE_THREAD\";\n        case DBG_TERMINATE_PROCESS:\n            return \"DBG_TERMINATE_PROCESS\";\n        case DBG_CONTROL_C:\n            return \"DBG_CONTROL_C\";\n        case DBG_PRINTEXCEPTION_C:\n            return \"DBG_PRINTEXCEPTION_C\";\n        case DBG_RIPEXCEPTION:\n            return \"DBG_RIPEXCEPTION\";\n        case DBG_CONTROL_BREAK:\n            return \"DBG_CONTROL_BREAK\";\n        case DBG_COMMAND_EXCEPTION:\n            return \"DBG_COMMAND_EXCEPTION\";\n        case DBG_PRINTEXCEPTION_WIDE_C:\n            return \"DBG_PRINTEXCEPTION_WIDE_C\";\n        case RPC_NT_UUID_LOCAL_ONLY:\n            return \"RPC_NT_UUID_LOCAL_ONLY\";\n        case RPC_NT_SEND_INCOMPLETE:\n            return \"RPC_NT_SEND_INCOMPLETE\";\n        case STATUS_CTX_CDM_CONNECT:\n            return \"STATUS_CTX_CDM_CONNECT\";\n        case STATUS_CTX_CDM_DISCONNECT:\n            return \"STATUS_CTX_CDM_DISCONNECT\";\n        case STATUS_SXS_RELEASE_ACTIVATION_CONTEXT:\n            return \"STATUS_SXS_RELEASE_ACTIVATION_CONTEXT\";\n        case STATUS_RECOVERY_NOT_NEEDED:\n            return \"STATUS_RECOVERY_NOT_NEEDED\";\n        case STATUS_RM_ALREADY_STARTED:\n            return \"STATUS_RM_ALREADY_STARTED\";\n        case STATUS_LOG_NO_RESTART:\n            return \"STATUS_LOG_NO_RESTART\";\n        case STATUS_VIDEO_DRIVER_DEBUG_REPORT_REQUEST:\n            return \"STATUS_VIDEO_DRIVER_DEBUG_REPORT_REQUEST\";\n        case STATUS_GRAPHICS_PARTIAL_DATA_POPULATED:\n            return \"STATUS_GRAPHICS_PARTIAL_DATA_POPULATED\";\n        case STATUS_GRAPHICS_DRIVER_MISMATCH:\n            return \"STATUS_GRAPHICS_DRIVER_MISMATCH\";\n        case STATUS_GRAPHICS_MODE_NOT_PINNED:\n            return \"STATUS_GRAPHICS_MODE_NOT_PINNED\";\n        case STATUS_GRAPHICS_NO_PREFERRED_MODE:\n            return \"STATUS_GRAPHICS_NO_PREFERRED_MODE\";\n        case STATUS_GRAPHICS_DATASET_IS_EMPTY:\n            return \"STATUS_GRAPHICS_DATASET_IS_EMPTY\";\n        case STATUS_GRAPHICS_NO_MORE_ELEMENTS_IN_DATASET:\n            return \"STATUS_GRAPHICS_NO_MORE_ELEMENTS_IN_DATASET\";\n        case STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_PINNED:\n            return \"STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_PINNED\";\n        case STATUS_GRAPHICS_UNKNOWN_CHILD_STATUS:\n            return \"STATUS_GRAPHICS_UNKNOWN_CHILD_STATUS\";\n        case STATUS_GRAPHICS_LEADLINK_START_DEFERRED:\n            return \"STATUS_GRAPHICS_LEADLINK_START_DEFERRED\";\n        case STATUS_GRAPHICS_POLLING_TOO_FREQUENTLY:\n            return \"STATUS_GRAPHICS_POLLING_TOO_FREQUENTLY\";\n        case STATUS_GRAPHICS_START_DEFERRED:\n            return \"STATUS_GRAPHICS_START_DEFERRED\";\n        case STATUS_NDIS_INDICATION_REQUIRED:\n            return \"STATUS_NDIS_INDICATION_REQUIRED\";\n        case STATUS_GUARD_PAGE_VIOLATION:\n            return \"STATUS_GUARD_PAGE_VIOLATION\";\n        case STATUS_DATATYPE_MISALIGNMENT:\n            return \"STATUS_DATATYPE_MISALIGNMENT\";\n        case STATUS_BREAKPOINT:\n            return \"STATUS_BREAKPOINT\";\n        case STATUS_SINGLE_STEP:\n            return \"STATUS_SINGLE_STEP\";\n        case STATUS_BUFFER_OVERFLOW:\n            return \"STATUS_BUFFER_OVERFLOW\";\n        case STATUS_NO_MORE_FILES:\n            return \"STATUS_NO_MORE_FILES\";\n        case STATUS_WAKE_SYSTEM_DEBUGGER:\n            return \"STATUS_WAKE_SYSTEM_DEBUGGER\";\n        case STATUS_HANDLES_CLOSED:\n            return \"STATUS_HANDLES_CLOSED\";\n        case STATUS_NO_INHERITANCE:\n            return \"STATUS_NO_INHERITANCE\";\n        case STATUS_GUID_SUBSTITUTION_MADE:\n            return \"STATUS_GUID_SUBSTITUTION_MADE\";\n        case STATUS_PARTIAL_COPY:\n            return \"STATUS_PARTIAL_COPY\";\n        case STATUS_DEVICE_PAPER_EMPTY:\n            return \"STATUS_DEVICE_PAPER_EMPTY\";\n        case STATUS_DEVICE_POWERED_OFF:\n            return \"STATUS_DEVICE_POWERED_OFF\";\n        case STATUS_DEVICE_OFF_LINE:\n            return \"STATUS_DEVICE_OFF_LINE\";\n        case STATUS_DEVICE_BUSY:\n            return \"STATUS_DEVICE_BUSY\";\n        case STATUS_NO_MORE_EAS:\n            return \"STATUS_NO_MORE_EAS\";\n        case STATUS_INVALID_EA_NAME:\n            return \"STATUS_INVALID_EA_NAME\";\n        case STATUS_EA_LIST_INCONSISTENT:\n            return \"STATUS_EA_LIST_INCONSISTENT\";\n        case STATUS_INVALID_EA_FLAG:\n            return \"STATUS_INVALID_EA_FLAG\";\n        case STATUS_VERIFY_REQUIRED:\n            return \"STATUS_VERIFY_REQUIRED\";\n        case STATUS_EXTRANEOUS_INFORMATION:\n            return \"STATUS_EXTRANEOUS_INFORMATION\";\n        case STATUS_RXACT_COMMIT_NECESSARY:\n            return \"STATUS_RXACT_COMMIT_NECESSARY\";\n        case STATUS_NO_MORE_ENTRIES:\n            return \"STATUS_NO_MORE_ENTRIES\";\n        case STATUS_FILEMARK_DETECTED:\n            return \"STATUS_FILEMARK_DETECTED\";\n        case STATUS_MEDIA_CHANGED:\n            return \"STATUS_MEDIA_CHANGED\";\n        case STATUS_BUS_RESET:\n            return \"STATUS_BUS_RESET\";\n        case STATUS_END_OF_MEDIA:\n            return \"STATUS_END_OF_MEDIA\";\n        case STATUS_BEGINNING_OF_MEDIA:\n            return \"STATUS_BEGINNING_OF_MEDIA\";\n        case STATUS_MEDIA_CHECK:\n            return \"STATUS_MEDIA_CHECK\";\n        case STATUS_SETMARK_DETECTED:\n            return \"STATUS_SETMARK_DETECTED\";\n        case STATUS_NO_DATA_DETECTED:\n            return \"STATUS_NO_DATA_DETECTED\";\n        case STATUS_REDIRECTOR_HAS_OPEN_HANDLES:\n            return \"STATUS_REDIRECTOR_HAS_OPEN_HANDLES\";\n        case STATUS_SERVER_HAS_OPEN_HANDLES:\n            return \"STATUS_SERVER_HAS_OPEN_HANDLES\";\n        case STATUS_ALREADY_DISCONNECTED:\n            return \"STATUS_ALREADY_DISCONNECTED\";\n        case STATUS_LONGJUMP:\n            return \"STATUS_LONGJUMP\";\n        case STATUS_CLEANER_CARTRIDGE_INSTALLED:\n            return \"STATUS_CLEANER_CARTRIDGE_INSTALLED\";\n        case STATUS_PLUGPLAY_QUERY_VETOED:\n            return \"STATUS_PLUGPLAY_QUERY_VETOED\";\n        case STATUS_UNWIND_CONSOLIDATE:\n            return \"STATUS_UNWIND_CONSOLIDATE\";\n        case STATUS_REGISTRY_HIVE_RECOVERED:\n            return \"STATUS_REGISTRY_HIVE_RECOVERED\";\n        case STATUS_DLL_MIGHT_BE_INSECURE:\n            return \"STATUS_DLL_MIGHT_BE_INSECURE\";\n        case STATUS_DLL_MIGHT_BE_INCOMPATIBLE:\n            return \"STATUS_DLL_MIGHT_BE_INCOMPATIBLE\";\n        case STATUS_STOPPED_ON_SYMLINK:\n            return \"STATUS_STOPPED_ON_SYMLINK\";\n        case STATUS_DEVICE_REQUIRES_CLEANING:\n            return \"STATUS_DEVICE_REQUIRES_CLEANING\";\n        case STATUS_DEVICE_DOOR_OPEN:\n            return \"STATUS_DEVICE_DOOR_OPEN\";\n        case STATUS_DATA_LOST_REPAIR:\n            return \"STATUS_DATA_LOST_REPAIR\";\n        case DBG_EXCEPTION_NOT_HANDLED:\n            return \"DBG_EXCEPTION_NOT_HANDLED\";\n        case STATUS_CLUSTER_NODE_ALREADY_UP:\n            return \"STATUS_CLUSTER_NODE_ALREADY_UP\";\n        case STATUS_CLUSTER_NODE_ALREADY_DOWN:\n            return \"STATUS_CLUSTER_NODE_ALREADY_DOWN\";\n        case STATUS_CLUSTER_NETWORK_ALREADY_ONLINE:\n            return \"STATUS_CLUSTER_NETWORK_ALREADY_ONLINE\";\n        case STATUS_CLUSTER_NETWORK_ALREADY_OFFLINE:\n            return \"STATUS_CLUSTER_NETWORK_ALREADY_OFFLINE\";\n        case STATUS_CLUSTER_NODE_ALREADY_MEMBER:\n            return \"STATUS_CLUSTER_NODE_ALREADY_MEMBER\";\n        case STATUS_COULD_NOT_RESIZE_LOG:\n            return \"STATUS_COULD_NOT_RESIZE_LOG\";\n        case STATUS_NO_TXF_METADATA:\n            return \"STATUS_NO_TXF_METADATA\";\n        case STATUS_CANT_RECOVER_WITH_HANDLE_OPEN:\n            return \"STATUS_CANT_RECOVER_WITH_HANDLE_OPEN\";\n        case STATUS_TXF_METADATA_ALREADY_PRESENT:\n            return \"STATUS_TXF_METADATA_ALREADY_PRESENT\";\n        case STATUS_TRANSACTION_SCOPE_CALLBACKS_NOT_SET:\n            return \"STATUS_TRANSACTION_SCOPE_CALLBACKS_NOT_SET\";\n        case STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD_RECOVERED:\n            return \"STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD_RECOVERED\";\n        case STATUS_FLT_BUFFER_TOO_SMALL:\n            return \"STATUS_FLT_BUFFER_TOO_SMALL\";\n        case STATUS_FVE_PARTIAL_METADATA:\n            return \"STATUS_FVE_PARTIAL_METADATA\";\n        case STATUS_FVE_TRANSIENT_STATE:\n            return \"STATUS_FVE_TRANSIENT_STATE\";\n        case STATUS_UNSUCCESSFUL:\n            return \"STATUS_UNSUCCESSFUL\";\n        case STATUS_NOT_IMPLEMENTED:\n            return \"STATUS_NOT_IMPLEMENTED\";\n        case STATUS_INVALID_INFO_CLASS:\n            return \"STATUS_INVALID_INFO_CLASS\";\n        case STATUS_INFO_LENGTH_MISMATCH:\n            return \"STATUS_INFO_LENGTH_MISMATCH\";\n        case STATUS_ACCESS_VIOLATION:\n            return \"STATUS_ACCESS_VIOLATION\";\n        case STATUS_IN_PAGE_ERROR:\n            return \"STATUS_IN_PAGE_ERROR\";\n        case STATUS_PAGEFILE_QUOTA:\n            return \"STATUS_PAGEFILE_QUOTA\";\n        case STATUS_INVALID_HANDLE:\n            return \"STATUS_INVALID_HANDLE\";\n        case STATUS_BAD_INITIAL_STACK:\n            return \"STATUS_BAD_INITIAL_STACK\";\n        case STATUS_BAD_INITIAL_PC:\n            return \"STATUS_BAD_INITIAL_PC\";\n        case STATUS_INVALID_CID:\n            return \"STATUS_INVALID_CID\";\n        case STATUS_TIMER_NOT_CANCELED:\n            return \"STATUS_TIMER_NOT_CANCELED\";\n        case STATUS_INVALID_PARAMETER:\n            return \"STATUS_INVALID_PARAMETER\";\n        case STATUS_NO_SUCH_DEVICE:\n            return \"STATUS_NO_SUCH_DEVICE\";\n        case STATUS_NO_SUCH_FILE:\n            return \"STATUS_NO_SUCH_FILE\";\n        case STATUS_INVALID_DEVICE_REQUEST:\n            return \"STATUS_INVALID_DEVICE_REQUEST\";\n        case STATUS_END_OF_FILE:\n            return \"STATUS_END_OF_FILE\";\n        case STATUS_WRONG_VOLUME:\n            return \"STATUS_WRONG_VOLUME\";\n        case STATUS_NO_MEDIA_IN_DEVICE:\n            return \"STATUS_NO_MEDIA_IN_DEVICE\";\n        case STATUS_UNRECOGNIZED_MEDIA:\n            return \"STATUS_UNRECOGNIZED_MEDIA\";\n        case STATUS_NONEXISTENT_SECTOR:\n            return \"STATUS_NONEXISTENT_SECTOR\";\n        case STATUS_MORE_PROCESSING_REQUIRED:\n            return \"STATUS_MORE_PROCESSING_REQUIRED\";\n        case STATUS_NO_MEMORY:\n            return \"STATUS_NO_MEMORY\";\n        case STATUS_CONFLICTING_ADDRESSES:\n            return \"STATUS_CONFLICTING_ADDRESSES\";\n        case STATUS_NOT_MAPPED_VIEW:\n            return \"STATUS_NOT_MAPPED_VIEW\";\n        case STATUS_UNABLE_TO_FREE_VM:\n            return \"STATUS_UNABLE_TO_FREE_VM\";\n        case STATUS_UNABLE_TO_DELETE_SECTION:\n            return \"STATUS_UNABLE_TO_DELETE_SECTION\";\n        case STATUS_INVALID_SYSTEM_SERVICE:\n            return \"STATUS_INVALID_SYSTEM_SERVICE\";\n        case STATUS_ILLEGAL_INSTRUCTION:\n            return \"STATUS_ILLEGAL_INSTRUCTION\";\n        case STATUS_INVALID_LOCK_SEQUENCE:\n            return \"STATUS_INVALID_LOCK_SEQUENCE\";\n        case STATUS_INVALID_VIEW_SIZE:\n            return \"STATUS_INVALID_VIEW_SIZE\";\n        case STATUS_INVALID_FILE_FOR_SECTION:\n            return \"STATUS_INVALID_FILE_FOR_SECTION\";\n        case STATUS_ALREADY_COMMITTED:\n            return \"STATUS_ALREADY_COMMITTED\";\n        case STATUS_ACCESS_DENIED:\n            return \"STATUS_ACCESS_DENIED\";\n        case STATUS_BUFFER_TOO_SMALL:\n            return \"STATUS_BUFFER_TOO_SMALL\";\n        case STATUS_OBJECT_TYPE_MISMATCH:\n            return \"STATUS_OBJECT_TYPE_MISMATCH\";\n        case STATUS_NONCONTINUABLE_EXCEPTION:\n            return \"STATUS_NONCONTINUABLE_EXCEPTION\";\n        case STATUS_INVALID_DISPOSITION:\n            return \"STATUS_INVALID_DISPOSITION\";\n        case STATUS_UNWIND:\n            return \"STATUS_UNWIND\";\n        case STATUS_BAD_STACK:\n            return \"STATUS_BAD_STACK\";\n        case STATUS_INVALID_UNWIND_TARGET:\n            return \"STATUS_INVALID_UNWIND_TARGET\";\n        case STATUS_NOT_LOCKED:\n            return \"STATUS_NOT_LOCKED\";\n        case STATUS_PARITY_ERROR:\n            return \"STATUS_PARITY_ERROR\";\n        case STATUS_UNABLE_TO_DECOMMIT_VM:\n            return \"STATUS_UNABLE_TO_DECOMMIT_VM\";\n        case STATUS_NOT_COMMITTED:\n            return \"STATUS_NOT_COMMITTED\";\n        case STATUS_INVALID_PORT_ATTRIBUTES:\n            return \"STATUS_INVALID_PORT_ATTRIBUTES\";\n        case STATUS_PORT_MESSAGE_TOO_LONG:\n            return \"STATUS_PORT_MESSAGE_TOO_LONG\";\n        case STATUS_INVALID_PARAMETER_MIX:\n            return \"STATUS_INVALID_PARAMETER_MIX\";\n        case STATUS_INVALID_QUOTA_LOWER:\n            return \"STATUS_INVALID_QUOTA_LOWER\";\n        case STATUS_DISK_CORRUPT_ERROR:\n            return \"STATUS_DISK_CORRUPT_ERROR\";\n        case STATUS_OBJECT_NAME_INVALID:\n            return \"STATUS_OBJECT_NAME_INVALID\";\n        case STATUS_OBJECT_NAME_NOT_FOUND:\n            return \"STATUS_OBJECT_NAME_NOT_FOUND\";\n        case STATUS_OBJECT_NAME_COLLISION:\n            return \"STATUS_OBJECT_NAME_COLLISION\";\n        case STATUS_PORT_DISCONNECTED:\n            return \"STATUS_PORT_DISCONNECTED\";\n        case STATUS_DEVICE_ALREADY_ATTACHED:\n            return \"STATUS_DEVICE_ALREADY_ATTACHED\";\n        case STATUS_OBJECT_PATH_INVALID:\n            return \"STATUS_OBJECT_PATH_INVALID\";\n        case STATUS_OBJECT_PATH_NOT_FOUND:\n            return \"STATUS_OBJECT_PATH_NOT_FOUND\";\n        case STATUS_OBJECT_PATH_SYNTAX_BAD:\n            return \"STATUS_OBJECT_PATH_SYNTAX_BAD\";\n        case STATUS_DATA_OVERRUN:\n            return \"STATUS_DATA_OVERRUN\";\n        case STATUS_DATA_LATE_ERROR:\n            return \"STATUS_DATA_LATE_ERROR\";\n        case STATUS_DATA_ERROR:\n            return \"STATUS_DATA_ERROR\";\n        case STATUS_CRC_ERROR:\n            return \"STATUS_CRC_ERROR\";\n        case STATUS_SECTION_TOO_BIG:\n            return \"STATUS_SECTION_TOO_BIG\";\n        case STATUS_PORT_CONNECTION_REFUSED:\n            return \"STATUS_PORT_CONNECTION_REFUSED\";\n        case STATUS_INVALID_PORT_HANDLE:\n            return \"STATUS_INVALID_PORT_HANDLE\";\n        case STATUS_SHARING_VIOLATION:\n            return \"STATUS_SHARING_VIOLATION\";\n        case STATUS_QUOTA_EXCEEDED:\n            return \"STATUS_QUOTA_EXCEEDED\";\n        case STATUS_INVALID_PAGE_PROTECTION:\n            return \"STATUS_INVALID_PAGE_PROTECTION\";\n        case STATUS_MUTANT_NOT_OWNED:\n            return \"STATUS_MUTANT_NOT_OWNED\";\n        case STATUS_SEMAPHORE_LIMIT_EXCEEDED:\n            return \"STATUS_SEMAPHORE_LIMIT_EXCEEDED\";\n        case STATUS_PORT_ALREADY_SET:\n            return \"STATUS_PORT_ALREADY_SET\";\n        case STATUS_SECTION_NOT_IMAGE:\n            return \"STATUS_SECTION_NOT_IMAGE\";\n        case STATUS_SUSPEND_COUNT_EXCEEDED:\n            return \"STATUS_SUSPEND_COUNT_EXCEEDED\";\n        case STATUS_THREAD_IS_TERMINATING:\n            return \"STATUS_THREAD_IS_TERMINATING\";\n        case STATUS_BAD_WORKING_SET_LIMIT:\n            return \"STATUS_BAD_WORKING_SET_LIMIT\";\n        case STATUS_INCOMPATIBLE_FILE_MAP:\n            return \"STATUS_INCOMPATIBLE_FILE_MAP\";\n        case STATUS_SECTION_PROTECTION:\n            return \"STATUS_SECTION_PROTECTION\";\n        case STATUS_EAS_NOT_SUPPORTED:\n            return \"STATUS_EAS_NOT_SUPPORTED\";\n        case STATUS_EA_TOO_LARGE:\n            return \"STATUS_EA_TOO_LARGE\";\n        case STATUS_NONEXISTENT_EA_ENTRY:\n            return \"STATUS_NONEXISTENT_EA_ENTRY\";\n        case STATUS_NO_EAS_ON_FILE:\n            return \"STATUS_NO_EAS_ON_FILE\";\n        case STATUS_EA_CORRUPT_ERROR:\n            return \"STATUS_EA_CORRUPT_ERROR\";\n        case STATUS_FILE_LOCK_CONFLICT:\n            return \"STATUS_FILE_LOCK_CONFLICT\";\n        case STATUS_LOCK_NOT_GRANTED:\n            return \"STATUS_LOCK_NOT_GRANTED\";\n        case STATUS_DELETE_PENDING:\n            return \"STATUS_DELETE_PENDING\";\n        case STATUS_CTL_FILE_NOT_SUPPORTED:\n            return \"STATUS_CTL_FILE_NOT_SUPPORTED\";\n        case STATUS_UNKNOWN_REVISION:\n            return \"STATUS_UNKNOWN_REVISION\";\n        case STATUS_REVISION_MISMATCH:\n            return \"STATUS_REVISION_MISMATCH\";\n        case STATUS_INVALID_OWNER:\n            return \"STATUS_INVALID_OWNER\";\n        case STATUS_INVALID_PRIMARY_GROUP:\n            return \"STATUS_INVALID_PRIMARY_GROUP\";\n        case STATUS_NO_IMPERSONATION_TOKEN:\n            return \"STATUS_NO_IMPERSONATION_TOKEN\";\n        case STATUS_CANT_DISABLE_MANDATORY:\n            return \"STATUS_CANT_DISABLE_MANDATORY\";\n        case STATUS_NO_LOGON_SERVERS:\n            return \"STATUS_NO_LOGON_SERVERS\";\n        case STATUS_NO_SUCH_LOGON_SESSION:\n            return \"STATUS_NO_SUCH_LOGON_SESSION\";\n        case STATUS_NO_SUCH_PRIVILEGE:\n            return \"STATUS_NO_SUCH_PRIVILEGE\";\n        case STATUS_PRIVILEGE_NOT_HELD:\n            return \"STATUS_PRIVILEGE_NOT_HELD\";\n        case STATUS_INVALID_ACCOUNT_NAME:\n            return \"STATUS_INVALID_ACCOUNT_NAME\";\n        case STATUS_USER_EXISTS:\n            return \"STATUS_USER_EXISTS\";\n        case STATUS_NO_SUCH_USER:\n            return \"STATUS_NO_SUCH_USER\";\n        case STATUS_GROUP_EXISTS:\n            return \"STATUS_GROUP_EXISTS\";\n        case STATUS_NO_SUCH_GROUP:\n            return \"STATUS_NO_SUCH_GROUP\";\n        case STATUS_MEMBER_IN_GROUP:\n            return \"STATUS_MEMBER_IN_GROUP\";\n        case STATUS_MEMBER_NOT_IN_GROUP:\n            return \"STATUS_MEMBER_NOT_IN_GROUP\";\n        case STATUS_LAST_ADMIN:\n            return \"STATUS_LAST_ADMIN\";\n        case STATUS_WRONG_PASSWORD:\n            return \"STATUS_WRONG_PASSWORD\";\n        case STATUS_ILL_FORMED_PASSWORD:\n            return \"STATUS_ILL_FORMED_PASSWORD\";\n        case STATUS_PASSWORD_RESTRICTION:\n            return \"STATUS_PASSWORD_RESTRICTION\";\n        case STATUS_LOGON_FAILURE:\n            return \"STATUS_LOGON_FAILURE\";\n        case STATUS_ACCOUNT_RESTRICTION:\n            return \"STATUS_ACCOUNT_RESTRICTION\";\n        case STATUS_INVALID_LOGON_HOURS:\n            return \"STATUS_INVALID_LOGON_HOURS\";\n        case STATUS_INVALID_WORKSTATION:\n            return \"STATUS_INVALID_WORKSTATION\";\n        case STATUS_PASSWORD_EXPIRED:\n            return \"STATUS_PASSWORD_EXPIRED\";\n        case STATUS_ACCOUNT_DISABLED:\n            return \"STATUS_ACCOUNT_DISABLED\";\n        case STATUS_NONE_MAPPED:\n            return \"STATUS_NONE_MAPPED\";\n        case STATUS_TOO_MANY_LUIDS_REQUESTED:\n            return \"STATUS_TOO_MANY_LUIDS_REQUESTED\";\n        case STATUS_LUIDS_EXHAUSTED:\n            return \"STATUS_LUIDS_EXHAUSTED\";\n        case STATUS_INVALID_SUB_AUTHORITY:\n            return \"STATUS_INVALID_SUB_AUTHORITY\";\n        case STATUS_INVALID_ACL:\n            return \"STATUS_INVALID_ACL\";\n        case STATUS_INVALID_SID:\n            return \"STATUS_INVALID_SID\";\n        case STATUS_INVALID_SECURITY_DESCR:\n            return \"STATUS_INVALID_SECURITY_DESCR\";\n        case STATUS_PROCEDURE_NOT_FOUND:\n            return \"STATUS_PROCEDURE_NOT_FOUND\";\n        case STATUS_INVALID_IMAGE_FORMAT:\n            return \"STATUS_INVALID_IMAGE_FORMAT\";\n        case STATUS_NO_TOKEN:\n            return \"STATUS_NO_TOKEN\";\n        case STATUS_BAD_INHERITANCE_ACL:\n            return \"STATUS_BAD_INHERITANCE_ACL\";\n        case STATUS_RANGE_NOT_LOCKED:\n            return \"STATUS_RANGE_NOT_LOCKED\";\n        case STATUS_DISK_FULL:\n            return \"STATUS_DISK_FULL\";\n        case STATUS_SERVER_DISABLED:\n            return \"STATUS_SERVER_DISABLED\";\n        case STATUS_SERVER_NOT_DISABLED:\n            return \"STATUS_SERVER_NOT_DISABLED\";\n        case STATUS_TOO_MANY_GUIDS_REQUESTED:\n            return \"STATUS_TOO_MANY_GUIDS_REQUESTED\";\n        case STATUS_GUIDS_EXHAUSTED:\n            return \"STATUS_GUIDS_EXHAUSTED\";\n        case STATUS_INVALID_ID_AUTHORITY:\n            return \"STATUS_INVALID_ID_AUTHORITY\";\n        case STATUS_AGENTS_EXHAUSTED:\n            return \"STATUS_AGENTS_EXHAUSTED\";\n        case STATUS_INVALID_VOLUME_LABEL:\n            return \"STATUS_INVALID_VOLUME_LABEL\";\n        case STATUS_SECTION_NOT_EXTENDED:\n            return \"STATUS_SECTION_NOT_EXTENDED\";\n        case STATUS_NOT_MAPPED_DATA:\n            return \"STATUS_NOT_MAPPED_DATA\";\n        case STATUS_RESOURCE_DATA_NOT_FOUND:\n            return \"STATUS_RESOURCE_DATA_NOT_FOUND\";\n        case STATUS_RESOURCE_TYPE_NOT_FOUND:\n            return \"STATUS_RESOURCE_TYPE_NOT_FOUND\";\n        case STATUS_RESOURCE_NAME_NOT_FOUND:\n            return \"STATUS_RESOURCE_NAME_NOT_FOUND\";\n        case STATUS_ARRAY_BOUNDS_EXCEEDED:\n            return \"STATUS_ARRAY_BOUNDS_EXCEEDED\";\n        case STATUS_FLOAT_DENORMAL_OPERAND:\n            return \"STATUS_FLOAT_DENORMAL_OPERAND\";\n        case STATUS_FLOAT_DIVIDE_BY_ZERO:\n            return \"STATUS_FLOAT_DIVIDE_BY_ZERO\";\n        case STATUS_FLOAT_INEXACT_RESULT:\n            return \"STATUS_FLOAT_INEXACT_RESULT\";\n        case STATUS_FLOAT_INVALID_OPERATION:\n            return \"STATUS_FLOAT_INVALID_OPERATION\";\n        case STATUS_FLOAT_OVERFLOW:\n            return \"STATUS_FLOAT_OVERFLOW\";\n        case STATUS_FLOAT_STACK_CHECK:\n            return \"STATUS_FLOAT_STACK_CHECK\";\n        case STATUS_FLOAT_UNDERFLOW:\n            return \"STATUS_FLOAT_UNDERFLOW\";\n        case STATUS_INTEGER_DIVIDE_BY_ZERO:\n            return \"STATUS_INTEGER_DIVIDE_BY_ZERO\";\n        case STATUS_INTEGER_OVERFLOW:\n            return \"STATUS_INTEGER_OVERFLOW\";\n        case STATUS_PRIVILEGED_INSTRUCTION:\n            return \"STATUS_PRIVILEGED_INSTRUCTION\";\n        case STATUS_TOO_MANY_PAGING_FILES:\n            return \"STATUS_TOO_MANY_PAGING_FILES\";\n        case STATUS_FILE_INVALID:\n            return \"STATUS_FILE_INVALID\";\n        case STATUS_ALLOTTED_SPACE_EXCEEDED:\n            return \"STATUS_ALLOTTED_SPACE_EXCEEDED\";\n        case STATUS_INSUFFICIENT_RESOURCES:\n            return \"STATUS_INSUFFICIENT_RESOURCES\";\n        case STATUS_DFS_EXIT_PATH_FOUND:\n            return \"STATUS_DFS_EXIT_PATH_FOUND\";\n        case STATUS_DEVICE_DATA_ERROR:\n            return \"STATUS_DEVICE_DATA_ERROR\";\n        case STATUS_DEVICE_NOT_CONNECTED:\n            return \"STATUS_DEVICE_NOT_CONNECTED\";\n        case STATUS_FREE_VM_NOT_AT_BASE:\n            return \"STATUS_FREE_VM_NOT_AT_BASE\";\n        case STATUS_MEMORY_NOT_ALLOCATED:\n            return \"STATUS_MEMORY_NOT_ALLOCATED\";\n        case STATUS_WORKING_SET_QUOTA:\n            return \"STATUS_WORKING_SET_QUOTA\";\n        case STATUS_MEDIA_WRITE_PROTECTED:\n            return \"STATUS_MEDIA_WRITE_PROTECTED\";\n        case STATUS_DEVICE_NOT_READY:\n            return \"STATUS_DEVICE_NOT_READY\";\n        case STATUS_INVALID_GROUP_ATTRIBUTES:\n            return \"STATUS_INVALID_GROUP_ATTRIBUTES\";\n        case STATUS_BAD_IMPERSONATION_LEVEL:\n            return \"STATUS_BAD_IMPERSONATION_LEVEL\";\n        case STATUS_CANT_OPEN_ANONYMOUS:\n            return \"STATUS_CANT_OPEN_ANONYMOUS\";\n        case STATUS_BAD_VALIDATION_CLASS:\n            return \"STATUS_BAD_VALIDATION_CLASS\";\n        case STATUS_BAD_TOKEN_TYPE:\n            return \"STATUS_BAD_TOKEN_TYPE\";\n        case STATUS_BAD_MASTER_BOOT_RECORD:\n            return \"STATUS_BAD_MASTER_BOOT_RECORD\";\n        case STATUS_INSTRUCTION_MISALIGNMENT:\n            return \"STATUS_INSTRUCTION_MISALIGNMENT\";\n        case STATUS_INSTANCE_NOT_AVAILABLE:\n            return \"STATUS_INSTANCE_NOT_AVAILABLE\";\n        case STATUS_PIPE_NOT_AVAILABLE:\n            return \"STATUS_PIPE_NOT_AVAILABLE\";\n        case STATUS_INVALID_PIPE_STATE:\n            return \"STATUS_INVALID_PIPE_STATE\";\n        case STATUS_PIPE_BUSY:\n            return \"STATUS_PIPE_BUSY\";\n        case STATUS_ILLEGAL_FUNCTION:\n            return \"STATUS_ILLEGAL_FUNCTION\";\n        case STATUS_PIPE_DISCONNECTED:\n            return \"STATUS_PIPE_DISCONNECTED\";\n        case STATUS_PIPE_CLOSING:\n            return \"STATUS_PIPE_CLOSING\";\n        case STATUS_PIPE_CONNECTED:\n            return \"STATUS_PIPE_CONNECTED\";\n        case STATUS_PIPE_LISTENING:\n            return \"STATUS_PIPE_LISTENING\";\n        case STATUS_INVALID_READ_MODE:\n            return \"STATUS_INVALID_READ_MODE\";\n        case STATUS_IO_TIMEOUT:\n            return \"STATUS_IO_TIMEOUT\";\n        case STATUS_FILE_FORCED_CLOSED:\n            return \"STATUS_FILE_FORCED_CLOSED\";\n        case STATUS_PROFILING_NOT_STARTED:\n            return \"STATUS_PROFILING_NOT_STARTED\";\n        case STATUS_PROFILING_NOT_STOPPED:\n            return \"STATUS_PROFILING_NOT_STOPPED\";\n        case STATUS_COULD_NOT_INTERPRET:\n            return \"STATUS_COULD_NOT_INTERPRET\";\n        case STATUS_FILE_IS_A_DIRECTORY:\n            return \"STATUS_FILE_IS_A_DIRECTORY\";\n        case STATUS_NOT_SUPPORTED:\n            return \"STATUS_NOT_SUPPORTED\";\n        case STATUS_REMOTE_NOT_LISTENING:\n            return \"STATUS_REMOTE_NOT_LISTENING\";\n        case STATUS_DUPLICATE_NAME:\n            return \"STATUS_DUPLICATE_NAME\";\n        case STATUS_BAD_NETWORK_PATH:\n            return \"STATUS_BAD_NETWORK_PATH\";\n        case STATUS_NETWORK_BUSY:\n            return \"STATUS_NETWORK_BUSY\";\n        case STATUS_DEVICE_DOES_NOT_EXIST:\n            return \"STATUS_DEVICE_DOES_NOT_EXIST\";\n        case STATUS_TOO_MANY_COMMANDS:\n            return \"STATUS_TOO_MANY_COMMANDS\";\n        case STATUS_ADAPTER_HARDWARE_ERROR:\n            return \"STATUS_ADAPTER_HARDWARE_ERROR\";\n        case STATUS_INVALID_NETWORK_RESPONSE:\n            return \"STATUS_INVALID_NETWORK_RESPONSE\";\n        case STATUS_UNEXPECTED_NETWORK_ERROR:\n            return \"STATUS_UNEXPECTED_NETWORK_ERROR\";\n        case STATUS_BAD_REMOTE_ADAPTER:\n            return \"STATUS_BAD_REMOTE_ADAPTER\";\n        case STATUS_PRINT_QUEUE_FULL:\n            return \"STATUS_PRINT_QUEUE_FULL\";\n        case STATUS_NO_SPOOL_SPACE:\n            return \"STATUS_NO_SPOOL_SPACE\";\n        case STATUS_PRINT_CANCELLED:\n            return \"STATUS_PRINT_CANCELLED\";\n        case STATUS_NETWORK_NAME_DELETED:\n            return \"STATUS_NETWORK_NAME_DELETED\";\n        case STATUS_NETWORK_ACCESS_DENIED:\n            return \"STATUS_NETWORK_ACCESS_DENIED\";\n        case STATUS_BAD_DEVICE_TYPE:\n            return \"STATUS_BAD_DEVICE_TYPE\";\n        case STATUS_BAD_NETWORK_NAME:\n            return \"STATUS_BAD_NETWORK_NAME\";\n        case STATUS_TOO_MANY_NAMES:\n            return \"STATUS_TOO_MANY_NAMES\";\n        case STATUS_TOO_MANY_SESSIONS:\n            return \"STATUS_TOO_MANY_SESSIONS\";\n        case STATUS_SHARING_PAUSED:\n            return \"STATUS_SHARING_PAUSED\";\n        case STATUS_REQUEST_NOT_ACCEPTED:\n            return \"STATUS_REQUEST_NOT_ACCEPTED\";\n        case STATUS_REDIRECTOR_PAUSED:\n            return \"STATUS_REDIRECTOR_PAUSED\";\n        case STATUS_NET_WRITE_FAULT:\n            return \"STATUS_NET_WRITE_FAULT\";\n        case STATUS_PROFILING_AT_LIMIT:\n            return \"STATUS_PROFILING_AT_LIMIT\";\n        case STATUS_NOT_SAME_DEVICE:\n            return \"STATUS_NOT_SAME_DEVICE\";\n        case STATUS_FILE_RENAMED:\n            return \"STATUS_FILE_RENAMED\";\n        case STATUS_VIRTUAL_CIRCUIT_CLOSED:\n            return \"STATUS_VIRTUAL_CIRCUIT_CLOSED\";\n        case STATUS_NO_SECURITY_ON_OBJECT:\n            return \"STATUS_NO_SECURITY_ON_OBJECT\";\n        case STATUS_CANT_WAIT:\n            return \"STATUS_CANT_WAIT\";\n        case STATUS_PIPE_EMPTY:\n            return \"STATUS_PIPE_EMPTY\";\n        case STATUS_CANT_ACCESS_DOMAIN_INFO:\n            return \"STATUS_CANT_ACCESS_DOMAIN_INFO\";\n        case STATUS_CANT_TERMINATE_SELF:\n            return \"STATUS_CANT_TERMINATE_SELF\";\n        case STATUS_INVALID_SERVER_STATE:\n            return \"STATUS_INVALID_SERVER_STATE\";\n        case STATUS_INVALID_DOMAIN_STATE:\n            return \"STATUS_INVALID_DOMAIN_STATE\";\n        case STATUS_INVALID_DOMAIN_ROLE:\n            return \"STATUS_INVALID_DOMAIN_ROLE\";\n        case STATUS_NO_SUCH_DOMAIN:\n            return \"STATUS_NO_SUCH_DOMAIN\";\n        case STATUS_DOMAIN_EXISTS:\n            return \"STATUS_DOMAIN_EXISTS\";\n        case STATUS_DOMAIN_LIMIT_EXCEEDED:\n            return \"STATUS_DOMAIN_LIMIT_EXCEEDED\";\n        case STATUS_OPLOCK_NOT_GRANTED:\n            return \"STATUS_OPLOCK_NOT_GRANTED\";\n        case STATUS_INVALID_OPLOCK_PROTOCOL:\n            return \"STATUS_INVALID_OPLOCK_PROTOCOL\";\n        case STATUS_INTERNAL_DB_CORRUPTION:\n            return \"STATUS_INTERNAL_DB_CORRUPTION\";\n        case STATUS_INTERNAL_ERROR:\n            return \"STATUS_INTERNAL_ERROR\";\n        case STATUS_GENERIC_NOT_MAPPED:\n            return \"STATUS_GENERIC_NOT_MAPPED\";\n        case STATUS_BAD_DESCRIPTOR_FORMAT:\n            return \"STATUS_BAD_DESCRIPTOR_FORMAT\";\n        case STATUS_INVALID_USER_BUFFER:\n            return \"STATUS_INVALID_USER_BUFFER\";\n        case STATUS_UNEXPECTED_IO_ERROR:\n            return \"STATUS_UNEXPECTED_IO_ERROR\";\n        case STATUS_UNEXPECTED_MM_CREATE_ERR:\n            return \"STATUS_UNEXPECTED_MM_CREATE_ERR\";\n        case STATUS_UNEXPECTED_MM_MAP_ERROR:\n            return \"STATUS_UNEXPECTED_MM_MAP_ERROR\";\n        case STATUS_UNEXPECTED_MM_EXTEND_ERR:\n            return \"STATUS_UNEXPECTED_MM_EXTEND_ERR\";\n        case STATUS_NOT_LOGON_PROCESS:\n            return \"STATUS_NOT_LOGON_PROCESS\";\n        case STATUS_LOGON_SESSION_EXISTS:\n            return \"STATUS_LOGON_SESSION_EXISTS\";\n        case STATUS_INVALID_PARAMETER_1:\n            return \"STATUS_INVALID_PARAMETER_1\";\n        case STATUS_INVALID_PARAMETER_2:\n            return \"STATUS_INVALID_PARAMETER_2\";\n        case STATUS_INVALID_PARAMETER_3:\n            return \"STATUS_INVALID_PARAMETER_3\";\n        case STATUS_INVALID_PARAMETER_4:\n            return \"STATUS_INVALID_PARAMETER_4\";\n        case STATUS_INVALID_PARAMETER_5:\n            return \"STATUS_INVALID_PARAMETER_5\";\n        case STATUS_INVALID_PARAMETER_6:\n            return \"STATUS_INVALID_PARAMETER_6\";\n        case STATUS_INVALID_PARAMETER_7:\n            return \"STATUS_INVALID_PARAMETER_7\";\n        case STATUS_INVALID_PARAMETER_8:\n            return \"STATUS_INVALID_PARAMETER_8\";\n        case STATUS_INVALID_PARAMETER_9:\n            return \"STATUS_INVALID_PARAMETER_9\";\n        case STATUS_INVALID_PARAMETER_10:\n            return \"STATUS_INVALID_PARAMETER_10\";\n        case STATUS_INVALID_PARAMETER_11:\n            return \"STATUS_INVALID_PARAMETER_11\";\n        case STATUS_INVALID_PARAMETER_12:\n            return \"STATUS_INVALID_PARAMETER_12\";\n        case STATUS_REDIRECTOR_NOT_STARTED:\n            return \"STATUS_REDIRECTOR_NOT_STARTED\";\n        case STATUS_REDIRECTOR_STARTED:\n            return \"STATUS_REDIRECTOR_STARTED\";\n        case STATUS_STACK_OVERFLOW:\n            return \"STATUS_STACK_OVERFLOW\";\n        case STATUS_NO_SUCH_PACKAGE:\n            return \"STATUS_NO_SUCH_PACKAGE\";\n        case STATUS_BAD_FUNCTION_TABLE:\n            return \"STATUS_BAD_FUNCTION_TABLE\";\n        case STATUS_VARIABLE_NOT_FOUND:\n            return \"STATUS_VARIABLE_NOT_FOUND\";\n        case STATUS_DIRECTORY_NOT_EMPTY:\n            return \"STATUS_DIRECTORY_NOT_EMPTY\";\n        case STATUS_FILE_CORRUPT_ERROR:\n            return \"STATUS_FILE_CORRUPT_ERROR\";\n        case STATUS_NOT_A_DIRECTORY:\n            return \"STATUS_NOT_A_DIRECTORY\";\n        case STATUS_BAD_LOGON_SESSION_STATE:\n            return \"STATUS_BAD_LOGON_SESSION_STATE\";\n        case STATUS_LOGON_SESSION_COLLISION:\n            return \"STATUS_LOGON_SESSION_COLLISION\";\n        case STATUS_NAME_TOO_LONG:\n            return \"STATUS_NAME_TOO_LONG\";\n        case STATUS_FILES_OPEN:\n            return \"STATUS_FILES_OPEN\";\n        case STATUS_CONNECTION_IN_USE:\n            return \"STATUS_CONNECTION_IN_USE\";\n        case STATUS_MESSAGE_NOT_FOUND:\n            return \"STATUS_MESSAGE_NOT_FOUND\";\n        case STATUS_PROCESS_IS_TERMINATING:\n            return \"STATUS_PROCESS_IS_TERMINATING\";\n        case STATUS_INVALID_LOGON_TYPE:\n            return \"STATUS_INVALID_LOGON_TYPE\";\n        case STATUS_NO_GUID_TRANSLATION:\n            return \"STATUS_NO_GUID_TRANSLATION\";\n        case STATUS_CANNOT_IMPERSONATE:\n            return \"STATUS_CANNOT_IMPERSONATE\";\n        case STATUS_IMAGE_ALREADY_LOADED:\n            return \"STATUS_IMAGE_ALREADY_LOADED\";\n        case STATUS_NO_LDT:\n            return \"STATUS_NO_LDT\";\n        case STATUS_INVALID_LDT_SIZE:\n            return \"STATUS_INVALID_LDT_SIZE\";\n        case STATUS_INVALID_LDT_OFFSET:\n            return \"STATUS_INVALID_LDT_OFFSET\";\n        case STATUS_INVALID_LDT_DESCRIPTOR:\n            return \"STATUS_INVALID_LDT_DESCRIPTOR\";\n        case STATUS_INVALID_IMAGE_NE_FORMAT:\n            return \"STATUS_INVALID_IMAGE_NE_FORMAT\";\n        case STATUS_RXACT_INVALID_STATE:\n            return \"STATUS_RXACT_INVALID_STATE\";\n        case STATUS_RXACT_COMMIT_FAILURE:\n            return \"STATUS_RXACT_COMMIT_FAILURE\";\n        case STATUS_MAPPED_FILE_SIZE_ZERO:\n            return \"STATUS_MAPPED_FILE_SIZE_ZERO\";\n        case STATUS_TOO_MANY_OPENED_FILES:\n            return \"STATUS_TOO_MANY_OPENED_FILES\";\n        case STATUS_CANCELLED:\n            return \"STATUS_CANCELLED\";\n        case STATUS_CANNOT_DELETE:\n            return \"STATUS_CANNOT_DELETE\";\n        case STATUS_INVALID_COMPUTER_NAME:\n            return \"STATUS_INVALID_COMPUTER_NAME\";\n        case STATUS_FILE_DELETED:\n            return \"STATUS_FILE_DELETED\";\n        case STATUS_SPECIAL_ACCOUNT:\n            return \"STATUS_SPECIAL_ACCOUNT\";\n        case STATUS_SPECIAL_GROUP:\n            return \"STATUS_SPECIAL_GROUP\";\n        case STATUS_SPECIAL_USER:\n            return \"STATUS_SPECIAL_USER\";\n        case STATUS_MEMBERS_PRIMARY_GROUP:\n            return \"STATUS_MEMBERS_PRIMARY_GROUP\";\n        case STATUS_FILE_CLOSED:\n            return \"STATUS_FILE_CLOSED\";\n        case STATUS_TOO_MANY_THREADS:\n            return \"STATUS_TOO_MANY_THREADS\";\n        case STATUS_THREAD_NOT_IN_PROCESS:\n            return \"STATUS_THREAD_NOT_IN_PROCESS\";\n        case STATUS_TOKEN_ALREADY_IN_USE:\n            return \"STATUS_TOKEN_ALREADY_IN_USE\";\n        case STATUS_PAGEFILE_QUOTA_EXCEEDED:\n            return \"STATUS_PAGEFILE_QUOTA_EXCEEDED\";\n        case STATUS_COMMITMENT_LIMIT:\n            return \"STATUS_COMMITMENT_LIMIT\";\n        case STATUS_INVALID_IMAGE_LE_FORMAT:\n            return \"STATUS_INVALID_IMAGE_LE_FORMAT\";\n        case STATUS_INVALID_IMAGE_NOT_MZ:\n            return \"STATUS_INVALID_IMAGE_NOT_MZ\";\n        case STATUS_INVALID_IMAGE_PROTECT:\n            return \"STATUS_INVALID_IMAGE_PROTECT\";\n        case STATUS_INVALID_IMAGE_WIN_16:\n            return \"STATUS_INVALID_IMAGE_WIN_16\";\n        case STATUS_LOGON_SERVER_CONFLICT:\n            return \"STATUS_LOGON_SERVER_CONFLICT\";\n        case STATUS_TIME_DIFFERENCE_AT_DC:\n            return \"STATUS_TIME_DIFFERENCE_AT_DC\";\n        case STATUS_SYNCHRONIZATION_REQUIRED:\n            return \"STATUS_SYNCHRONIZATION_REQUIRED\";\n        case STATUS_DLL_NOT_FOUND:\n            return \"STATUS_DLL_NOT_FOUND\";\n        case STATUS_OPEN_FAILED:\n            return \"STATUS_OPEN_FAILED\";\n        case STATUS_IO_PRIVILEGE_FAILED:\n            return \"STATUS_IO_PRIVILEGE_FAILED\";\n        case STATUS_ORDINAL_NOT_FOUND:\n            return \"STATUS_ORDINAL_NOT_FOUND\";\n        case STATUS_ENTRYPOINT_NOT_FOUND:\n            return \"STATUS_ENTRYPOINT_NOT_FOUND\";\n        case STATUS_CONTROL_C_EXIT:\n            return \"STATUS_CONTROL_C_EXIT\";\n        case STATUS_LOCAL_DISCONNECT:\n            return \"STATUS_LOCAL_DISCONNECT\";\n        case STATUS_REMOTE_DISCONNECT:\n            return \"STATUS_REMOTE_DISCONNECT\";\n        case STATUS_REMOTE_RESOURCES:\n            return \"STATUS_REMOTE_RESOURCES\";\n        case STATUS_LINK_FAILED:\n            return \"STATUS_LINK_FAILED\";\n        case STATUS_LINK_TIMEOUT:\n            return \"STATUS_LINK_TIMEOUT\";\n        case STATUS_INVALID_CONNECTION:\n            return \"STATUS_INVALID_CONNECTION\";\n        case STATUS_INVALID_ADDRESS:\n            return \"STATUS_INVALID_ADDRESS\";\n        case STATUS_DLL_INIT_FAILED:\n            return \"STATUS_DLL_INIT_FAILED\";\n        case STATUS_MISSING_SYSTEMFILE:\n            return \"STATUS_MISSING_SYSTEMFILE\";\n        case STATUS_UNHANDLED_EXCEPTION:\n            return \"STATUS_UNHANDLED_EXCEPTION\";\n        case STATUS_APP_INIT_FAILURE:\n            return \"STATUS_APP_INIT_FAILURE\";\n        case STATUS_PAGEFILE_CREATE_FAILED:\n            return \"STATUS_PAGEFILE_CREATE_FAILED\";\n        case STATUS_NO_PAGEFILE:\n            return \"STATUS_NO_PAGEFILE\";\n        case STATUS_INVALID_LEVEL:\n            return \"STATUS_INVALID_LEVEL\";\n        case STATUS_WRONG_PASSWORD_CORE:\n            return \"STATUS_WRONG_PASSWORD_CORE\";\n        case STATUS_ILLEGAL_FLOAT_CONTEXT:\n            return \"STATUS_ILLEGAL_FLOAT_CONTEXT\";\n        case STATUS_PIPE_BROKEN:\n            return \"STATUS_PIPE_BROKEN\";\n        case STATUS_REGISTRY_CORRUPT:\n            return \"STATUS_REGISTRY_CORRUPT\";\n        case STATUS_REGISTRY_IO_FAILED:\n            return \"STATUS_REGISTRY_IO_FAILED\";\n        case STATUS_NO_EVENT_PAIR:\n            return \"STATUS_NO_EVENT_PAIR\";\n        case STATUS_UNRECOGNIZED_VOLUME:\n            return \"STATUS_UNRECOGNIZED_VOLUME\";\n        case STATUS_SERIAL_NO_DEVICE_INITED:\n            return \"STATUS_SERIAL_NO_DEVICE_INITED\";\n        case STATUS_NO_SUCH_ALIAS:\n            return \"STATUS_NO_SUCH_ALIAS\";\n        case STATUS_MEMBER_NOT_IN_ALIAS:\n            return \"STATUS_MEMBER_NOT_IN_ALIAS\";\n        case STATUS_MEMBER_IN_ALIAS:\n            return \"STATUS_MEMBER_IN_ALIAS\";\n        case STATUS_ALIAS_EXISTS:\n            return \"STATUS_ALIAS_EXISTS\";\n        case STATUS_LOGON_NOT_GRANTED:\n            return \"STATUS_LOGON_NOT_GRANTED\";\n        case STATUS_TOO_MANY_SECRETS:\n            return \"STATUS_TOO_MANY_SECRETS\";\n        case STATUS_SECRET_TOO_LONG:\n            return \"STATUS_SECRET_TOO_LONG\";\n        case STATUS_INTERNAL_DB_ERROR:\n            return \"STATUS_INTERNAL_DB_ERROR\";\n        case STATUS_FULLSCREEN_MODE:\n            return \"STATUS_FULLSCREEN_MODE\";\n        case STATUS_TOO_MANY_CONTEXT_IDS:\n            return \"STATUS_TOO_MANY_CONTEXT_IDS\";\n        case STATUS_LOGON_TYPE_NOT_GRANTED:\n            return \"STATUS_LOGON_TYPE_NOT_GRANTED\";\n        case STATUS_NOT_REGISTRY_FILE:\n            return \"STATUS_NOT_REGISTRY_FILE\";\n        case STATUS_NT_CROSS_ENCRYPTION_REQUIRED:\n            return \"STATUS_NT_CROSS_ENCRYPTION_REQUIRED\";\n        case STATUS_DOMAIN_CTRLR_CONFIG_ERROR:\n            return \"STATUS_DOMAIN_CTRLR_CONFIG_ERROR\";\n        case STATUS_FT_MISSING_MEMBER:\n            return \"STATUS_FT_MISSING_MEMBER\";\n        case STATUS_ILL_FORMED_SERVICE_ENTRY:\n            return \"STATUS_ILL_FORMED_SERVICE_ENTRY\";\n        case STATUS_ILLEGAL_CHARACTER:\n            return \"STATUS_ILLEGAL_CHARACTER\";\n        case STATUS_UNMAPPABLE_CHARACTER:\n            return \"STATUS_UNMAPPABLE_CHARACTER\";\n        case STATUS_UNDEFINED_CHARACTER:\n            return \"STATUS_UNDEFINED_CHARACTER\";\n        case STATUS_FLOPPY_VOLUME:\n            return \"STATUS_FLOPPY_VOLUME\";\n        case STATUS_FLOPPY_ID_MARK_NOT_FOUND:\n            return \"STATUS_FLOPPY_ID_MARK_NOT_FOUND\";\n        case STATUS_FLOPPY_WRONG_CYLINDER:\n            return \"STATUS_FLOPPY_WRONG_CYLINDER\";\n        case STATUS_FLOPPY_UNKNOWN_ERROR:\n            return \"STATUS_FLOPPY_UNKNOWN_ERROR\";\n        case STATUS_FLOPPY_BAD_REGISTERS:\n            return \"STATUS_FLOPPY_BAD_REGISTERS\";\n        case STATUS_DISK_RECALIBRATE_FAILED:\n            return \"STATUS_DISK_RECALIBRATE_FAILED\";\n        case STATUS_DISK_OPERATION_FAILED:\n            return \"STATUS_DISK_OPERATION_FAILED\";\n        case STATUS_DISK_RESET_FAILED:\n            return \"STATUS_DISK_RESET_FAILED\";\n        case STATUS_SHARED_IRQ_BUSY:\n            return \"STATUS_SHARED_IRQ_BUSY\";\n        case STATUS_FT_ORPHANING:\n            return \"STATUS_FT_ORPHANING\";\n        case STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT:\n            return \"STATUS_BIOS_FAILED_TO_CONNECT_INTERRUPT\";\n        case STATUS_PARTITION_FAILURE:\n            return \"STATUS_PARTITION_FAILURE\";\n        case STATUS_INVALID_BLOCK_LENGTH:\n            return \"STATUS_INVALID_BLOCK_LENGTH\";\n        case STATUS_DEVICE_NOT_PARTITIONED:\n            return \"STATUS_DEVICE_NOT_PARTITIONED\";\n        case STATUS_UNABLE_TO_LOCK_MEDIA:\n            return \"STATUS_UNABLE_TO_LOCK_MEDIA\";\n        case STATUS_UNABLE_TO_UNLOAD_MEDIA:\n            return \"STATUS_UNABLE_TO_UNLOAD_MEDIA\";\n        case STATUS_EOM_OVERFLOW:\n            return \"STATUS_EOM_OVERFLOW\";\n        case STATUS_NO_MEDIA:\n            return \"STATUS_NO_MEDIA\";\n        case STATUS_NO_SUCH_MEMBER:\n            return \"STATUS_NO_SUCH_MEMBER\";\n        case STATUS_INVALID_MEMBER:\n            return \"STATUS_INVALID_MEMBER\";\n        case STATUS_KEY_DELETED:\n            return \"STATUS_KEY_DELETED\";\n        case STATUS_NO_LOG_SPACE:\n            return \"STATUS_NO_LOG_SPACE\";\n        case STATUS_TOO_MANY_SIDS:\n            return \"STATUS_TOO_MANY_SIDS\";\n        case STATUS_LM_CROSS_ENCRYPTION_REQUIRED:\n            return \"STATUS_LM_CROSS_ENCRYPTION_REQUIRED\";\n        case STATUS_KEY_HAS_CHILDREN:\n            return \"STATUS_KEY_HAS_CHILDREN\";\n        case STATUS_CHILD_MUST_BE_VOLATILE:\n            return \"STATUS_CHILD_MUST_BE_VOLATILE\";\n        case STATUS_DEVICE_CONFIGURATION_ERROR:\n            return \"STATUS_DEVICE_CONFIGURATION_ERROR\";\n        case STATUS_DRIVER_INTERNAL_ERROR:\n            return \"STATUS_DRIVER_INTERNAL_ERROR\";\n        case STATUS_INVALID_DEVICE_STATE:\n            return \"STATUS_INVALID_DEVICE_STATE\";\n        case STATUS_IO_DEVICE_ERROR:\n            return \"STATUS_IO_DEVICE_ERROR\";\n        case STATUS_DEVICE_PROTOCOL_ERROR:\n            return \"STATUS_DEVICE_PROTOCOL_ERROR\";\n        case STATUS_BACKUP_CONTROLLER:\n            return \"STATUS_BACKUP_CONTROLLER\";\n        case STATUS_LOG_FILE_FULL:\n            return \"STATUS_LOG_FILE_FULL\";\n        case STATUS_TOO_LATE:\n            return \"STATUS_TOO_LATE\";\n        case STATUS_NO_TRUST_LSA_SECRET:\n            return \"STATUS_NO_TRUST_LSA_SECRET\";\n        case STATUS_NO_TRUST_SAM_ACCOUNT:\n            return \"STATUS_NO_TRUST_SAM_ACCOUNT\";\n        case STATUS_TRUSTED_DOMAIN_FAILURE:\n            return \"STATUS_TRUSTED_DOMAIN_FAILURE\";\n        case STATUS_TRUSTED_RELATIONSHIP_FAILURE:\n            return \"STATUS_TRUSTED_RELATIONSHIP_FAILURE\";\n        case STATUS_EVENTLOG_FILE_CORRUPT:\n            return \"STATUS_EVENTLOG_FILE_CORRUPT\";\n        case STATUS_EVENTLOG_CANT_START:\n            return \"STATUS_EVENTLOG_CANT_START\";\n        case STATUS_TRUST_FAILURE:\n            return \"STATUS_TRUST_FAILURE\";\n        case STATUS_MUTANT_LIMIT_EXCEEDED:\n            return \"STATUS_MUTANT_LIMIT_EXCEEDED\";\n        case STATUS_NETLOGON_NOT_STARTED:\n            return \"STATUS_NETLOGON_NOT_STARTED\";\n        case STATUS_ACCOUNT_EXPIRED:\n            return \"STATUS_ACCOUNT_EXPIRED\";\n        case STATUS_POSSIBLE_DEADLOCK:\n            return \"STATUS_POSSIBLE_DEADLOCK\";\n        case STATUS_NETWORK_CREDENTIAL_CONFLICT:\n            return \"STATUS_NETWORK_CREDENTIAL_CONFLICT\";\n        case STATUS_REMOTE_SESSION_LIMIT:\n            return \"STATUS_REMOTE_SESSION_LIMIT\";\n        case STATUS_EVENTLOG_FILE_CHANGED:\n            return \"STATUS_EVENTLOG_FILE_CHANGED\";\n        case STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT:\n            return \"STATUS_NOLOGON_INTERDOMAIN_TRUST_ACCOUNT\";\n        case STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT:\n            return \"STATUS_NOLOGON_WORKSTATION_TRUST_ACCOUNT\";\n        case STATUS_NOLOGON_SERVER_TRUST_ACCOUNT:\n            return \"STATUS_NOLOGON_SERVER_TRUST_ACCOUNT\";\n        case STATUS_DOMAIN_TRUST_INCONSISTENT:\n            return \"STATUS_DOMAIN_TRUST_INCONSISTENT\";\n        case STATUS_FS_DRIVER_REQUIRED:\n            return \"STATUS_FS_DRIVER_REQUIRED\";\n        case STATUS_IMAGE_ALREADY_LOADED_AS_DLL:\n            return \"STATUS_IMAGE_ALREADY_LOADED_AS_DLL\";\n        case STATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING:\n            return \"STATUS_INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING\";\n        case STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME:\n            return \"STATUS_SHORT_NAMES_NOT_ENABLED_ON_VOLUME\";\n        case STATUS_SECURITY_STREAM_IS_INCONSISTENT:\n            return \"STATUS_SECURITY_STREAM_IS_INCONSISTENT\";\n        case STATUS_INVALID_LOCK_RANGE:\n            return \"STATUS_INVALID_LOCK_RANGE\";\n        case STATUS_INVALID_ACE_CONDITION:\n            return \"STATUS_INVALID_ACE_CONDITION\";\n        case STATUS_IMAGE_SUBSYSTEM_NOT_PRESENT:\n            return \"STATUS_IMAGE_SUBSYSTEM_NOT_PRESENT\";\n        case STATUS_NOTIFICATION_GUID_ALREADY_DEFINED:\n            return \"STATUS_NOTIFICATION_GUID_ALREADY_DEFINED\";\n        case STATUS_NETWORK_OPEN_RESTRICTION:\n            return \"STATUS_NETWORK_OPEN_RESTRICTION\";\n        case STATUS_NO_USER_SESSION_KEY:\n            return \"STATUS_NO_USER_SESSION_KEY\";\n        case STATUS_USER_SESSION_DELETED:\n            return \"STATUS_USER_SESSION_DELETED\";\n        case STATUS_RESOURCE_LANG_NOT_FOUND:\n            return \"STATUS_RESOURCE_LANG_NOT_FOUND\";\n        case STATUS_INSUFF_SERVER_RESOURCES:\n            return \"STATUS_INSUFF_SERVER_RESOURCES\";\n        case STATUS_INVALID_BUFFER_SIZE:\n            return \"STATUS_INVALID_BUFFER_SIZE\";\n        case STATUS_INVALID_ADDRESS_COMPONENT:\n            return \"STATUS_INVALID_ADDRESS_COMPONENT\";\n        case STATUS_INVALID_ADDRESS_WILDCARD:\n            return \"STATUS_INVALID_ADDRESS_WILDCARD\";\n        case STATUS_TOO_MANY_ADDRESSES:\n            return \"STATUS_TOO_MANY_ADDRESSES\";\n        case STATUS_ADDRESS_ALREADY_EXISTS:\n            return \"STATUS_ADDRESS_ALREADY_EXISTS\";\n        case STATUS_ADDRESS_CLOSED:\n            return \"STATUS_ADDRESS_CLOSED\";\n        case STATUS_CONNECTION_DISCONNECTED:\n            return \"STATUS_CONNECTION_DISCONNECTED\";\n        case STATUS_CONNECTION_RESET:\n            return \"STATUS_CONNECTION_RESET\";\n        case STATUS_TOO_MANY_NODES:\n            return \"STATUS_TOO_MANY_NODES\";\n        case STATUS_TRANSACTION_ABORTED:\n            return \"STATUS_TRANSACTION_ABORTED\";\n        case STATUS_TRANSACTION_TIMED_OUT:\n            return \"STATUS_TRANSACTION_TIMED_OUT\";\n        case STATUS_TRANSACTION_NO_RELEASE:\n            return \"STATUS_TRANSACTION_NO_RELEASE\";\n        case STATUS_TRANSACTION_NO_MATCH:\n            return \"STATUS_TRANSACTION_NO_MATCH\";\n        case STATUS_TRANSACTION_RESPONDED:\n            return \"STATUS_TRANSACTION_RESPONDED\";\n        case STATUS_TRANSACTION_INVALID_ID:\n            return \"STATUS_TRANSACTION_INVALID_ID\";\n        case STATUS_TRANSACTION_INVALID_TYPE:\n            return \"STATUS_TRANSACTION_INVALID_TYPE\";\n        case STATUS_NOT_SERVER_SESSION:\n            return \"STATUS_NOT_SERVER_SESSION\";\n        case STATUS_NOT_CLIENT_SESSION:\n            return \"STATUS_NOT_CLIENT_SESSION\";\n        case STATUS_CANNOT_LOAD_REGISTRY_FILE:\n            return \"STATUS_CANNOT_LOAD_REGISTRY_FILE\";\n        case STATUS_DEBUG_ATTACH_FAILED:\n            return \"STATUS_DEBUG_ATTACH_FAILED\";\n        case STATUS_SYSTEM_PROCESS_TERMINATED:\n            return \"STATUS_SYSTEM_PROCESS_TERMINATED\";\n        case STATUS_DATA_NOT_ACCEPTED:\n            return \"STATUS_DATA_NOT_ACCEPTED\";\n        case STATUS_NO_BROWSER_SERVERS_FOUND:\n            return \"STATUS_NO_BROWSER_SERVERS_FOUND\";\n        case STATUS_VDM_HARD_ERROR:\n            return \"STATUS_VDM_HARD_ERROR\";\n        case STATUS_DRIVER_CANCEL_TIMEOUT:\n            return \"STATUS_DRIVER_CANCEL_TIMEOUT\";\n        case STATUS_REPLY_MESSAGE_MISMATCH:\n            return \"STATUS_REPLY_MESSAGE_MISMATCH\";\n        case STATUS_MAPPED_ALIGNMENT:\n            return \"STATUS_MAPPED_ALIGNMENT\";\n        case STATUS_IMAGE_CHECKSUM_MISMATCH:\n            return \"STATUS_IMAGE_CHECKSUM_MISMATCH\";\n        case STATUS_LOST_WRITEBEHIND_DATA:\n            return \"STATUS_LOST_WRITEBEHIND_DATA\";\n        case STATUS_CLIENT_SERVER_PARAMETERS_INVALID:\n            return \"STATUS_CLIENT_SERVER_PARAMETERS_INVALID\";\n        case STATUS_PASSWORD_MUST_CHANGE:\n            return \"STATUS_PASSWORD_MUST_CHANGE\";\n        case STATUS_NOT_FOUND:\n            return \"STATUS_NOT_FOUND\";\n        case STATUS_NOT_TINY_STREAM:\n            return \"STATUS_NOT_TINY_STREAM\";\n        case STATUS_RECOVERY_FAILURE:\n            return \"STATUS_RECOVERY_FAILURE\";\n        case STATUS_STACK_OVERFLOW_READ:\n            return \"STATUS_STACK_OVERFLOW_READ\";\n        case STATUS_FAIL_CHECK:\n            return \"STATUS_FAIL_CHECK\";\n        case STATUS_DUPLICATE_OBJECTID:\n            return \"STATUS_DUPLICATE_OBJECTID\";\n        case STATUS_OBJECTID_EXISTS:\n            return \"STATUS_OBJECTID_EXISTS\";\n        case STATUS_CONVERT_TO_LARGE:\n            return \"STATUS_CONVERT_TO_LARGE\";\n        case STATUS_RETRY:\n            return \"STATUS_RETRY\";\n        case STATUS_FOUND_OUT_OF_SCOPE:\n            return \"STATUS_FOUND_OUT_OF_SCOPE\";\n        case STATUS_ALLOCATE_BUCKET:\n            return \"STATUS_ALLOCATE_BUCKET\";\n        case STATUS_PROPSET_NOT_FOUND:\n            return \"STATUS_PROPSET_NOT_FOUND\";\n        case STATUS_MARSHALL_OVERFLOW:\n            return \"STATUS_MARSHALL_OVERFLOW\";\n        case STATUS_INVALID_VARIANT:\n            return \"STATUS_INVALID_VARIANT\";\n        case STATUS_DOMAIN_CONTROLLER_NOT_FOUND:\n            return \"STATUS_DOMAIN_CONTROLLER_NOT_FOUND\";\n        case STATUS_ACCOUNT_LOCKED_OUT:\n            return \"STATUS_ACCOUNT_LOCKED_OUT\";\n        case STATUS_HANDLE_NOT_CLOSABLE:\n            return \"STATUS_HANDLE_NOT_CLOSABLE\";\n        case STATUS_CONNECTION_REFUSED:\n            return \"STATUS_CONNECTION_REFUSED\";\n        case STATUS_GRACEFUL_DISCONNECT:\n            return \"STATUS_GRACEFUL_DISCONNECT\";\n        case STATUS_ADDRESS_ALREADY_ASSOCIATED:\n            return \"STATUS_ADDRESS_ALREADY_ASSOCIATED\";\n        case STATUS_ADDRESS_NOT_ASSOCIATED:\n            return \"STATUS_ADDRESS_NOT_ASSOCIATED\";\n        case STATUS_CONNECTION_INVALID:\n            return \"STATUS_CONNECTION_INVALID\";\n        case STATUS_CONNECTION_ACTIVE:\n            return \"STATUS_CONNECTION_ACTIVE\";\n        case STATUS_NETWORK_UNREACHABLE:\n            return \"STATUS_NETWORK_UNREACHABLE\";\n        case STATUS_HOST_UNREACHABLE:\n            return \"STATUS_HOST_UNREACHABLE\";\n        case STATUS_PROTOCOL_UNREACHABLE:\n            return \"STATUS_PROTOCOL_UNREACHABLE\";\n        case STATUS_PORT_UNREACHABLE:\n            return \"STATUS_PORT_UNREACHABLE\";\n        case STATUS_REQUEST_ABORTED:\n            return \"STATUS_REQUEST_ABORTED\";\n        case STATUS_CONNECTION_ABORTED:\n            return \"STATUS_CONNECTION_ABORTED\";\n        case STATUS_BAD_COMPRESSION_BUFFER:\n            return \"STATUS_BAD_COMPRESSION_BUFFER\";\n        case STATUS_USER_MAPPED_FILE:\n            return \"STATUS_USER_MAPPED_FILE\";\n        case STATUS_AUDIT_FAILED:\n            return \"STATUS_AUDIT_FAILED\";\n        case STATUS_TIMER_RESOLUTION_NOT_SET:\n            return \"STATUS_TIMER_RESOLUTION_NOT_SET\";\n        case STATUS_CONNECTION_COUNT_LIMIT:\n            return \"STATUS_CONNECTION_COUNT_LIMIT\";\n        case STATUS_LOGIN_TIME_RESTRICTION:\n            return \"STATUS_LOGIN_TIME_RESTRICTION\";\n        case STATUS_LOGIN_WKSTA_RESTRICTION:\n            return \"STATUS_LOGIN_WKSTA_RESTRICTION\";\n        case STATUS_IMAGE_MP_UP_MISMATCH:\n            return \"STATUS_IMAGE_MP_UP_MISMATCH\";\n        case STATUS_INSUFFICIENT_LOGON_INFO:\n            return \"STATUS_INSUFFICIENT_LOGON_INFO\";\n        case STATUS_BAD_DLL_ENTRYPOINT:\n            return \"STATUS_BAD_DLL_ENTRYPOINT\";\n        case STATUS_BAD_SERVICE_ENTRYPOINT:\n            return \"STATUS_BAD_SERVICE_ENTRYPOINT\";\n        case STATUS_LPC_REPLY_LOST:\n            return \"STATUS_LPC_REPLY_LOST\";\n        case STATUS_IP_ADDRESS_CONFLICT1:\n            return \"STATUS_IP_ADDRESS_CONFLICT1\";\n        case STATUS_IP_ADDRESS_CONFLICT2:\n            return \"STATUS_IP_ADDRESS_CONFLICT2\";\n        case STATUS_REGISTRY_QUOTA_LIMIT:\n            return \"STATUS_REGISTRY_QUOTA_LIMIT\";\n        case STATUS_PATH_NOT_COVERED:\n            return \"STATUS_PATH_NOT_COVERED\";\n        case STATUS_NO_CALLBACK_ACTIVE:\n            return \"STATUS_NO_CALLBACK_ACTIVE\";\n        case STATUS_LICENSE_QUOTA_EXCEEDED:\n            return \"STATUS_LICENSE_QUOTA_EXCEEDED\";\n        case STATUS_PWD_TOO_SHORT:\n            return \"STATUS_PWD_TOO_SHORT\";\n        case STATUS_PWD_TOO_RECENT:\n            return \"STATUS_PWD_TOO_RECENT\";\n        case STATUS_PWD_HISTORY_CONFLICT:\n            return \"STATUS_PWD_HISTORY_CONFLICT\";\n        case STATUS_PLUGPLAY_NO_DEVICE:\n            return \"STATUS_PLUGPLAY_NO_DEVICE\";\n        case STATUS_UNSUPPORTED_COMPRESSION:\n            return \"STATUS_UNSUPPORTED_COMPRESSION\";\n        case STATUS_INVALID_HW_PROFILE:\n            return \"STATUS_INVALID_HW_PROFILE\";\n        case STATUS_INVALID_PLUGPLAY_DEVICE_PATH:\n            return \"STATUS_INVALID_PLUGPLAY_DEVICE_PATH\";\n        case STATUS_DRIVER_ORDINAL_NOT_FOUND:\n            return \"STATUS_DRIVER_ORDINAL_NOT_FOUND\";\n        case STATUS_DRIVER_ENTRYPOINT_NOT_FOUND:\n            return \"STATUS_DRIVER_ENTRYPOINT_NOT_FOUND\";\n        case STATUS_RESOURCE_NOT_OWNED:\n            return \"STATUS_RESOURCE_NOT_OWNED\";\n        case STATUS_TOO_MANY_LINKS:\n            return \"STATUS_TOO_MANY_LINKS\";\n        case STATUS_QUOTA_LIST_INCONSISTENT:\n            return \"STATUS_QUOTA_LIST_INCONSISTENT\";\n        case STATUS_FILE_IS_OFFLINE:\n            return \"STATUS_FILE_IS_OFFLINE\";\n        case STATUS_EVALUATION_EXPIRATION:\n            return \"STATUS_EVALUATION_EXPIRATION\";\n        case STATUS_ILLEGAL_DLL_RELOCATION:\n            return \"STATUS_ILLEGAL_DLL_RELOCATION\";\n        case STATUS_LICENSE_VIOLATION:\n            return \"STATUS_LICENSE_VIOLATION\";\n        case STATUS_DLL_INIT_FAILED_LOGOFF:\n            return \"STATUS_DLL_INIT_FAILED_LOGOFF\";\n        case STATUS_DRIVER_UNABLE_TO_LOAD:\n            return \"STATUS_DRIVER_UNABLE_TO_LOAD\";\n        case STATUS_DFS_UNAVAILABLE:\n            return \"STATUS_DFS_UNAVAILABLE\";\n        case STATUS_VOLUME_DISMOUNTED:\n            return \"STATUS_VOLUME_DISMOUNTED\";\n        case STATUS_WX86_INTERNAL_ERROR:\n            return \"STATUS_WX86_INTERNAL_ERROR\";\n        case STATUS_WX86_FLOAT_STACK_CHECK:\n            return \"STATUS_WX86_FLOAT_STACK_CHECK\";\n        case STATUS_VALIDATE_CONTINUE:\n            return \"STATUS_VALIDATE_CONTINUE\";\n        case STATUS_NO_MATCH:\n            return \"STATUS_NO_MATCH\";\n        case STATUS_NO_MORE_MATCHES:\n            return \"STATUS_NO_MORE_MATCHES\";\n        case STATUS_NOT_A_REPARSE_POINT:\n            return \"STATUS_NOT_A_REPARSE_POINT\";\n        case STATUS_IO_REPARSE_TAG_INVALID:\n            return \"STATUS_IO_REPARSE_TAG_INVALID\";\n        case STATUS_IO_REPARSE_TAG_MISMATCH:\n            return \"STATUS_IO_REPARSE_TAG_MISMATCH\";\n        case STATUS_IO_REPARSE_DATA_INVALID:\n            return \"STATUS_IO_REPARSE_DATA_INVALID\";\n        case STATUS_IO_REPARSE_TAG_NOT_HANDLED:\n            return \"STATUS_IO_REPARSE_TAG_NOT_HANDLED\";\n        case STATUS_REPARSE_POINT_NOT_RESOLVED:\n            return \"STATUS_REPARSE_POINT_NOT_RESOLVED\";\n        case STATUS_DIRECTORY_IS_A_REPARSE_POINT:\n            return \"STATUS_DIRECTORY_IS_A_REPARSE_POINT\";\n        case STATUS_RANGE_LIST_CONFLICT:\n            return \"STATUS_RANGE_LIST_CONFLICT\";\n        case STATUS_SOURCE_ELEMENT_EMPTY:\n            return \"STATUS_SOURCE_ELEMENT_EMPTY\";\n        case STATUS_DESTINATION_ELEMENT_FULL:\n            return \"STATUS_DESTINATION_ELEMENT_FULL\";\n        case STATUS_ILLEGAL_ELEMENT_ADDRESS:\n            return \"STATUS_ILLEGAL_ELEMENT_ADDRESS\";\n        case STATUS_MAGAZINE_NOT_PRESENT:\n            return \"STATUS_MAGAZINE_NOT_PRESENT\";\n        case STATUS_REINITIALIZATION_NEEDED:\n            return \"STATUS_REINITIALIZATION_NEEDED\";\n        case STATUS_ENCRYPTION_FAILED:\n            return \"STATUS_ENCRYPTION_FAILED\";\n        case STATUS_DECRYPTION_FAILED:\n            return \"STATUS_DECRYPTION_FAILED\";\n        case STATUS_RANGE_NOT_FOUND:\n            return \"STATUS_RANGE_NOT_FOUND\";\n        case STATUS_NO_RECOVERY_POLICY:\n            return \"STATUS_NO_RECOVERY_POLICY\";\n        case STATUS_NO_EFS:\n            return \"STATUS_NO_EFS\";\n        case STATUS_WRONG_EFS:\n            return \"STATUS_WRONG_EFS\";\n        case STATUS_NO_USER_KEYS:\n            return \"STATUS_NO_USER_KEYS\";\n        case STATUS_FILE_NOT_ENCRYPTED:\n            return \"STATUS_FILE_NOT_ENCRYPTED\";\n        case STATUS_NOT_EXPORT_FORMAT:\n            return \"STATUS_NOT_EXPORT_FORMAT\";\n        case STATUS_FILE_ENCRYPTED:\n            return \"STATUS_FILE_ENCRYPTED\";\n        case STATUS_WMI_GUID_NOT_FOUND:\n            return \"STATUS_WMI_GUID_NOT_FOUND\";\n        case STATUS_WMI_INSTANCE_NOT_FOUND:\n            return \"STATUS_WMI_INSTANCE_NOT_FOUND\";\n        case STATUS_WMI_ITEMID_NOT_FOUND:\n            return \"STATUS_WMI_ITEMID_NOT_FOUND\";\n        case STATUS_WMI_TRY_AGAIN:\n            return \"STATUS_WMI_TRY_AGAIN\";\n        case STATUS_SHARED_POLICY:\n            return \"STATUS_SHARED_POLICY\";\n        case STATUS_POLICY_OBJECT_NOT_FOUND:\n            return \"STATUS_POLICY_OBJECT_NOT_FOUND\";\n        case STATUS_POLICY_ONLY_IN_DS:\n            return \"STATUS_POLICY_ONLY_IN_DS\";\n        case STATUS_VOLUME_NOT_UPGRADED:\n            return \"STATUS_VOLUME_NOT_UPGRADED\";\n        case STATUS_REMOTE_STORAGE_NOT_ACTIVE:\n            return \"STATUS_REMOTE_STORAGE_NOT_ACTIVE\";\n        case STATUS_REMOTE_STORAGE_MEDIA_ERROR:\n            return \"STATUS_REMOTE_STORAGE_MEDIA_ERROR\";\n        case STATUS_NO_TRACKING_SERVICE:\n            return \"STATUS_NO_TRACKING_SERVICE\";\n        case STATUS_SERVER_SID_MISMATCH:\n            return \"STATUS_SERVER_SID_MISMATCH\";\n        case STATUS_DS_NO_ATTRIBUTE_OR_VALUE:\n            return \"STATUS_DS_NO_ATTRIBUTE_OR_VALUE\";\n        case STATUS_DS_INVALID_ATTRIBUTE_SYNTAX:\n            return \"STATUS_DS_INVALID_ATTRIBUTE_SYNTAX\";\n        case STATUS_DS_ATTRIBUTE_TYPE_UNDEFINED:\n            return \"STATUS_DS_ATTRIBUTE_TYPE_UNDEFINED\";\n        case STATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS:\n            return \"STATUS_DS_ATTRIBUTE_OR_VALUE_EXISTS\";\n        case STATUS_DS_BUSY:\n            return \"STATUS_DS_BUSY\";\n        case STATUS_DS_UNAVAILABLE:\n            return \"STATUS_DS_UNAVAILABLE\";\n        case STATUS_DS_NO_RIDS_ALLOCATED:\n            return \"STATUS_DS_NO_RIDS_ALLOCATED\";\n        case STATUS_DS_NO_MORE_RIDS:\n            return \"STATUS_DS_NO_MORE_RIDS\";\n        case STATUS_DS_INCORRECT_ROLE_OWNER:\n            return \"STATUS_DS_INCORRECT_ROLE_OWNER\";\n        case STATUS_DS_RIDMGR_INIT_ERROR:\n            return \"STATUS_DS_RIDMGR_INIT_ERROR\";\n        case STATUS_DS_OBJ_CLASS_VIOLATION:\n            return \"STATUS_DS_OBJ_CLASS_VIOLATION\";\n        case STATUS_DS_CANT_ON_NON_LEAF:\n            return \"STATUS_DS_CANT_ON_NON_LEAF\";\n        case STATUS_DS_CANT_ON_RDN:\n            return \"STATUS_DS_CANT_ON_RDN\";\n        case STATUS_DS_CANT_MOD_OBJ_CLASS:\n            return \"STATUS_DS_CANT_MOD_OBJ_CLASS\";\n        case STATUS_DS_CROSS_DOM_MOVE_FAILED:\n            return \"STATUS_DS_CROSS_DOM_MOVE_FAILED\";\n        case STATUS_DS_GC_NOT_AVAILABLE:\n            return \"STATUS_DS_GC_NOT_AVAILABLE\";\n        case STATUS_DIRECTORY_SERVICE_REQUIRED:\n            return \"STATUS_DIRECTORY_SERVICE_REQUIRED\";\n        case STATUS_REPARSE_ATTRIBUTE_CONFLICT:\n            return \"STATUS_REPARSE_ATTRIBUTE_CONFLICT\";\n        case STATUS_CANT_ENABLE_DENY_ONLY:\n            return \"STATUS_CANT_ENABLE_DENY_ONLY\";\n        case STATUS_FLOAT_MULTIPLE_FAULTS:\n            return \"STATUS_FLOAT_MULTIPLE_FAULTS\";\n        case STATUS_FLOAT_MULTIPLE_TRAPS:\n            return \"STATUS_FLOAT_MULTIPLE_TRAPS\";\n        case STATUS_DEVICE_REMOVED:\n            return \"STATUS_DEVICE_REMOVED\";\n        case STATUS_JOURNAL_DELETE_IN_PROGRESS:\n            return \"STATUS_JOURNAL_DELETE_IN_PROGRESS\";\n        case STATUS_JOURNAL_NOT_ACTIVE:\n            return \"STATUS_JOURNAL_NOT_ACTIVE\";\n        case STATUS_NOINTERFACE:\n            return \"STATUS_NOINTERFACE\";\n        case STATUS_DS_ADMIN_LIMIT_EXCEEDED:\n            return \"STATUS_DS_ADMIN_LIMIT_EXCEEDED\";\n        case STATUS_DRIVER_FAILED_SLEEP:\n            return \"STATUS_DRIVER_FAILED_SLEEP\";\n        case STATUS_MUTUAL_AUTHENTICATION_FAILED:\n            return \"STATUS_MUTUAL_AUTHENTICATION_FAILED\";\n        case STATUS_CORRUPT_SYSTEM_FILE:\n            return \"STATUS_CORRUPT_SYSTEM_FILE\";\n        case STATUS_DATATYPE_MISALIGNMENT_ERROR:\n            return \"STATUS_DATATYPE_MISALIGNMENT_ERROR\";\n        case STATUS_WMI_READ_ONLY:\n            return \"STATUS_WMI_READ_ONLY\";\n        case STATUS_WMI_SET_FAILURE:\n            return \"STATUS_WMI_SET_FAILURE\";\n        case STATUS_COMMITMENT_MINIMUM:\n            return \"STATUS_COMMITMENT_MINIMUM\";\n        case STATUS_REG_NAT_CONSUMPTION:\n            return \"STATUS_REG_NAT_CONSUMPTION\";\n        case STATUS_TRANSPORT_FULL:\n            return \"STATUS_TRANSPORT_FULL\";\n        case STATUS_DS_SAM_INIT_FAILURE:\n            return \"STATUS_DS_SAM_INIT_FAILURE\";\n        case STATUS_ONLY_IF_CONNECTED:\n            return \"STATUS_ONLY_IF_CONNECTED\";\n        case STATUS_DS_SENSITIVE_GROUP_VIOLATION:\n            return \"STATUS_DS_SENSITIVE_GROUP_VIOLATION\";\n        case STATUS_PNP_RESTART_ENUMERATION:\n            return \"STATUS_PNP_RESTART_ENUMERATION\";\n        case STATUS_JOURNAL_ENTRY_DELETED:\n            return \"STATUS_JOURNAL_ENTRY_DELETED\";\n        case STATUS_DS_CANT_MOD_PRIMARYGROUPID:\n            return \"STATUS_DS_CANT_MOD_PRIMARYGROUPID\";\n        case STATUS_SYSTEM_IMAGE_BAD_SIGNATURE:\n            return \"STATUS_SYSTEM_IMAGE_BAD_SIGNATURE\";\n        case STATUS_PNP_REBOOT_REQUIRED:\n            return \"STATUS_PNP_REBOOT_REQUIRED\";\n        case STATUS_POWER_STATE_INVALID:\n            return \"STATUS_POWER_STATE_INVALID\";\n        case STATUS_DS_INVALID_GROUP_TYPE:\n            return \"STATUS_DS_INVALID_GROUP_TYPE\";\n        case STATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN:\n            return \"STATUS_DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN\";\n        case STATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN:\n            return \"STATUS_DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN\";\n        case STATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER:\n            return \"STATUS_DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER\";\n        case STATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER:\n            return \"STATUS_DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER\";\n        case STATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER:\n            return \"STATUS_DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER\";\n        case STATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER:\n            return \"STATUS_DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER\";\n        case STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER:\n            return \"STATUS_DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER\";\n        case STATUS_DS_HAVE_PRIMARY_MEMBERS:\n            return \"STATUS_DS_HAVE_PRIMARY_MEMBERS\";\n        case STATUS_WMI_NOT_SUPPORTED:\n            return \"STATUS_WMI_NOT_SUPPORTED\";\n        case STATUS_INSUFFICIENT_POWER:\n            return \"STATUS_INSUFFICIENT_POWER\";\n        case STATUS_SAM_NEED_BOOTKEY_PASSWORD:\n            return \"STATUS_SAM_NEED_BOOTKEY_PASSWORD\";\n        case STATUS_SAM_NEED_BOOTKEY_FLOPPY:\n            return \"STATUS_SAM_NEED_BOOTKEY_FLOPPY\";\n        case STATUS_DS_CANT_START:\n            return \"STATUS_DS_CANT_START\";\n        case STATUS_DS_INIT_FAILURE:\n            return \"STATUS_DS_INIT_FAILURE\";\n        case STATUS_SAM_INIT_FAILURE:\n            return \"STATUS_SAM_INIT_FAILURE\";\n        case STATUS_DS_GC_REQUIRED:\n            return \"STATUS_DS_GC_REQUIRED\";\n        case STATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY:\n            return \"STATUS_DS_LOCAL_MEMBER_OF_LOCAL_ONLY\";\n        case STATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS:\n            return \"STATUS_DS_NO_FPO_IN_UNIVERSAL_GROUPS\";\n        case STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED:\n            return \"STATUS_DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED\";\n        case STATUS_CURRENT_DOMAIN_NOT_ALLOWED:\n            return \"STATUS_CURRENT_DOMAIN_NOT_ALLOWED\";\n        case STATUS_CANNOT_MAKE:\n            return \"STATUS_CANNOT_MAKE\";\n        case STATUS_SYSTEM_SHUTDOWN:\n            return \"STATUS_SYSTEM_SHUTDOWN\";\n        case STATUS_DS_INIT_FAILURE_CONSOLE:\n            return \"STATUS_DS_INIT_FAILURE_CONSOLE\";\n        case STATUS_DS_SAM_INIT_FAILURE_CONSOLE:\n            return \"STATUS_DS_SAM_INIT_FAILURE_CONSOLE\";\n        case STATUS_UNFINISHED_CONTEXT_DELETED:\n            return \"STATUS_UNFINISHED_CONTEXT_DELETED\";\n        case STATUS_NO_TGT_REPLY:\n            return \"STATUS_NO_TGT_REPLY\";\n        case STATUS_OBJECTID_NOT_FOUND:\n            return \"STATUS_OBJECTID_NOT_FOUND\";\n        case STATUS_NO_IP_ADDRESSES:\n            return \"STATUS_NO_IP_ADDRESSES\";\n        case STATUS_WRONG_CREDENTIAL_HANDLE:\n            return \"STATUS_WRONG_CREDENTIAL_HANDLE\";\n        case STATUS_CRYPTO_SYSTEM_INVALID:\n            return \"STATUS_CRYPTO_SYSTEM_INVALID\";\n        case STATUS_MAX_REFERRALS_EXCEEDED:\n            return \"STATUS_MAX_REFERRALS_EXCEEDED\";\n        case STATUS_MUST_BE_KDC:\n            return \"STATUS_MUST_BE_KDC\";\n        case STATUS_STRONG_CRYPTO_NOT_SUPPORTED:\n            return \"STATUS_STRONG_CRYPTO_NOT_SUPPORTED\";\n        case STATUS_TOO_MANY_PRINCIPALS:\n            return \"STATUS_TOO_MANY_PRINCIPALS\";\n        case STATUS_NO_PA_DATA:\n            return \"STATUS_NO_PA_DATA\";\n        case STATUS_PKINIT_NAME_MISMATCH:\n            return \"STATUS_PKINIT_NAME_MISMATCH\";\n        case STATUS_SMARTCARD_LOGON_REQUIRED:\n            return \"STATUS_SMARTCARD_LOGON_REQUIRED\";\n        case STATUS_KDC_INVALID_REQUEST:\n            return \"STATUS_KDC_INVALID_REQUEST\";\n        case STATUS_KDC_UNABLE_TO_REFER:\n            return \"STATUS_KDC_UNABLE_TO_REFER\";\n        case STATUS_KDC_UNKNOWN_ETYPE:\n            return \"STATUS_KDC_UNKNOWN_ETYPE\";\n        case STATUS_SHUTDOWN_IN_PROGRESS:\n            return \"STATUS_SHUTDOWN_IN_PROGRESS\";\n        case STATUS_SERVER_SHUTDOWN_IN_PROGRESS:\n            return \"STATUS_SERVER_SHUTDOWN_IN_PROGRESS\";\n        case STATUS_NOT_SUPPORTED_ON_SBS:\n            return \"STATUS_NOT_SUPPORTED_ON_SBS\";\n        case STATUS_WMI_GUID_DISCONNECTED:\n            return \"STATUS_WMI_GUID_DISCONNECTED\";\n        case STATUS_WMI_ALREADY_DISABLED:\n            return \"STATUS_WMI_ALREADY_DISABLED\";\n        case STATUS_WMI_ALREADY_ENABLED:\n            return \"STATUS_WMI_ALREADY_ENABLED\";\n        case STATUS_MFT_TOO_FRAGMENTED:\n            return \"STATUS_MFT_TOO_FRAGMENTED\";\n        case STATUS_COPY_PROTECTION_FAILURE:\n            return \"STATUS_COPY_PROTECTION_FAILURE\";\n        case STATUS_CSS_AUTHENTICATION_FAILURE:\n            return \"STATUS_CSS_AUTHENTICATION_FAILURE\";\n        case STATUS_CSS_KEY_NOT_PRESENT:\n            return \"STATUS_CSS_KEY_NOT_PRESENT\";\n        case STATUS_CSS_KEY_NOT_ESTABLISHED:\n            return \"STATUS_CSS_KEY_NOT_ESTABLISHED\";\n        case STATUS_CSS_SCRAMBLED_SECTOR:\n            return \"STATUS_CSS_SCRAMBLED_SECTOR\";\n        case STATUS_CSS_REGION_MISMATCH:\n            return \"STATUS_CSS_REGION_MISMATCH\";\n        case STATUS_CSS_RESETS_EXHAUSTED:\n            return \"STATUS_CSS_RESETS_EXHAUSTED\";\n        case STATUS_PKINIT_FAILURE:\n            return \"STATUS_PKINIT_FAILURE\";\n        case STATUS_SMARTCARD_SUBSYSTEM_FAILURE:\n            return \"STATUS_SMARTCARD_SUBSYSTEM_FAILURE\";\n        case STATUS_NO_KERB_KEY:\n            return \"STATUS_NO_KERB_KEY\";\n        case STATUS_HOST_DOWN:\n            return \"STATUS_HOST_DOWN\";\n        case STATUS_UNSUPPORTED_PREAUTH:\n            return \"STATUS_UNSUPPORTED_PREAUTH\";\n        case STATUS_EFS_ALG_BLOB_TOO_BIG:\n            return \"STATUS_EFS_ALG_BLOB_TOO_BIG\";\n        case STATUS_PORT_NOT_SET:\n            return \"STATUS_PORT_NOT_SET\";\n        case STATUS_DEBUGGER_INACTIVE:\n            return \"STATUS_DEBUGGER_INACTIVE\";\n        case STATUS_DS_VERSION_CHECK_FAILURE:\n            return \"STATUS_DS_VERSION_CHECK_FAILURE\";\n        case STATUS_AUDITING_DISABLED:\n            return \"STATUS_AUDITING_DISABLED\";\n        case STATUS_PRENT4_MACHINE_ACCOUNT:\n            return \"STATUS_PRENT4_MACHINE_ACCOUNT\";\n        case STATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER:\n            return \"STATUS_DS_AG_CANT_HAVE_UNIVERSAL_MEMBER\";\n        case STATUS_INVALID_IMAGE_WIN_32:\n            return \"STATUS_INVALID_IMAGE_WIN_32\";\n        case STATUS_INVALID_IMAGE_WIN_64:\n            return \"STATUS_INVALID_IMAGE_WIN_64\";\n        case STATUS_BAD_BINDINGS:\n            return \"STATUS_BAD_BINDINGS\";\n        case STATUS_NETWORK_SESSION_EXPIRED:\n            return \"STATUS_NETWORK_SESSION_EXPIRED\";\n        case STATUS_APPHELP_BLOCK:\n            return \"STATUS_APPHELP_BLOCK\";\n        case STATUS_ALL_SIDS_FILTERED:\n            return \"STATUS_ALL_SIDS_FILTERED\";\n        case STATUS_NOT_SAFE_MODE_DRIVER:\n            return \"STATUS_NOT_SAFE_MODE_DRIVER\";\n        case STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT:\n            return \"STATUS_ACCESS_DISABLED_BY_POLICY_DEFAULT\";\n        case STATUS_ACCESS_DISABLED_BY_POLICY_PATH:\n            return \"STATUS_ACCESS_DISABLED_BY_POLICY_PATH\";\n        case STATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER:\n            return \"STATUS_ACCESS_DISABLED_BY_POLICY_PUBLISHER\";\n        case STATUS_ACCESS_DISABLED_BY_POLICY_OTHER:\n            return \"STATUS_ACCESS_DISABLED_BY_POLICY_OTHER\";\n        case STATUS_FAILED_DRIVER_ENTRY:\n            return \"STATUS_FAILED_DRIVER_ENTRY\";\n        case STATUS_DEVICE_ENUMERATION_ERROR:\n            return \"STATUS_DEVICE_ENUMERATION_ERROR\";\n        case STATUS_MOUNT_POINT_NOT_RESOLVED:\n            return \"STATUS_MOUNT_POINT_NOT_RESOLVED\";\n        case STATUS_INVALID_DEVICE_OBJECT_PARAMETER:\n            return \"STATUS_INVALID_DEVICE_OBJECT_PARAMETER\";\n        case STATUS_MCA_OCCURED:\n            return \"STATUS_MCA_OCCURED\";\n        case STATUS_DRIVER_BLOCKED_CRITICAL:\n            return \"STATUS_DRIVER_BLOCKED_CRITICAL\";\n        case STATUS_DRIVER_BLOCKED:\n            return \"STATUS_DRIVER_BLOCKED\";\n        case STATUS_DRIVER_DATABASE_ERROR:\n            return \"STATUS_DRIVER_DATABASE_ERROR\";\n        case STATUS_SYSTEM_HIVE_TOO_LARGE:\n            return \"STATUS_SYSTEM_HIVE_TOO_LARGE\";\n        case STATUS_INVALID_IMPORT_OF_NON_DLL:\n            return \"STATUS_INVALID_IMPORT_OF_NON_DLL\";\n        case STATUS_NO_SECRETS:\n            return \"STATUS_NO_SECRETS\";\n        case STATUS_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY:\n            return \"STATUS_ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY\";\n        case STATUS_FAILED_STACK_SWITCH:\n            return \"STATUS_FAILED_STACK_SWITCH\";\n        case STATUS_HEAP_CORRUPTION:\n            return \"STATUS_HEAP_CORRUPTION\";\n        case STATUS_SMARTCARD_WRONG_PIN:\n            return \"STATUS_SMARTCARD_WRONG_PIN\";\n        case STATUS_SMARTCARD_CARD_BLOCKED:\n            return \"STATUS_SMARTCARD_CARD_BLOCKED\";\n        case STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED:\n            return \"STATUS_SMARTCARD_CARD_NOT_AUTHENTICATED\";\n        case STATUS_SMARTCARD_NO_CARD:\n            return \"STATUS_SMARTCARD_NO_CARD\";\n        case STATUS_SMARTCARD_NO_KEY_CONTAINER:\n            return \"STATUS_SMARTCARD_NO_KEY_CONTAINER\";\n        case STATUS_SMARTCARD_NO_CERTIFICATE:\n            return \"STATUS_SMARTCARD_NO_CERTIFICATE\";\n        case STATUS_SMARTCARD_NO_KEYSET:\n            return \"STATUS_SMARTCARD_NO_KEYSET\";\n        case STATUS_SMARTCARD_IO_ERROR:\n            return \"STATUS_SMARTCARD_IO_ERROR\";\n        case STATUS_DOWNGRADE_DETECTED:\n            return \"STATUS_DOWNGRADE_DETECTED\";\n        case STATUS_SMARTCARD_CERT_REVOKED:\n            return \"STATUS_SMARTCARD_CERT_REVOKED\";\n        case STATUS_ISSUING_CA_UNTRUSTED:\n            return \"STATUS_ISSUING_CA_UNTRUSTED\";\n        case STATUS_REVOCATION_OFFLINE_C:\n            return \"STATUS_REVOCATION_OFFLINE_C\";\n        case STATUS_PKINIT_CLIENT_FAILURE:\n            return \"STATUS_PKINIT_CLIENT_FAILURE\";\n        case STATUS_SMARTCARD_CERT_EXPIRED:\n            return \"STATUS_SMARTCARD_CERT_EXPIRED\";\n        case STATUS_DRIVER_FAILED_PRIOR_UNLOAD:\n            return \"STATUS_DRIVER_FAILED_PRIOR_UNLOAD\";\n        case STATUS_SMARTCARD_SILENT_CONTEXT:\n            return \"STATUS_SMARTCARD_SILENT_CONTEXT\";\n        case STATUS_PER_USER_TRUST_QUOTA_EXCEEDED:\n            return \"STATUS_PER_USER_TRUST_QUOTA_EXCEEDED\";\n        case STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED:\n            return \"STATUS_ALL_USER_TRUST_QUOTA_EXCEEDED\";\n        case STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED:\n            return \"STATUS_USER_DELETE_TRUST_QUOTA_EXCEEDED\";\n        case STATUS_DS_NAME_NOT_UNIQUE:\n            return \"STATUS_DS_NAME_NOT_UNIQUE\";\n        case STATUS_DS_DUPLICATE_ID_FOUND:\n            return \"STATUS_DS_DUPLICATE_ID_FOUND\";\n        case STATUS_DS_GROUP_CONVERSION_ERROR:\n            return \"STATUS_DS_GROUP_CONVERSION_ERROR\";\n        case STATUS_VOLSNAP_PREPARE_HIBERNATE:\n            return \"STATUS_VOLSNAP_PREPARE_HIBERNATE\";\n        case STATUS_USER2USER_REQUIRED:\n            return \"STATUS_USER2USER_REQUIRED\";\n        case STATUS_STACK_BUFFER_OVERRUN:\n            return \"STATUS_STACK_BUFFER_OVERRUN\";\n        case STATUS_NO_S4U_PROT_SUPPORT:\n            return \"STATUS_NO_S4U_PROT_SUPPORT\";\n        case STATUS_CROSSREALM_DELEGATION_FAILURE:\n            return \"STATUS_CROSSREALM_DELEGATION_FAILURE\";\n        case STATUS_REVOCATION_OFFLINE_KDC:\n            return \"STATUS_REVOCATION_OFFLINE_KDC\";\n        case STATUS_ISSUING_CA_UNTRUSTED_KDC:\n            return \"STATUS_ISSUING_CA_UNTRUSTED_KDC\";\n        case STATUS_KDC_CERT_EXPIRED:\n            return \"STATUS_KDC_CERT_EXPIRED\";\n        case STATUS_KDC_CERT_REVOKED:\n            return \"STATUS_KDC_CERT_REVOKED\";\n        case STATUS_PARAMETER_QUOTA_EXCEEDED:\n            return \"STATUS_PARAMETER_QUOTA_EXCEEDED\";\n        case STATUS_HIBERNATION_FAILURE:\n            return \"STATUS_HIBERNATION_FAILURE\";\n        case STATUS_DELAY_LOAD_FAILED:\n            return \"STATUS_DELAY_LOAD_FAILED\";\n        case STATUS_AUTHENTICATION_FIREWALL_FAILED:\n            return \"STATUS_AUTHENTICATION_FIREWALL_FAILED\";\n        case STATUS_VDM_DISALLOWED:\n            return \"STATUS_VDM_DISALLOWED\";\n        case STATUS_HUNG_DISPLAY_DRIVER_THREAD:\n            return \"STATUS_HUNG_DISPLAY_DRIVER_THREAD\";\n        case STATUS_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE:\n            return \"STATUS_INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE\";\n        case STATUS_INVALID_CRUNTIME_PARAMETER:\n            return \"STATUS_INVALID_CRUNTIME_PARAMETER\";\n        case STATUS_NTLM_BLOCKED:\n            return \"STATUS_NTLM_BLOCKED\";\n        case STATUS_DS_SRC_SID_EXISTS_IN_FOREST:\n            return \"STATUS_DS_SRC_SID_EXISTS_IN_FOREST\";\n        case STATUS_DS_DOMAIN_NAME_EXISTS_IN_FOREST:\n            return \"STATUS_DS_DOMAIN_NAME_EXISTS_IN_FOREST\";\n        case STATUS_DS_FLAT_NAME_EXISTS_IN_FOREST:\n            return \"STATUS_DS_FLAT_NAME_EXISTS_IN_FOREST\";\n        case STATUS_INVALID_USER_PRINCIPAL_NAME:\n            return \"STATUS_INVALID_USER_PRINCIPAL_NAME\";\n        case STATUS_ASSERTION_FAILURE:\n            return \"STATUS_ASSERTION_FAILURE\";\n        case STATUS_VERIFIER_STOP:\n            return \"STATUS_VERIFIER_STOP\";\n        case STATUS_CALLBACK_POP_STACK:\n            return \"STATUS_CALLBACK_POP_STACK\";\n        case STATUS_INCOMPATIBLE_DRIVER_BLOCKED:\n            return \"STATUS_INCOMPATIBLE_DRIVER_BLOCKED\";\n        case STATUS_HIVE_UNLOADED:\n            return \"STATUS_HIVE_UNLOADED\";\n        case STATUS_COMPRESSION_DISABLED:\n            return \"STATUS_COMPRESSION_DISABLED\";\n        case STATUS_FILE_SYSTEM_LIMITATION:\n            return \"STATUS_FILE_SYSTEM_LIMITATION\";\n        case STATUS_INVALID_IMAGE_HASH:\n            return \"STATUS_INVALID_IMAGE_HASH\";\n        case STATUS_NOT_CAPABLE:\n            return \"STATUS_NOT_CAPABLE\";\n        case STATUS_REQUEST_OUT_OF_SEQUENCE:\n            return \"STATUS_REQUEST_OUT_OF_SEQUENCE\";\n        case STATUS_IMPLEMENTATION_LIMIT:\n            return \"STATUS_IMPLEMENTATION_LIMIT\";\n        case STATUS_ELEVATION_REQUIRED:\n            return \"STATUS_ELEVATION_REQUIRED\";\n        case STATUS_NO_SECURITY_CONTEXT:\n            return \"STATUS_NO_SECURITY_CONTEXT\";\n        case STATUS_PKU2U_CERT_FAILURE:\n            return \"STATUS_PKU2U_CERT_FAILURE\";\n        case STATUS_BEYOND_VDL:\n            return \"STATUS_BEYOND_VDL\";\n        case STATUS_ENCOUNTERED_WRITE_IN_PROGRESS:\n            return \"STATUS_ENCOUNTERED_WRITE_IN_PROGRESS\";\n        case STATUS_PTE_CHANGED:\n            return \"STATUS_PTE_CHANGED\";\n        case STATUS_PURGE_FAILED:\n            return \"STATUS_PURGE_FAILED\";\n        case STATUS_CRED_REQUIRES_CONFIRMATION:\n            return \"STATUS_CRED_REQUIRES_CONFIRMATION\";\n        case STATUS_CS_ENCRYPTION_INVALID_SERVER_RESPONSE:\n            return \"STATUS_CS_ENCRYPTION_INVALID_SERVER_RESPONSE\";\n        case STATUS_CS_ENCRYPTION_UNSUPPORTED_SERVER:\n            return \"STATUS_CS_ENCRYPTION_UNSUPPORTED_SERVER\";\n        case STATUS_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE:\n            return \"STATUS_CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE\";\n        case STATUS_CS_ENCRYPTION_NEW_ENCRYPTED_FILE:\n            return \"STATUS_CS_ENCRYPTION_NEW_ENCRYPTED_FILE\";\n        case STATUS_CS_ENCRYPTION_FILE_NOT_CSE:\n            return \"STATUS_CS_ENCRYPTION_FILE_NOT_CSE\";\n        case STATUS_INVALID_LABEL:\n            return \"STATUS_INVALID_LABEL\";\n        case STATUS_DRIVER_PROCESS_TERMINATED:\n            return \"STATUS_DRIVER_PROCESS_TERMINATED\";\n        case STATUS_AMBIGUOUS_SYSTEM_DEVICE:\n            return \"STATUS_AMBIGUOUS_SYSTEM_DEVICE\";\n        case STATUS_SYSTEM_DEVICE_NOT_FOUND:\n            return \"STATUS_SYSTEM_DEVICE_NOT_FOUND\";\n        case STATUS_RESTART_BOOT_APPLICATION:\n            return \"STATUS_RESTART_BOOT_APPLICATION\";\n        case STATUS_INSUFFICIENT_NVRAM_RESOURCES:\n            return \"STATUS_INSUFFICIENT_NVRAM_RESOURCES\";\n        case STATUS_INVALID_TASK_NAME:\n            return \"STATUS_INVALID_TASK_NAME\";\n        case STATUS_INVALID_TASK_INDEX:\n            return \"STATUS_INVALID_TASK_INDEX\";\n        case STATUS_THREAD_ALREADY_IN_TASK:\n            return \"STATUS_THREAD_ALREADY_IN_TASK\";\n        case STATUS_CALLBACK_BYPASS:\n            return \"STATUS_CALLBACK_BYPASS\";\n        case STATUS_FAIL_FAST_EXCEPTION:\n            return \"STATUS_FAIL_FAST_EXCEPTION\";\n        case STATUS_IMAGE_CERT_REVOKED:\n            return \"STATUS_IMAGE_CERT_REVOKED\";\n        case STATUS_PORT_CLOSED:\n            return \"STATUS_PORT_CLOSED\";\n        case STATUS_MESSAGE_LOST:\n            return \"STATUS_MESSAGE_LOST\";\n        case STATUS_INVALID_MESSAGE:\n            return \"STATUS_INVALID_MESSAGE\";\n        case STATUS_REQUEST_CANCELED:\n            return \"STATUS_REQUEST_CANCELED\";\n        case STATUS_RECURSIVE_DISPATCH:\n            return \"STATUS_RECURSIVE_DISPATCH\";\n        case STATUS_LPC_RECEIVE_BUFFER_EXPECTED:\n            return \"STATUS_LPC_RECEIVE_BUFFER_EXPECTED\";\n        case STATUS_LPC_INVALID_CONNECTION_USAGE:\n            return \"STATUS_LPC_INVALID_CONNECTION_USAGE\";\n        case STATUS_LPC_REQUESTS_NOT_ALLOWED:\n            return \"STATUS_LPC_REQUESTS_NOT_ALLOWED\";\n        case STATUS_RESOURCE_IN_USE:\n            return \"STATUS_RESOURCE_IN_USE\";\n        case STATUS_HARDWARE_MEMORY_ERROR:\n            return \"STATUS_HARDWARE_MEMORY_ERROR\";\n        case STATUS_THREADPOOL_HANDLE_EXCEPTION:\n            return \"STATUS_THREADPOOL_HANDLE_EXCEPTION\";\n        case STATUS_THREADPOOL_SET_EVENT_ON_COMPLETION_FAILED:\n            return \"STATUS_THREADPOOL_SET_EVENT_ON_COMPLETION_FAILED\";\n        case STATUS_THREADPOOL_RELEASE_SEMAPHORE_ON_COMPLETION_FAILED:\n            return \"STATUS_THREADPOOL_RELEASE_SEMAPHORE_ON_COMPLETION_FAILED\";\n        case STATUS_THREADPOOL_RELEASE_MUTEX_ON_COMPLETION_FAILED:\n            return \"STATUS_THREADPOOL_RELEASE_MUTEX_ON_COMPLETION_FAILED\";\n        case STATUS_THREADPOOL_FREE_LIBRARY_ON_COMPLETION_FAILED:\n            return \"STATUS_THREADPOOL_FREE_LIBRARY_ON_COMPLETION_FAILED\";\n        case STATUS_THREADPOOL_RELEASED_DURING_OPERATION:\n            return \"STATUS_THREADPOOL_RELEASED_DURING_OPERATION\";\n        case STATUS_CALLBACK_RETURNED_WHILE_IMPERSONATING:\n            return \"STATUS_CALLBACK_RETURNED_WHILE_IMPERSONATING\";\n        case STATUS_APC_RETURNED_WHILE_IMPERSONATING:\n            return \"STATUS_APC_RETURNED_WHILE_IMPERSONATING\";\n        case STATUS_PROCESS_IS_PROTECTED:\n            return \"STATUS_PROCESS_IS_PROTECTED\";\n        case STATUS_MCA_EXCEPTION:\n            return \"STATUS_MCA_EXCEPTION\";\n        case STATUS_CERTIFICATE_MAPPING_NOT_UNIQUE:\n            return \"STATUS_CERTIFICATE_MAPPING_NOT_UNIQUE\";\n        case STATUS_SYMLINK_CLASS_DISABLED:\n            return \"STATUS_SYMLINK_CLASS_DISABLED\";\n        case STATUS_INVALID_IDN_NORMALIZATION:\n            return \"STATUS_INVALID_IDN_NORMALIZATION\";\n        case STATUS_NO_UNICODE_TRANSLATION:\n            return \"STATUS_NO_UNICODE_TRANSLATION\";\n        case STATUS_ALREADY_REGISTERED:\n            return \"STATUS_ALREADY_REGISTERED\";\n        case STATUS_CONTEXT_MISMATCH:\n            return \"STATUS_CONTEXT_MISMATCH\";\n        case STATUS_PORT_ALREADY_HAS_COMPLETION_LIST:\n            return \"STATUS_PORT_ALREADY_HAS_COMPLETION_LIST\";\n        case STATUS_CALLBACK_RETURNED_THREAD_PRIORITY:\n            return \"STATUS_CALLBACK_RETURNED_THREAD_PRIORITY\";\n        case STATUS_INVALID_THREAD:\n            return \"STATUS_INVALID_THREAD\";\n        case STATUS_CALLBACK_RETURNED_TRANSACTION:\n            return \"STATUS_CALLBACK_RETURNED_TRANSACTION\";\n        case STATUS_CALLBACK_RETURNED_LDR_LOCK:\n            return \"STATUS_CALLBACK_RETURNED_LDR_LOCK\";\n        case STATUS_CALLBACK_RETURNED_LANG:\n            return \"STATUS_CALLBACK_RETURNED_LANG\";\n        case STATUS_CALLBACK_RETURNED_PRI_BACK:\n            return \"STATUS_CALLBACK_RETURNED_PRI_BACK\";\n        case STATUS_DISK_REPAIR_DISABLED:\n            return \"STATUS_DISK_REPAIR_DISABLED\";\n        case STATUS_DS_DOMAIN_RENAME_IN_PROGRESS:\n            return \"STATUS_DS_DOMAIN_RENAME_IN_PROGRESS\";\n        case STATUS_DISK_QUOTA_EXCEEDED:\n            return \"STATUS_DISK_QUOTA_EXCEEDED\";\n        case STATUS_CONTENT_BLOCKED:\n            return \"STATUS_CONTENT_BLOCKED\";\n        case STATUS_BAD_CLUSTERS:\n            return \"STATUS_BAD_CLUSTERS\";\n        case STATUS_VOLUME_DIRTY:\n            return \"STATUS_VOLUME_DIRTY\";\n        case STATUS_FILE_CHECKED_OUT:\n            return \"STATUS_FILE_CHECKED_OUT\";\n        case STATUS_CHECKOUT_REQUIRED:\n            return \"STATUS_CHECKOUT_REQUIRED\";\n        case STATUS_BAD_FILE_TYPE:\n            return \"STATUS_BAD_FILE_TYPE\";\n        case STATUS_FILE_TOO_LARGE:\n            return \"STATUS_FILE_TOO_LARGE\";\n        case STATUS_FORMS_AUTH_REQUIRED:\n            return \"STATUS_FORMS_AUTH_REQUIRED\";\n        case STATUS_VIRUS_INFECTED:\n            return \"STATUS_VIRUS_INFECTED\";\n        case STATUS_VIRUS_DELETED:\n            return \"STATUS_VIRUS_DELETED\";\n        case STATUS_BAD_MCFG_TABLE:\n            return \"STATUS_BAD_MCFG_TABLE\";\n        case STATUS_CANNOT_BREAK_OPLOCK:\n            return \"STATUS_CANNOT_BREAK_OPLOCK\";\n        case STATUS_WOW_ASSERTION:\n            return \"STATUS_WOW_ASSERTION\";\n        case STATUS_INVALID_SIGNATURE:\n            return \"STATUS_INVALID_SIGNATURE\";\n        case STATUS_HMAC_NOT_SUPPORTED:\n            return \"STATUS_HMAC_NOT_SUPPORTED\";\n        case STATUS_IPSEC_QUEUE_OVERFLOW:\n            return \"STATUS_IPSEC_QUEUE_OVERFLOW\";\n        case STATUS_ND_QUEUE_OVERFLOW:\n            return \"STATUS_ND_QUEUE_OVERFLOW\";\n        case STATUS_HOPLIMIT_EXCEEDED:\n            return \"STATUS_HOPLIMIT_EXCEEDED\";\n        case STATUS_PROTOCOL_NOT_SUPPORTED:\n            return \"STATUS_PROTOCOL_NOT_SUPPORTED\";\n        case STATUS_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED:\n            return \"STATUS_LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED\";\n        case STATUS_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR:\n            return \"STATUS_LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR\";\n        case STATUS_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR:\n            return \"STATUS_LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR\";\n        case STATUS_XML_PARSE_ERROR:\n            return \"STATUS_XML_PARSE_ERROR\";\n        case STATUS_XMLDSIG_ERROR:\n            return \"STATUS_XMLDSIG_ERROR\";\n        case STATUS_WRONG_COMPARTMENT:\n            return \"STATUS_WRONG_COMPARTMENT\";\n        case STATUS_AUTHIP_FAILURE:\n            return \"STATUS_AUTHIP_FAILURE\";\n        case STATUS_DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS:\n            return \"STATUS_DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS\";\n        case STATUS_DS_OID_NOT_FOUND:\n            return \"STATUS_DS_OID_NOT_FOUND\";\n        case STATUS_HASH_NOT_SUPPORTED:\n            return \"STATUS_HASH_NOT_SUPPORTED\";\n        case STATUS_HASH_NOT_PRESENT:\n            return \"STATUS_HASH_NOT_PRESENT\";\n        case DBG_NO_STATE_CHANGE:\n            return \"DBG_NO_STATE_CHANGE\";\n        case DBG_APP_NOT_IDLE:\n            return \"DBG_APP_NOT_IDLE\";\n        case RPC_NT_INVALID_STRING_BINDING:\n            return \"RPC_NT_INVALID_STRING_BINDING\";\n        case RPC_NT_WRONG_KIND_OF_BINDING:\n            return \"RPC_NT_WRONG_KIND_OF_BINDING\";\n        case RPC_NT_INVALID_BINDING:\n            return \"RPC_NT_INVALID_BINDING\";\n        case RPC_NT_PROTSEQ_NOT_SUPPORTED:\n            return \"RPC_NT_PROTSEQ_NOT_SUPPORTED\";\n        case RPC_NT_INVALID_RPC_PROTSEQ:\n            return \"RPC_NT_INVALID_RPC_PROTSEQ\";\n        case RPC_NT_INVALID_STRING_UUID:\n            return \"RPC_NT_INVALID_STRING_UUID\";\n        case RPC_NT_INVALID_ENDPOINT_FORMAT:\n            return \"RPC_NT_INVALID_ENDPOINT_FORMAT\";\n        case RPC_NT_INVALID_NET_ADDR:\n            return \"RPC_NT_INVALID_NET_ADDR\";\n        case RPC_NT_NO_ENDPOINT_FOUND:\n            return \"RPC_NT_NO_ENDPOINT_FOUND\";\n        case RPC_NT_INVALID_TIMEOUT:\n            return \"RPC_NT_INVALID_TIMEOUT\";\n        case RPC_NT_OBJECT_NOT_FOUND:\n            return \"RPC_NT_OBJECT_NOT_FOUND\";\n        case RPC_NT_ALREADY_REGISTERED:\n            return \"RPC_NT_ALREADY_REGISTERED\";\n        case RPC_NT_TYPE_ALREADY_REGISTERED:\n            return \"RPC_NT_TYPE_ALREADY_REGISTERED\";\n        case RPC_NT_ALREADY_LISTENING:\n            return \"RPC_NT_ALREADY_LISTENING\";\n        case RPC_NT_NO_PROTSEQS_REGISTERED:\n            return \"RPC_NT_NO_PROTSEQS_REGISTERED\";\n        case RPC_NT_NOT_LISTENING:\n            return \"RPC_NT_NOT_LISTENING\";\n        case RPC_NT_UNKNOWN_MGR_TYPE:\n            return \"RPC_NT_UNKNOWN_MGR_TYPE\";\n        case RPC_NT_UNKNOWN_IF:\n            return \"RPC_NT_UNKNOWN_IF\";\n        case RPC_NT_NO_BINDINGS:\n            return \"RPC_NT_NO_BINDINGS\";\n        case RPC_NT_NO_PROTSEQS:\n            return \"RPC_NT_NO_PROTSEQS\";\n        case RPC_NT_CANT_CREATE_ENDPOINT:\n            return \"RPC_NT_CANT_CREATE_ENDPOINT\";\n        case RPC_NT_OUT_OF_RESOURCES:\n            return \"RPC_NT_OUT_OF_RESOURCES\";\n        case RPC_NT_SERVER_UNAVAILABLE:\n            return \"RPC_NT_SERVER_UNAVAILABLE\";\n        case RPC_NT_SERVER_TOO_BUSY:\n            return \"RPC_NT_SERVER_TOO_BUSY\";\n        case RPC_NT_INVALID_NETWORK_OPTIONS:\n            return \"RPC_NT_INVALID_NETWORK_OPTIONS\";\n        case RPC_NT_NO_CALL_ACTIVE:\n            return \"RPC_NT_NO_CALL_ACTIVE\";\n        case RPC_NT_CALL_FAILED:\n            return \"RPC_NT_CALL_FAILED\";\n        case RPC_NT_CALL_FAILED_DNE:\n            return \"RPC_NT_CALL_FAILED_DNE\";\n        case RPC_NT_PROTOCOL_ERROR:\n            return \"RPC_NT_PROTOCOL_ERROR\";\n        case RPC_NT_UNSUPPORTED_TRANS_SYN:\n            return \"RPC_NT_UNSUPPORTED_TRANS_SYN\";\n        case RPC_NT_UNSUPPORTED_TYPE:\n            return \"RPC_NT_UNSUPPORTED_TYPE\";\n        case RPC_NT_INVALID_TAG:\n            return \"RPC_NT_INVALID_TAG\";\n        case RPC_NT_INVALID_BOUND:\n            return \"RPC_NT_INVALID_BOUND\";\n        case RPC_NT_NO_ENTRY_NAME:\n            return \"RPC_NT_NO_ENTRY_NAME\";\n        case RPC_NT_INVALID_NAME_SYNTAX:\n            return \"RPC_NT_INVALID_NAME_SYNTAX\";\n        case RPC_NT_UNSUPPORTED_NAME_SYNTAX:\n            return \"RPC_NT_UNSUPPORTED_NAME_SYNTAX\";\n        case RPC_NT_UUID_NO_ADDRESS:\n            return \"RPC_NT_UUID_NO_ADDRESS\";\n        case RPC_NT_DUPLICATE_ENDPOINT:\n            return \"RPC_NT_DUPLICATE_ENDPOINT\";\n        case RPC_NT_UNKNOWN_AUTHN_TYPE:\n            return \"RPC_NT_UNKNOWN_AUTHN_TYPE\";\n        case RPC_NT_MAX_CALLS_TOO_SMALL:\n            return \"RPC_NT_MAX_CALLS_TOO_SMALL\";\n        case RPC_NT_STRING_TOO_LONG:\n            return \"RPC_NT_STRING_TOO_LONG\";\n        case RPC_NT_PROTSEQ_NOT_FOUND:\n            return \"RPC_NT_PROTSEQ_NOT_FOUND\";\n        case RPC_NT_PROCNUM_OUT_OF_RANGE:\n            return \"RPC_NT_PROCNUM_OUT_OF_RANGE\";\n        case RPC_NT_BINDING_HAS_NO_AUTH:\n            return \"RPC_NT_BINDING_HAS_NO_AUTH\";\n        case RPC_NT_UNKNOWN_AUTHN_SERVICE:\n            return \"RPC_NT_UNKNOWN_AUTHN_SERVICE\";\n        case RPC_NT_UNKNOWN_AUTHN_LEVEL:\n            return \"RPC_NT_UNKNOWN_AUTHN_LEVEL\";\n        case RPC_NT_INVALID_AUTH_IDENTITY:\n            return \"RPC_NT_INVALID_AUTH_IDENTITY\";\n        case RPC_NT_UNKNOWN_AUTHZ_SERVICE:\n            return \"RPC_NT_UNKNOWN_AUTHZ_SERVICE\";\n        case EPT_NT_INVALID_ENTRY:\n            return \"EPT_NT_INVALID_ENTRY\";\n        case EPT_NT_CANT_PERFORM_OP:\n            return \"EPT_NT_CANT_PERFORM_OP\";\n        case EPT_NT_NOT_REGISTERED:\n            return \"EPT_NT_NOT_REGISTERED\";\n        case RPC_NT_NOTHING_TO_EXPORT:\n            return \"RPC_NT_NOTHING_TO_EXPORT\";\n        case RPC_NT_INCOMPLETE_NAME:\n            return \"RPC_NT_INCOMPLETE_NAME\";\n        case RPC_NT_INVALID_VERS_OPTION:\n            return \"RPC_NT_INVALID_VERS_OPTION\";\n        case RPC_NT_NO_MORE_MEMBERS:\n            return \"RPC_NT_NO_MORE_MEMBERS\";\n        case RPC_NT_NOT_ALL_OBJS_UNEXPORTED:\n            return \"RPC_NT_NOT_ALL_OBJS_UNEXPORTED\";\n        case RPC_NT_INTERFACE_NOT_FOUND:\n            return \"RPC_NT_INTERFACE_NOT_FOUND\";\n        case RPC_NT_ENTRY_ALREADY_EXISTS:\n            return \"RPC_NT_ENTRY_ALREADY_EXISTS\";\n        case RPC_NT_ENTRY_NOT_FOUND:\n            return \"RPC_NT_ENTRY_NOT_FOUND\";\n        case RPC_NT_NAME_SERVICE_UNAVAILABLE:\n            return \"RPC_NT_NAME_SERVICE_UNAVAILABLE\";\n        case RPC_NT_INVALID_NAF_ID:\n            return \"RPC_NT_INVALID_NAF_ID\";\n        case RPC_NT_CANNOT_SUPPORT:\n            return \"RPC_NT_CANNOT_SUPPORT\";\n        case RPC_NT_NO_CONTEXT_AVAILABLE:\n            return \"RPC_NT_NO_CONTEXT_AVAILABLE\";\n        case RPC_NT_INTERNAL_ERROR:\n            return \"RPC_NT_INTERNAL_ERROR\";\n        case RPC_NT_ZERO_DIVIDE:\n            return \"RPC_NT_ZERO_DIVIDE\";\n        case RPC_NT_ADDRESS_ERROR:\n            return \"RPC_NT_ADDRESS_ERROR\";\n        case RPC_NT_FP_DIV_ZERO:\n            return \"RPC_NT_FP_DIV_ZERO\";\n        case RPC_NT_FP_UNDERFLOW:\n            return \"RPC_NT_FP_UNDERFLOW\";\n        case RPC_NT_FP_OVERFLOW:\n            return \"RPC_NT_FP_OVERFLOW\";\n        case RPC_NT_CALL_IN_PROGRESS:\n            return \"RPC_NT_CALL_IN_PROGRESS\";\n        case RPC_NT_NO_MORE_BINDINGS:\n            return \"RPC_NT_NO_MORE_BINDINGS\";\n        case RPC_NT_GROUP_MEMBER_NOT_FOUND:\n            return \"RPC_NT_GROUP_MEMBER_NOT_FOUND\";\n        case EPT_NT_CANT_CREATE:\n            return \"EPT_NT_CANT_CREATE\";\n        case RPC_NT_INVALID_OBJECT:\n            return \"RPC_NT_INVALID_OBJECT\";\n        case RPC_NT_NO_INTERFACES:\n            return \"RPC_NT_NO_INTERFACES\";\n        case RPC_NT_CALL_CANCELLED:\n            return \"RPC_NT_CALL_CANCELLED\";\n        case RPC_NT_BINDING_INCOMPLETE:\n            return \"RPC_NT_BINDING_INCOMPLETE\";\n        case RPC_NT_COMM_FAILURE:\n            return \"RPC_NT_COMM_FAILURE\";\n        case RPC_NT_UNSUPPORTED_AUTHN_LEVEL:\n            return \"RPC_NT_UNSUPPORTED_AUTHN_LEVEL\";\n        case RPC_NT_NO_PRINC_NAME:\n            return \"RPC_NT_NO_PRINC_NAME\";\n        case RPC_NT_NOT_RPC_ERROR:\n            return \"RPC_NT_NOT_RPC_ERROR\";\n        case RPC_NT_SEC_PKG_ERROR:\n            return \"RPC_NT_SEC_PKG_ERROR\";\n        case RPC_NT_NOT_CANCELLED:\n            return \"RPC_NT_NOT_CANCELLED\";\n        case RPC_NT_INVALID_ASYNC_HANDLE:\n            return \"RPC_NT_INVALID_ASYNC_HANDLE\";\n        case RPC_NT_INVALID_ASYNC_CALL:\n            return \"RPC_NT_INVALID_ASYNC_CALL\";\n        case RPC_NT_PROXY_ACCESS_DENIED:\n            return \"RPC_NT_PROXY_ACCESS_DENIED\";\n        case RPC_NT_NO_MORE_ENTRIES:\n            return \"RPC_NT_NO_MORE_ENTRIES\";\n        case RPC_NT_SS_CHAR_TRANS_OPEN_FAIL:\n            return \"RPC_NT_SS_CHAR_TRANS_OPEN_FAIL\";\n        case RPC_NT_SS_CHAR_TRANS_SHORT_FILE:\n            return \"RPC_NT_SS_CHAR_TRANS_SHORT_FILE\";\n        case RPC_NT_SS_IN_NULL_CONTEXT:\n            return \"RPC_NT_SS_IN_NULL_CONTEXT\";\n        case RPC_NT_SS_CONTEXT_MISMATCH:\n            return \"RPC_NT_SS_CONTEXT_MISMATCH\";\n        case RPC_NT_SS_CONTEXT_DAMAGED:\n            return \"RPC_NT_SS_CONTEXT_DAMAGED\";\n        case RPC_NT_SS_HANDLES_MISMATCH:\n            return \"RPC_NT_SS_HANDLES_MISMATCH\";\n        case RPC_NT_SS_CANNOT_GET_CALL_HANDLE:\n            return \"RPC_NT_SS_CANNOT_GET_CALL_HANDLE\";\n        case RPC_NT_NULL_REF_POINTER:\n            return \"RPC_NT_NULL_REF_POINTER\";\n        case RPC_NT_ENUM_VALUE_OUT_OF_RANGE:\n            return \"RPC_NT_ENUM_VALUE_OUT_OF_RANGE\";\n        case RPC_NT_BYTE_COUNT_TOO_SMALL:\n            return \"RPC_NT_BYTE_COUNT_TOO_SMALL\";\n        case RPC_NT_BAD_STUB_DATA:\n            return \"RPC_NT_BAD_STUB_DATA\";\n        case RPC_NT_INVALID_ES_ACTION:\n            return \"RPC_NT_INVALID_ES_ACTION\";\n        case RPC_NT_WRONG_ES_VERSION:\n            return \"RPC_NT_WRONG_ES_VERSION\";\n        case RPC_NT_WRONG_STUB_VERSION:\n            return \"RPC_NT_WRONG_STUB_VERSION\";\n        case RPC_NT_INVALID_PIPE_OBJECT:\n            return \"RPC_NT_INVALID_PIPE_OBJECT\";\n        case RPC_NT_INVALID_PIPE_OPERATION:\n            return \"RPC_NT_INVALID_PIPE_OPERATION\";\n        case RPC_NT_WRONG_PIPE_VERSION:\n            return \"RPC_NT_WRONG_PIPE_VERSION\";\n        case RPC_NT_PIPE_CLOSED:\n            return \"RPC_NT_PIPE_CLOSED\";\n        case RPC_NT_PIPE_DISCIPLINE_ERROR:\n            return \"RPC_NT_PIPE_DISCIPLINE_ERROR\";\n        case RPC_NT_PIPE_EMPTY:\n            return \"RPC_NT_PIPE_EMPTY\";\n        case STATUS_PNP_BAD_MPS_TABLE:\n            return \"STATUS_PNP_BAD_MPS_TABLE\";\n        case STATUS_PNP_TRANSLATION_FAILED:\n            return \"STATUS_PNP_TRANSLATION_FAILED\";\n        case STATUS_PNP_IRQ_TRANSLATION_FAILED:\n            return \"STATUS_PNP_IRQ_TRANSLATION_FAILED\";\n        case STATUS_PNP_INVALID_ID:\n            return \"STATUS_PNP_INVALID_ID\";\n        case STATUS_IO_REISSUE_AS_CACHED:\n            return \"STATUS_IO_REISSUE_AS_CACHED\";\n        case STATUS_CTX_WINSTATION_NAME_INVALID:\n            return \"STATUS_CTX_WINSTATION_NAME_INVALID\";\n        case STATUS_CTX_INVALID_PD:\n            return \"STATUS_CTX_INVALID_PD\";\n        case STATUS_CTX_PD_NOT_FOUND:\n            return \"STATUS_CTX_PD_NOT_FOUND\";\n        case STATUS_CTX_CLOSE_PENDING:\n            return \"STATUS_CTX_CLOSE_PENDING\";\n        case STATUS_CTX_NO_OUTBUF:\n            return \"STATUS_CTX_NO_OUTBUF\";\n        case STATUS_CTX_MODEM_INF_NOT_FOUND:\n            return \"STATUS_CTX_MODEM_INF_NOT_FOUND\";\n        case STATUS_CTX_INVALID_MODEMNAME:\n            return \"STATUS_CTX_INVALID_MODEMNAME\";\n        case STATUS_CTX_RESPONSE_ERROR:\n            return \"STATUS_CTX_RESPONSE_ERROR\";\n        case STATUS_CTX_MODEM_RESPONSE_TIMEOUT:\n            return \"STATUS_CTX_MODEM_RESPONSE_TIMEOUT\";\n        case STATUS_CTX_MODEM_RESPONSE_NO_CARRIER:\n            return \"STATUS_CTX_MODEM_RESPONSE_NO_CARRIER\";\n        case STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE:\n            return \"STATUS_CTX_MODEM_RESPONSE_NO_DIALTONE\";\n        case STATUS_CTX_MODEM_RESPONSE_BUSY:\n            return \"STATUS_CTX_MODEM_RESPONSE_BUSY\";\n        case STATUS_CTX_MODEM_RESPONSE_VOICE:\n            return \"STATUS_CTX_MODEM_RESPONSE_VOICE\";\n        case STATUS_CTX_TD_ERROR:\n            return \"STATUS_CTX_TD_ERROR\";\n        case STATUS_CTX_LICENSE_CLIENT_INVALID:\n            return \"STATUS_CTX_LICENSE_CLIENT_INVALID\";\n        case STATUS_CTX_LICENSE_NOT_AVAILABLE:\n            return \"STATUS_CTX_LICENSE_NOT_AVAILABLE\";\n        case STATUS_CTX_LICENSE_EXPIRED:\n            return \"STATUS_CTX_LICENSE_EXPIRED\";\n        case STATUS_CTX_WINSTATION_NOT_FOUND:\n            return \"STATUS_CTX_WINSTATION_NOT_FOUND\";\n        case STATUS_CTX_WINSTATION_NAME_COLLISION:\n            return \"STATUS_CTX_WINSTATION_NAME_COLLISION\";\n        case STATUS_CTX_WINSTATION_BUSY:\n            return \"STATUS_CTX_WINSTATION_BUSY\";\n        case STATUS_CTX_BAD_VIDEO_MODE:\n            return \"STATUS_CTX_BAD_VIDEO_MODE\";\n        case STATUS_CTX_GRAPHICS_INVALID:\n            return \"STATUS_CTX_GRAPHICS_INVALID\";\n        case STATUS_CTX_NOT_CONSOLE:\n            return \"STATUS_CTX_NOT_CONSOLE\";\n        case STATUS_CTX_CLIENT_QUERY_TIMEOUT:\n            return \"STATUS_CTX_CLIENT_QUERY_TIMEOUT\";\n        case STATUS_CTX_CONSOLE_DISCONNECT:\n            return \"STATUS_CTX_CONSOLE_DISCONNECT\";\n        case STATUS_CTX_CONSOLE_CONNECT:\n            return \"STATUS_CTX_CONSOLE_CONNECT\";\n        case STATUS_CTX_SHADOW_DENIED:\n            return \"STATUS_CTX_SHADOW_DENIED\";\n        case STATUS_CTX_WINSTATION_ACCESS_DENIED:\n            return \"STATUS_CTX_WINSTATION_ACCESS_DENIED\";\n        case STATUS_CTX_INVALID_WD:\n            return \"STATUS_CTX_INVALID_WD\";\n        case STATUS_CTX_WD_NOT_FOUND:\n            return \"STATUS_CTX_WD_NOT_FOUND\";\n        case STATUS_CTX_SHADOW_INVALID:\n            return \"STATUS_CTX_SHADOW_INVALID\";\n        case STATUS_CTX_SHADOW_DISABLED:\n            return \"STATUS_CTX_SHADOW_DISABLED\";\n        case STATUS_RDP_PROTOCOL_ERROR:\n            return \"STATUS_RDP_PROTOCOL_ERROR\";\n        case STATUS_CTX_CLIENT_LICENSE_NOT_SET:\n            return \"STATUS_CTX_CLIENT_LICENSE_NOT_SET\";\n        case STATUS_CTX_CLIENT_LICENSE_IN_USE:\n            return \"STATUS_CTX_CLIENT_LICENSE_IN_USE\";\n        case STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE:\n            return \"STATUS_CTX_SHADOW_ENDED_BY_MODE_CHANGE\";\n        case STATUS_CTX_SHADOW_NOT_RUNNING:\n            return \"STATUS_CTX_SHADOW_NOT_RUNNING\";\n        case STATUS_CTX_LOGON_DISABLED:\n            return \"STATUS_CTX_LOGON_DISABLED\";\n        case STATUS_CTX_SECURITY_LAYER_ERROR:\n            return \"STATUS_CTX_SECURITY_LAYER_ERROR\";\n        case STATUS_TS_INCOMPATIBLE_SESSIONS:\n            return \"STATUS_TS_INCOMPATIBLE_SESSIONS\";\n        case STATUS_MUI_FILE_NOT_FOUND:\n            return \"STATUS_MUI_FILE_NOT_FOUND\";\n        case STATUS_MUI_INVALID_FILE:\n            return \"STATUS_MUI_INVALID_FILE\";\n        case STATUS_MUI_INVALID_RC_CONFIG:\n            return \"STATUS_MUI_INVALID_RC_CONFIG\";\n        case STATUS_MUI_INVALID_LOCALE_NAME:\n            return \"STATUS_MUI_INVALID_LOCALE_NAME\";\n        case STATUS_MUI_INVALID_ULTIMATEFALLBACK_NAME:\n            return \"STATUS_MUI_INVALID_ULTIMATEFALLBACK_NAME\";\n        case STATUS_MUI_FILE_NOT_LOADED:\n            return \"STATUS_MUI_FILE_NOT_LOADED\";\n        case STATUS_RESOURCE_ENUM_USER_STOP:\n            return \"STATUS_RESOURCE_ENUM_USER_STOP\";\n        case STATUS_CLUSTER_INVALID_NODE:\n            return \"STATUS_CLUSTER_INVALID_NODE\";\n        case STATUS_CLUSTER_NODE_EXISTS:\n            return \"STATUS_CLUSTER_NODE_EXISTS\";\n        case STATUS_CLUSTER_JOIN_IN_PROGRESS:\n            return \"STATUS_CLUSTER_JOIN_IN_PROGRESS\";\n        case STATUS_CLUSTER_NODE_NOT_FOUND:\n            return \"STATUS_CLUSTER_NODE_NOT_FOUND\";\n        case STATUS_CLUSTER_LOCAL_NODE_NOT_FOUND:\n            return \"STATUS_CLUSTER_LOCAL_NODE_NOT_FOUND\";\n        case STATUS_CLUSTER_NETWORK_EXISTS:\n            return \"STATUS_CLUSTER_NETWORK_EXISTS\";\n        case STATUS_CLUSTER_NETWORK_NOT_FOUND:\n            return \"STATUS_CLUSTER_NETWORK_NOT_FOUND\";\n        case STATUS_CLUSTER_NETINTERFACE_EXISTS:\n            return \"STATUS_CLUSTER_NETINTERFACE_EXISTS\";\n        case STATUS_CLUSTER_NETINTERFACE_NOT_FOUND:\n            return \"STATUS_CLUSTER_NETINTERFACE_NOT_FOUND\";\n        case STATUS_CLUSTER_INVALID_REQUEST:\n            return \"STATUS_CLUSTER_INVALID_REQUEST\";\n        case STATUS_CLUSTER_INVALID_NETWORK_PROVIDER:\n            return \"STATUS_CLUSTER_INVALID_NETWORK_PROVIDER\";\n        case STATUS_CLUSTER_NODE_DOWN:\n            return \"STATUS_CLUSTER_NODE_DOWN\";\n        case STATUS_CLUSTER_NODE_UNREACHABLE:\n            return \"STATUS_CLUSTER_NODE_UNREACHABLE\";\n        case STATUS_CLUSTER_NODE_NOT_MEMBER:\n            return \"STATUS_CLUSTER_NODE_NOT_MEMBER\";\n        case STATUS_CLUSTER_JOIN_NOT_IN_PROGRESS:\n            return \"STATUS_CLUSTER_JOIN_NOT_IN_PROGRESS\";\n        case STATUS_CLUSTER_INVALID_NETWORK:\n            return \"STATUS_CLUSTER_INVALID_NETWORK\";\n        case STATUS_CLUSTER_NO_NET_ADAPTERS:\n            return \"STATUS_CLUSTER_NO_NET_ADAPTERS\";\n        case STATUS_CLUSTER_NODE_UP:\n            return \"STATUS_CLUSTER_NODE_UP\";\n        case STATUS_CLUSTER_NODE_PAUSED:\n            return \"STATUS_CLUSTER_NODE_PAUSED\";\n        case STATUS_CLUSTER_NODE_NOT_PAUSED:\n            return \"STATUS_CLUSTER_NODE_NOT_PAUSED\";\n        case STATUS_CLUSTER_NO_SECURITY_CONTEXT:\n            return \"STATUS_CLUSTER_NO_SECURITY_CONTEXT\";\n        case STATUS_CLUSTER_NETWORK_NOT_INTERNAL:\n            return \"STATUS_CLUSTER_NETWORK_NOT_INTERNAL\";\n        case STATUS_CLUSTER_POISONED:\n            return \"STATUS_CLUSTER_POISONED\";\n        case STATUS_ACPI_INVALID_OPCODE:\n            return \"STATUS_ACPI_INVALID_OPCODE\";\n        case STATUS_ACPI_STACK_OVERFLOW:\n            return \"STATUS_ACPI_STACK_OVERFLOW\";\n        case STATUS_ACPI_ASSERT_FAILED:\n            return \"STATUS_ACPI_ASSERT_FAILED\";\n        case STATUS_ACPI_INVALID_INDEX:\n            return \"STATUS_ACPI_INVALID_INDEX\";\n        case STATUS_ACPI_INVALID_ARGUMENT:\n            return \"STATUS_ACPI_INVALID_ARGUMENT\";\n        case STATUS_ACPI_FATAL:\n            return \"STATUS_ACPI_FATAL\";\n        case STATUS_ACPI_INVALID_SUPERNAME:\n            return \"STATUS_ACPI_INVALID_SUPERNAME\";\n        case STATUS_ACPI_INVALID_ARGTYPE:\n            return \"STATUS_ACPI_INVALID_ARGTYPE\";\n        case STATUS_ACPI_INVALID_OBJTYPE:\n            return \"STATUS_ACPI_INVALID_OBJTYPE\";\n        case STATUS_ACPI_INVALID_TARGETTYPE:\n            return \"STATUS_ACPI_INVALID_TARGETTYPE\";\n        case STATUS_ACPI_INCORRECT_ARGUMENT_COUNT:\n            return \"STATUS_ACPI_INCORRECT_ARGUMENT_COUNT\";\n        case STATUS_ACPI_ADDRESS_NOT_MAPPED:\n            return \"STATUS_ACPI_ADDRESS_NOT_MAPPED\";\n        case STATUS_ACPI_INVALID_EVENTTYPE:\n            return \"STATUS_ACPI_INVALID_EVENTTYPE\";\n        case STATUS_ACPI_HANDLER_COLLISION:\n            return \"STATUS_ACPI_HANDLER_COLLISION\";\n        case STATUS_ACPI_INVALID_DATA:\n            return \"STATUS_ACPI_INVALID_DATA\";\n        case STATUS_ACPI_INVALID_REGION:\n            return \"STATUS_ACPI_INVALID_REGION\";\n        case STATUS_ACPI_INVALID_ACCESS_SIZE:\n            return \"STATUS_ACPI_INVALID_ACCESS_SIZE\";\n        case STATUS_ACPI_ACQUIRE_GLOBAL_LOCK:\n            return \"STATUS_ACPI_ACQUIRE_GLOBAL_LOCK\";\n        case STATUS_ACPI_ALREADY_INITIALIZED:\n            return \"STATUS_ACPI_ALREADY_INITIALIZED\";\n        case STATUS_ACPI_NOT_INITIALIZED:\n            return \"STATUS_ACPI_NOT_INITIALIZED\";\n        case STATUS_ACPI_INVALID_MUTEX_LEVEL:\n            return \"STATUS_ACPI_INVALID_MUTEX_LEVEL\";\n        case STATUS_ACPI_MUTEX_NOT_OWNED:\n            return \"STATUS_ACPI_MUTEX_NOT_OWNED\";\n        case STATUS_ACPI_MUTEX_NOT_OWNER:\n            return \"STATUS_ACPI_MUTEX_NOT_OWNER\";\n        case STATUS_ACPI_RS_ACCESS:\n            return \"STATUS_ACPI_RS_ACCESS\";\n        case STATUS_ACPI_INVALID_TABLE:\n            return \"STATUS_ACPI_INVALID_TABLE\";\n        case STATUS_ACPI_REG_HANDLER_FAILED:\n            return \"STATUS_ACPI_REG_HANDLER_FAILED\";\n        case STATUS_ACPI_POWER_REQUEST_FAILED:\n            return \"STATUS_ACPI_POWER_REQUEST_FAILED\";\n        case STATUS_SXS_SECTION_NOT_FOUND:\n            return \"STATUS_SXS_SECTION_NOT_FOUND\";\n        case STATUS_SXS_CANT_GEN_ACTCTX:\n            return \"STATUS_SXS_CANT_GEN_ACTCTX\";\n        case STATUS_SXS_INVALID_ACTCTXDATA_FORMAT:\n            return \"STATUS_SXS_INVALID_ACTCTXDATA_FORMAT\";\n        case STATUS_SXS_ASSEMBLY_NOT_FOUND:\n            return \"STATUS_SXS_ASSEMBLY_NOT_FOUND\";\n        case STATUS_SXS_MANIFEST_FORMAT_ERROR:\n            return \"STATUS_SXS_MANIFEST_FORMAT_ERROR\";\n        case STATUS_SXS_MANIFEST_PARSE_ERROR:\n            return \"STATUS_SXS_MANIFEST_PARSE_ERROR\";\n        case STATUS_SXS_ACTIVATION_CONTEXT_DISABLED:\n            return \"STATUS_SXS_ACTIVATION_CONTEXT_DISABLED\";\n        case STATUS_SXS_KEY_NOT_FOUND:\n            return \"STATUS_SXS_KEY_NOT_FOUND\";\n        case STATUS_SXS_VERSION_CONFLICT:\n            return \"STATUS_SXS_VERSION_CONFLICT\";\n        case STATUS_SXS_WRONG_SECTION_TYPE:\n            return \"STATUS_SXS_WRONG_SECTION_TYPE\";\n        case STATUS_SXS_THREAD_QUERIES_DISABLED:\n            return \"STATUS_SXS_THREAD_QUERIES_DISABLED\";\n        case STATUS_SXS_ASSEMBLY_MISSING:\n            return \"STATUS_SXS_ASSEMBLY_MISSING\";\n        case STATUS_SXS_PROCESS_DEFAULT_ALREADY_SET:\n            return \"STATUS_SXS_PROCESS_DEFAULT_ALREADY_SET\";\n        case STATUS_SXS_EARLY_DEACTIVATION:\n            return \"STATUS_SXS_EARLY_DEACTIVATION\";\n        case STATUS_SXS_INVALID_DEACTIVATION:\n            return \"STATUS_SXS_INVALID_DEACTIVATION\";\n        case STATUS_SXS_MULTIPLE_DEACTIVATION:\n            return \"STATUS_SXS_MULTIPLE_DEACTIVATION\";\n        case STATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY:\n            return \"STATUS_SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY\";\n        case STATUS_SXS_PROCESS_TERMINATION_REQUESTED:\n            return \"STATUS_SXS_PROCESS_TERMINATION_REQUESTED\";\n        case STATUS_SXS_CORRUPT_ACTIVATION_STACK:\n            return \"STATUS_SXS_CORRUPT_ACTIVATION_STACK\";\n        case STATUS_SXS_CORRUPTION:\n            return \"STATUS_SXS_CORRUPTION\";\n        case STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE:\n            return \"STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE\";\n        case STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_NAME:\n            return \"STATUS_SXS_INVALID_IDENTITY_ATTRIBUTE_NAME\";\n        case STATUS_SXS_IDENTITY_DUPLICATE_ATTRIBUTE:\n            return \"STATUS_SXS_IDENTITY_DUPLICATE_ATTRIBUTE\";\n        case STATUS_SXS_IDENTITY_PARSE_ERROR:\n            return \"STATUS_SXS_IDENTITY_PARSE_ERROR\";\n        case STATUS_SXS_COMPONENT_STORE_CORRUPT:\n            return \"STATUS_SXS_COMPONENT_STORE_CORRUPT\";\n        case STATUS_SXS_FILE_HASH_MISMATCH:\n            return \"STATUS_SXS_FILE_HASH_MISMATCH\";\n        case STATUS_SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT:\n            return \"STATUS_SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT\";\n        case STATUS_SXS_IDENTITIES_DIFFERENT:\n            return \"STATUS_SXS_IDENTITIES_DIFFERENT\";\n        case STATUS_SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT:\n            return \"STATUS_SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT\";\n        case STATUS_SXS_FILE_NOT_PART_OF_ASSEMBLY:\n            return \"STATUS_SXS_FILE_NOT_PART_OF_ASSEMBLY\";\n        case STATUS_ADVANCED_INSTALLER_FAILED:\n            return \"STATUS_ADVANCED_INSTALLER_FAILED\";\n        case STATUS_XML_ENCODING_MISMATCH:\n            return \"STATUS_XML_ENCODING_MISMATCH\";\n        case STATUS_SXS_MANIFEST_TOO_BIG:\n            return \"STATUS_SXS_MANIFEST_TOO_BIG\";\n        case STATUS_SXS_SETTING_NOT_REGISTERED:\n            return \"STATUS_SXS_SETTING_NOT_REGISTERED\";\n        case STATUS_SXS_TRANSACTION_CLOSURE_INCOMPLETE:\n            return \"STATUS_SXS_TRANSACTION_CLOSURE_INCOMPLETE\";\n        case STATUS_SMI_PRIMITIVE_INSTALLER_FAILED:\n            return \"STATUS_SMI_PRIMITIVE_INSTALLER_FAILED\";\n        case STATUS_GENERIC_COMMAND_FAILED:\n            return \"STATUS_GENERIC_COMMAND_FAILED\";\n        case STATUS_SXS_FILE_HASH_MISSING:\n            return \"STATUS_SXS_FILE_HASH_MISSING\";\n        case STATUS_TRANSACTIONAL_CONFLICT:\n            return \"STATUS_TRANSACTIONAL_CONFLICT\";\n        case STATUS_INVALID_TRANSACTION:\n            return \"STATUS_INVALID_TRANSACTION\";\n        case STATUS_TRANSACTION_NOT_ACTIVE:\n            return \"STATUS_TRANSACTION_NOT_ACTIVE\";\n        case STATUS_TM_INITIALIZATION_FAILED:\n            return \"STATUS_TM_INITIALIZATION_FAILED\";\n        case STATUS_RM_NOT_ACTIVE:\n            return \"STATUS_RM_NOT_ACTIVE\";\n        case STATUS_RM_METADATA_CORRUPT:\n            return \"STATUS_RM_METADATA_CORRUPT\";\n        case STATUS_TRANSACTION_NOT_JOINED:\n            return \"STATUS_TRANSACTION_NOT_JOINED\";\n        case STATUS_DIRECTORY_NOT_RM:\n            return \"STATUS_DIRECTORY_NOT_RM\";\n        case STATUS_TRANSACTIONS_UNSUPPORTED_REMOTE:\n            return \"STATUS_TRANSACTIONS_UNSUPPORTED_REMOTE\";\n        case STATUS_LOG_RESIZE_INVALID_SIZE:\n            return \"STATUS_LOG_RESIZE_INVALID_SIZE\";\n        case STATUS_REMOTE_FILE_VERSION_MISMATCH:\n            return \"STATUS_REMOTE_FILE_VERSION_MISMATCH\";\n        case STATUS_CRM_PROTOCOL_ALREADY_EXISTS:\n            return \"STATUS_CRM_PROTOCOL_ALREADY_EXISTS\";\n        case STATUS_TRANSACTION_PROPAGATION_FAILED:\n            return \"STATUS_TRANSACTION_PROPAGATION_FAILED\";\n        case STATUS_CRM_PROTOCOL_NOT_FOUND:\n            return \"STATUS_CRM_PROTOCOL_NOT_FOUND\";\n        case STATUS_TRANSACTION_SUPERIOR_EXISTS:\n            return \"STATUS_TRANSACTION_SUPERIOR_EXISTS\";\n        case STATUS_TRANSACTION_REQUEST_NOT_VALID:\n            return \"STATUS_TRANSACTION_REQUEST_NOT_VALID\";\n        case STATUS_TRANSACTION_NOT_REQUESTED:\n            return \"STATUS_TRANSACTION_NOT_REQUESTED\";\n        case STATUS_TRANSACTION_ALREADY_ABORTED:\n            return \"STATUS_TRANSACTION_ALREADY_ABORTED\";\n        case STATUS_TRANSACTION_ALREADY_COMMITTED:\n            return \"STATUS_TRANSACTION_ALREADY_COMMITTED\";\n        case STATUS_TRANSACTION_INVALID_MARSHALL_BUFFER:\n            return \"STATUS_TRANSACTION_INVALID_MARSHALL_BUFFER\";\n        case STATUS_CURRENT_TRANSACTION_NOT_VALID:\n            return \"STATUS_CURRENT_TRANSACTION_NOT_VALID\";\n        case STATUS_LOG_GROWTH_FAILED:\n            return \"STATUS_LOG_GROWTH_FAILED\";\n        case STATUS_OBJECT_NO_LONGER_EXISTS:\n            return \"STATUS_OBJECT_NO_LONGER_EXISTS\";\n        case STATUS_STREAM_MINIVERSION_NOT_FOUND:\n            return \"STATUS_STREAM_MINIVERSION_NOT_FOUND\";\n        case STATUS_STREAM_MINIVERSION_NOT_VALID:\n            return \"STATUS_STREAM_MINIVERSION_NOT_VALID\";\n        case STATUS_MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION:\n            return \"STATUS_MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION\";\n        case STATUS_CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT:\n            return \"STATUS_CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT\";\n        case STATUS_CANT_CREATE_MORE_STREAM_MINIVERSIONS:\n            return \"STATUS_CANT_CREATE_MORE_STREAM_MINIVERSIONS\";\n        case STATUS_HANDLE_NO_LONGER_VALID:\n            return \"STATUS_HANDLE_NO_LONGER_VALID\";\n        case STATUS_LOG_CORRUPTION_DETECTED:\n            return \"STATUS_LOG_CORRUPTION_DETECTED\";\n        case STATUS_RM_DISCONNECTED:\n            return \"STATUS_RM_DISCONNECTED\";\n        case STATUS_ENLISTMENT_NOT_SUPERIOR:\n            return \"STATUS_ENLISTMENT_NOT_SUPERIOR\";\n        case STATUS_FILE_IDENTITY_NOT_PERSISTENT:\n            return \"STATUS_FILE_IDENTITY_NOT_PERSISTENT\";\n        case STATUS_CANT_BREAK_TRANSACTIONAL_DEPENDENCY:\n            return \"STATUS_CANT_BREAK_TRANSACTIONAL_DEPENDENCY\";\n        case STATUS_CANT_CROSS_RM_BOUNDARY:\n            return \"STATUS_CANT_CROSS_RM_BOUNDARY\";\n        case STATUS_TXF_DIR_NOT_EMPTY:\n            return \"STATUS_TXF_DIR_NOT_EMPTY\";\n        case STATUS_INDOUBT_TRANSACTIONS_EXIST:\n            return \"STATUS_INDOUBT_TRANSACTIONS_EXIST\";\n        case STATUS_TM_VOLATILE:\n            return \"STATUS_TM_VOLATILE\";\n        case STATUS_ROLLBACK_TIMER_EXPIRED:\n            return \"STATUS_ROLLBACK_TIMER_EXPIRED\";\n        case STATUS_TXF_ATTRIBUTE_CORRUPT:\n            return \"STATUS_TXF_ATTRIBUTE_CORRUPT\";\n        case STATUS_EFS_NOT_ALLOWED_IN_TRANSACTION:\n            return \"STATUS_EFS_NOT_ALLOWED_IN_TRANSACTION\";\n        case STATUS_TRANSACTIONAL_OPEN_NOT_ALLOWED:\n            return \"STATUS_TRANSACTIONAL_OPEN_NOT_ALLOWED\";\n        case STATUS_TRANSACTED_MAPPING_UNSUPPORTED_REMOTE:\n            return \"STATUS_TRANSACTED_MAPPING_UNSUPPORTED_REMOTE\";\n        case STATUS_TRANSACTION_REQUIRED_PROMOTION:\n            return \"STATUS_TRANSACTION_REQUIRED_PROMOTION\";\n        case STATUS_CANNOT_EXECUTE_FILE_IN_TRANSACTION:\n            return \"STATUS_CANNOT_EXECUTE_FILE_IN_TRANSACTION\";\n        case STATUS_TRANSACTIONS_NOT_FROZEN:\n            return \"STATUS_TRANSACTIONS_NOT_FROZEN\";\n        case STATUS_TRANSACTION_FREEZE_IN_PROGRESS:\n            return \"STATUS_TRANSACTION_FREEZE_IN_PROGRESS\";\n        case STATUS_NOT_SNAPSHOT_VOLUME:\n            return \"STATUS_NOT_SNAPSHOT_VOLUME\";\n        case STATUS_NO_SAVEPOINT_WITH_OPEN_FILES:\n            return \"STATUS_NO_SAVEPOINT_WITH_OPEN_FILES\";\n        case STATUS_SPARSE_NOT_ALLOWED_IN_TRANSACTION:\n            return \"STATUS_SPARSE_NOT_ALLOWED_IN_TRANSACTION\";\n        case STATUS_TM_IDENTITY_MISMATCH:\n            return \"STATUS_TM_IDENTITY_MISMATCH\";\n        case STATUS_FLOATED_SECTION:\n            return \"STATUS_FLOATED_SECTION\";\n        case STATUS_CANNOT_ACCEPT_TRANSACTED_WORK:\n            return \"STATUS_CANNOT_ACCEPT_TRANSACTED_WORK\";\n        case STATUS_CANNOT_ABORT_TRANSACTIONS:\n            return \"STATUS_CANNOT_ABORT_TRANSACTIONS\";\n        case STATUS_TRANSACTION_NOT_FOUND:\n            return \"STATUS_TRANSACTION_NOT_FOUND\";\n        case STATUS_RESOURCEMANAGER_NOT_FOUND:\n            return \"STATUS_RESOURCEMANAGER_NOT_FOUND\";\n        case STATUS_ENLISTMENT_NOT_FOUND:\n            return \"STATUS_ENLISTMENT_NOT_FOUND\";\n        case STATUS_TRANSACTIONMANAGER_NOT_FOUND:\n            return \"STATUS_TRANSACTIONMANAGER_NOT_FOUND\";\n        case STATUS_TRANSACTIONMANAGER_NOT_ONLINE:\n            return \"STATUS_TRANSACTIONMANAGER_NOT_ONLINE\";\n        case STATUS_TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION:\n            return \"STATUS_TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION\";\n        case STATUS_TRANSACTION_NOT_ROOT:\n            return \"STATUS_TRANSACTION_NOT_ROOT\";\n        case STATUS_TRANSACTION_OBJECT_EXPIRED:\n            return \"STATUS_TRANSACTION_OBJECT_EXPIRED\";\n        case STATUS_COMPRESSION_NOT_ALLOWED_IN_TRANSACTION:\n            return \"STATUS_COMPRESSION_NOT_ALLOWED_IN_TRANSACTION\";\n        case STATUS_TRANSACTION_RESPONSE_NOT_ENLISTED:\n            return \"STATUS_TRANSACTION_RESPONSE_NOT_ENLISTED\";\n        case STATUS_TRANSACTION_RECORD_TOO_LONG:\n            return \"STATUS_TRANSACTION_RECORD_TOO_LONG\";\n        case STATUS_NO_LINK_TRACKING_IN_TRANSACTION:\n            return \"STATUS_NO_LINK_TRACKING_IN_TRANSACTION\";\n        case STATUS_OPERATION_NOT_SUPPORTED_IN_TRANSACTION:\n            return \"STATUS_OPERATION_NOT_SUPPORTED_IN_TRANSACTION\";\n        case STATUS_TRANSACTION_INTEGRITY_VIOLATED:\n            return \"STATUS_TRANSACTION_INTEGRITY_VIOLATED\";\n        case STATUS_EXPIRED_HANDLE:\n            return \"STATUS_EXPIRED_HANDLE\";\n        case STATUS_TRANSACTION_NOT_ENLISTED:\n            return \"STATUS_TRANSACTION_NOT_ENLISTED\";\n        case STATUS_LOG_SECTOR_INVALID:\n            return \"STATUS_LOG_SECTOR_INVALID\";\n        case STATUS_LOG_SECTOR_PARITY_INVALID:\n            return \"STATUS_LOG_SECTOR_PARITY_INVALID\";\n        case STATUS_LOG_SECTOR_REMAPPED:\n            return \"STATUS_LOG_SECTOR_REMAPPED\";\n        case STATUS_LOG_BLOCK_INCOMPLETE:\n            return \"STATUS_LOG_BLOCK_INCOMPLETE\";\n        case STATUS_LOG_INVALID_RANGE:\n            return \"STATUS_LOG_INVALID_RANGE\";\n        case STATUS_LOG_BLOCKS_EXHAUSTED:\n            return \"STATUS_LOG_BLOCKS_EXHAUSTED\";\n        case STATUS_LOG_READ_CONTEXT_INVALID:\n            return \"STATUS_LOG_READ_CONTEXT_INVALID\";\n        case STATUS_LOG_RESTART_INVALID:\n            return \"STATUS_LOG_RESTART_INVALID\";\n        case STATUS_LOG_BLOCK_VERSION:\n            return \"STATUS_LOG_BLOCK_VERSION\";\n        case STATUS_LOG_BLOCK_INVALID:\n            return \"STATUS_LOG_BLOCK_INVALID\";\n        case STATUS_LOG_READ_MODE_INVALID:\n            return \"STATUS_LOG_READ_MODE_INVALID\";\n        case STATUS_LOG_METADATA_CORRUPT:\n            return \"STATUS_LOG_METADATA_CORRUPT\";\n        case STATUS_LOG_METADATA_INVALID:\n            return \"STATUS_LOG_METADATA_INVALID\";\n        case STATUS_LOG_METADATA_INCONSISTENT:\n            return \"STATUS_LOG_METADATA_INCONSISTENT\";\n        case STATUS_LOG_RESERVATION_INVALID:\n            return \"STATUS_LOG_RESERVATION_INVALID\";\n        case STATUS_LOG_CANT_DELETE:\n            return \"STATUS_LOG_CANT_DELETE\";\n        case STATUS_LOG_CONTAINER_LIMIT_EXCEEDED:\n            return \"STATUS_LOG_CONTAINER_LIMIT_EXCEEDED\";\n        case STATUS_LOG_START_OF_LOG:\n            return \"STATUS_LOG_START_OF_LOG\";\n        case STATUS_LOG_POLICY_ALREADY_INSTALLED:\n            return \"STATUS_LOG_POLICY_ALREADY_INSTALLED\";\n        case STATUS_LOG_POLICY_NOT_INSTALLED:\n            return \"STATUS_LOG_POLICY_NOT_INSTALLED\";\n        case STATUS_LOG_POLICY_INVALID:\n            return \"STATUS_LOG_POLICY_INVALID\";\n        case STATUS_LOG_POLICY_CONFLICT:\n            return \"STATUS_LOG_POLICY_CONFLICT\";\n        case STATUS_LOG_PINNED_ARCHIVE_TAIL:\n            return \"STATUS_LOG_PINNED_ARCHIVE_TAIL\";\n        case STATUS_LOG_RECORD_NONEXISTENT:\n            return \"STATUS_LOG_RECORD_NONEXISTENT\";\n        case STATUS_LOG_RECORDS_RESERVED_INVALID:\n            return \"STATUS_LOG_RECORDS_RESERVED_INVALID\";\n        case STATUS_LOG_SPACE_RESERVED_INVALID:\n            return \"STATUS_LOG_SPACE_RESERVED_INVALID\";\n        case STATUS_LOG_TAIL_INVALID:\n            return \"STATUS_LOG_TAIL_INVALID\";\n        case STATUS_LOG_FULL:\n            return \"STATUS_LOG_FULL\";\n        case STATUS_LOG_MULTIPLEXED:\n            return \"STATUS_LOG_MULTIPLEXED\";\n        case STATUS_LOG_DEDICATED:\n            return \"STATUS_LOG_DEDICATED\";\n        case STATUS_LOG_ARCHIVE_NOT_IN_PROGRESS:\n            return \"STATUS_LOG_ARCHIVE_NOT_IN_PROGRESS\";\n        case STATUS_LOG_ARCHIVE_IN_PROGRESS:\n            return \"STATUS_LOG_ARCHIVE_IN_PROGRESS\";\n        case STATUS_LOG_EPHEMERAL:\n            return \"STATUS_LOG_EPHEMERAL\";\n        case STATUS_LOG_NOT_ENOUGH_CONTAINERS:\n            return \"STATUS_LOG_NOT_ENOUGH_CONTAINERS\";\n        case STATUS_LOG_CLIENT_ALREADY_REGISTERED:\n            return \"STATUS_LOG_CLIENT_ALREADY_REGISTERED\";\n        case STATUS_LOG_CLIENT_NOT_REGISTERED:\n            return \"STATUS_LOG_CLIENT_NOT_REGISTERED\";\n        case STATUS_LOG_FULL_HANDLER_IN_PROGRESS:\n            return \"STATUS_LOG_FULL_HANDLER_IN_PROGRESS\";\n        case STATUS_LOG_CONTAINER_READ_FAILED:\n            return \"STATUS_LOG_CONTAINER_READ_FAILED\";\n        case STATUS_LOG_CONTAINER_WRITE_FAILED:\n            return \"STATUS_LOG_CONTAINER_WRITE_FAILED\";\n        case STATUS_LOG_CONTAINER_OPEN_FAILED:\n            return \"STATUS_LOG_CONTAINER_OPEN_FAILED\";\n        case STATUS_LOG_CONTAINER_STATE_INVALID:\n            return \"STATUS_LOG_CONTAINER_STATE_INVALID\";\n        case STATUS_LOG_STATE_INVALID:\n            return \"STATUS_LOG_STATE_INVALID\";\n        case STATUS_LOG_PINNED:\n            return \"STATUS_LOG_PINNED\";\n        case STATUS_LOG_METADATA_FLUSH_FAILED:\n            return \"STATUS_LOG_METADATA_FLUSH_FAILED\";\n        case STATUS_LOG_INCONSISTENT_SECURITY:\n            return \"STATUS_LOG_INCONSISTENT_SECURITY\";\n        case STATUS_LOG_APPENDED_FLUSH_FAILED:\n            return \"STATUS_LOG_APPENDED_FLUSH_FAILED\";\n        case STATUS_LOG_PINNED_RESERVATION:\n            return \"STATUS_LOG_PINNED_RESERVATION\";\n        case STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD:\n            return \"STATUS_VIDEO_HUNG_DISPLAY_DRIVER_THREAD\";\n        case STATUS_FLT_NO_HANDLER_DEFINED:\n            return \"STATUS_FLT_NO_HANDLER_DEFINED\";\n        case STATUS_FLT_CONTEXT_ALREADY_DEFINED:\n            return \"STATUS_FLT_CONTEXT_ALREADY_DEFINED\";\n        case STATUS_FLT_INVALID_ASYNCHRONOUS_REQUEST:\n            return \"STATUS_FLT_INVALID_ASYNCHRONOUS_REQUEST\";\n        case STATUS_FLT_DISALLOW_FAST_IO:\n            return \"STATUS_FLT_DISALLOW_FAST_IO\";\n        case STATUS_FLT_INVALID_NAME_REQUEST:\n            return \"STATUS_FLT_INVALID_NAME_REQUEST\";\n        case STATUS_FLT_NOT_SAFE_TO_POST_OPERATION:\n            return \"STATUS_FLT_NOT_SAFE_TO_POST_OPERATION\";\n        case STATUS_FLT_NOT_INITIALIZED:\n            return \"STATUS_FLT_NOT_INITIALIZED\";\n        case STATUS_FLT_FILTER_NOT_READY:\n            return \"STATUS_FLT_FILTER_NOT_READY\";\n        case STATUS_FLT_POST_OPERATION_CLEANUP:\n            return \"STATUS_FLT_POST_OPERATION_CLEANUP\";\n        case STATUS_FLT_INTERNAL_ERROR:\n            return \"STATUS_FLT_INTERNAL_ERROR\";\n        case STATUS_FLT_DELETING_OBJECT:\n            return \"STATUS_FLT_DELETING_OBJECT\";\n        case STATUS_FLT_MUST_BE_NONPAGED_POOL:\n            return \"STATUS_FLT_MUST_BE_NONPAGED_POOL\";\n        case STATUS_FLT_DUPLICATE_ENTRY:\n            return \"STATUS_FLT_DUPLICATE_ENTRY\";\n        case STATUS_FLT_CBDQ_DISABLED:\n            return \"STATUS_FLT_CBDQ_DISABLED\";\n        case STATUS_FLT_DO_NOT_ATTACH:\n            return \"STATUS_FLT_DO_NOT_ATTACH\";\n        case STATUS_FLT_DO_NOT_DETACH:\n            return \"STATUS_FLT_DO_NOT_DETACH\";\n        case STATUS_FLT_INSTANCE_ALTITUDE_COLLISION:\n            return \"STATUS_FLT_INSTANCE_ALTITUDE_COLLISION\";\n        case STATUS_FLT_INSTANCE_NAME_COLLISION:\n            return \"STATUS_FLT_INSTANCE_NAME_COLLISION\";\n        case STATUS_FLT_FILTER_NOT_FOUND:\n            return \"STATUS_FLT_FILTER_NOT_FOUND\";\n        case STATUS_FLT_VOLUME_NOT_FOUND:\n            return \"STATUS_FLT_VOLUME_NOT_FOUND\";\n        case STATUS_FLT_INSTANCE_NOT_FOUND:\n            return \"STATUS_FLT_INSTANCE_NOT_FOUND\";\n        case STATUS_FLT_CONTEXT_ALLOCATION_NOT_FOUND:\n            return \"STATUS_FLT_CONTEXT_ALLOCATION_NOT_FOUND\";\n        case STATUS_FLT_INVALID_CONTEXT_REGISTRATION:\n            return \"STATUS_FLT_INVALID_CONTEXT_REGISTRATION\";\n        case STATUS_FLT_NAME_CACHE_MISS:\n            return \"STATUS_FLT_NAME_CACHE_MISS\";\n        case STATUS_FLT_NO_DEVICE_OBJECT:\n            return \"STATUS_FLT_NO_DEVICE_OBJECT\";\n        case STATUS_FLT_VOLUME_ALREADY_MOUNTED:\n            return \"STATUS_FLT_VOLUME_ALREADY_MOUNTED\";\n        case STATUS_FLT_ALREADY_ENLISTED:\n            return \"STATUS_FLT_ALREADY_ENLISTED\";\n        case STATUS_FLT_CONTEXT_ALREADY_LINKED:\n            return \"STATUS_FLT_CONTEXT_ALREADY_LINKED\";\n        case STATUS_FLT_NO_WAITER_FOR_REPLY:\n            return \"STATUS_FLT_NO_WAITER_FOR_REPLY\";\n        case STATUS_MONITOR_NO_DESCRIPTOR:\n            return \"STATUS_MONITOR_NO_DESCRIPTOR\";\n        case STATUS_MONITOR_UNKNOWN_DESCRIPTOR_FORMAT:\n            return \"STATUS_MONITOR_UNKNOWN_DESCRIPTOR_FORMAT\";\n        case STATUS_MONITOR_INVALID_DESCRIPTOR_CHECKSUM:\n            return \"STATUS_MONITOR_INVALID_DESCRIPTOR_CHECKSUM\";\n        case STATUS_MONITOR_INVALID_STANDARD_TIMING_BLOCK:\n            return \"STATUS_MONITOR_INVALID_STANDARD_TIMING_BLOCK\";\n        case STATUS_MONITOR_WMI_DATABLOCK_REGISTRATION_FAILED:\n            return \"STATUS_MONITOR_WMI_DATABLOCK_REGISTRATION_FAILED\";\n        case STATUS_MONITOR_INVALID_SERIAL_NUMBER_MONDSC_BLOCK:\n            return \"STATUS_MONITOR_INVALID_SERIAL_NUMBER_MONDSC_BLOCK\";\n        case STATUS_MONITOR_INVALID_USER_FRIENDLY_MONDSC_BLOCK:\n            return \"STATUS_MONITOR_INVALID_USER_FRIENDLY_MONDSC_BLOCK\";\n        case STATUS_MONITOR_NO_MORE_DESCRIPTOR_DATA:\n            return \"STATUS_MONITOR_NO_MORE_DESCRIPTOR_DATA\";\n        case STATUS_MONITOR_INVALID_DETAILED_TIMING_BLOCK:\n            return \"STATUS_MONITOR_INVALID_DETAILED_TIMING_BLOCK\";\n        case STATUS_MONITOR_INVALID_MANUFACTURE_DATE:\n            return \"STATUS_MONITOR_INVALID_MANUFACTURE_DATE\";\n        case STATUS_GRAPHICS_NOT_EXCLUSIVE_MODE_OWNER:\n            return \"STATUS_GRAPHICS_NOT_EXCLUSIVE_MODE_OWNER\";\n        case STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER:\n            return \"STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER\";\n        case STATUS_GRAPHICS_INVALID_DISPLAY_ADAPTER:\n            return \"STATUS_GRAPHICS_INVALID_DISPLAY_ADAPTER\";\n        case STATUS_GRAPHICS_ADAPTER_WAS_RESET:\n            return \"STATUS_GRAPHICS_ADAPTER_WAS_RESET\";\n        case STATUS_GRAPHICS_INVALID_DRIVER_MODEL:\n            return \"STATUS_GRAPHICS_INVALID_DRIVER_MODEL\";\n        case STATUS_GRAPHICS_PRESENT_MODE_CHANGED:\n            return \"STATUS_GRAPHICS_PRESENT_MODE_CHANGED\";\n        case STATUS_GRAPHICS_PRESENT_OCCLUDED:\n            return \"STATUS_GRAPHICS_PRESENT_OCCLUDED\";\n        case STATUS_GRAPHICS_PRESENT_DENIED:\n            return \"STATUS_GRAPHICS_PRESENT_DENIED\";\n        case STATUS_GRAPHICS_CANNOTCOLORCONVERT:\n            return \"STATUS_GRAPHICS_CANNOTCOLORCONVERT\";\n        case STATUS_GRAPHICS_PRESENT_REDIRECTION_DISABLED:\n            return \"STATUS_GRAPHICS_PRESENT_REDIRECTION_DISABLED\";\n        case STATUS_GRAPHICS_PRESENT_UNOCCLUDED:\n            return \"STATUS_GRAPHICS_PRESENT_UNOCCLUDED\";\n        case STATUS_GRAPHICS_NO_VIDEO_MEMORY:\n            return \"STATUS_GRAPHICS_NO_VIDEO_MEMORY\";\n        case STATUS_GRAPHICS_CANT_LOCK_MEMORY:\n            return \"STATUS_GRAPHICS_CANT_LOCK_MEMORY\";\n        case STATUS_GRAPHICS_ALLOCATION_BUSY:\n            return \"STATUS_GRAPHICS_ALLOCATION_BUSY\";\n        case STATUS_GRAPHICS_TOO_MANY_REFERENCES:\n            return \"STATUS_GRAPHICS_TOO_MANY_REFERENCES\";\n        case STATUS_GRAPHICS_TRY_AGAIN_LATER:\n            return \"STATUS_GRAPHICS_TRY_AGAIN_LATER\";\n        case STATUS_GRAPHICS_TRY_AGAIN_NOW:\n            return \"STATUS_GRAPHICS_TRY_AGAIN_NOW\";\n        case STATUS_GRAPHICS_ALLOCATION_INVALID:\n            return \"STATUS_GRAPHICS_ALLOCATION_INVALID\";\n        case STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNAVAILABLE:\n            return \"STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNAVAILABLE\";\n        case STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNSUPPORTED:\n            return \"STATUS_GRAPHICS_UNSWIZZLING_APERTURE_UNSUPPORTED\";\n        case STATUS_GRAPHICS_CANT_EVICT_PINNED_ALLOCATION:\n            return \"STATUS_GRAPHICS_CANT_EVICT_PINNED_ALLOCATION\";\n        case STATUS_GRAPHICS_INVALID_ALLOCATION_USAGE:\n            return \"STATUS_GRAPHICS_INVALID_ALLOCATION_USAGE\";\n        case STATUS_GRAPHICS_CANT_RENDER_LOCKED_ALLOCATION:\n            return \"STATUS_GRAPHICS_CANT_RENDER_LOCKED_ALLOCATION\";\n        case STATUS_GRAPHICS_ALLOCATION_CLOSED:\n            return \"STATUS_GRAPHICS_ALLOCATION_CLOSED\";\n        case STATUS_GRAPHICS_INVALID_ALLOCATION_INSTANCE:\n            return \"STATUS_GRAPHICS_INVALID_ALLOCATION_INSTANCE\";\n        case STATUS_GRAPHICS_INVALID_ALLOCATION_HANDLE:\n            return \"STATUS_GRAPHICS_INVALID_ALLOCATION_HANDLE\";\n        case STATUS_GRAPHICS_WRONG_ALLOCATION_DEVICE:\n            return \"STATUS_GRAPHICS_WRONG_ALLOCATION_DEVICE\";\n        case STATUS_GRAPHICS_ALLOCATION_CONTENT_LOST:\n            return \"STATUS_GRAPHICS_ALLOCATION_CONTENT_LOST\";\n        case STATUS_GRAPHICS_GPU_EXCEPTION_ON_DEVICE:\n            return \"STATUS_GRAPHICS_GPU_EXCEPTION_ON_DEVICE\";\n        case STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY:\n            return \"STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY\";\n        case STATUS_GRAPHICS_VIDPN_TOPOLOGY_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_VIDPN_TOPOLOGY_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_VIDPN_TOPOLOGY_CURRENTLY_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_VIDPN_TOPOLOGY_CURRENTLY_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_INVALID_VIDPN:\n            return \"STATUS_GRAPHICS_INVALID_VIDPN\";\n        case STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE:\n            return \"STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE\";\n        case STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET:\n            return \"STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET\";\n        case STATUS_GRAPHICS_VIDPN_MODALITY_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_VIDPN_MODALITY_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_INVALID_VIDPN_SOURCEMODESET:\n            return \"STATUS_GRAPHICS_INVALID_VIDPN_SOURCEMODESET\";\n        case STATUS_GRAPHICS_INVALID_VIDPN_TARGETMODESET:\n            return \"STATUS_GRAPHICS_INVALID_VIDPN_TARGETMODESET\";\n        case STATUS_GRAPHICS_INVALID_FREQUENCY:\n            return \"STATUS_GRAPHICS_INVALID_FREQUENCY\";\n        case STATUS_GRAPHICS_INVALID_ACTIVE_REGION:\n            return \"STATUS_GRAPHICS_INVALID_ACTIVE_REGION\";\n        case STATUS_GRAPHICS_INVALID_TOTAL_REGION:\n            return \"STATUS_GRAPHICS_INVALID_TOTAL_REGION\";\n        case STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE_MODE:\n            return \"STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_SOURCE_MODE\";\n        case STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET_MODE:\n            return \"STATUS_GRAPHICS_INVALID_VIDEO_PRESENT_TARGET_MODE\";\n        case STATUS_GRAPHICS_PINNED_MODE_MUST_REMAIN_IN_SET:\n            return \"STATUS_GRAPHICS_PINNED_MODE_MUST_REMAIN_IN_SET\";\n        case STATUS_GRAPHICS_PATH_ALREADY_IN_TOPOLOGY:\n            return \"STATUS_GRAPHICS_PATH_ALREADY_IN_TOPOLOGY\";\n        case STATUS_GRAPHICS_MODE_ALREADY_IN_MODESET:\n            return \"STATUS_GRAPHICS_MODE_ALREADY_IN_MODESET\";\n        case STATUS_GRAPHICS_INVALID_VIDEOPRESENTSOURCESET:\n            return \"STATUS_GRAPHICS_INVALID_VIDEOPRESENTSOURCESET\";\n        case STATUS_GRAPHICS_INVALID_VIDEOPRESENTTARGETSET:\n            return \"STATUS_GRAPHICS_INVALID_VIDEOPRESENTTARGETSET\";\n        case STATUS_GRAPHICS_SOURCE_ALREADY_IN_SET:\n            return \"STATUS_GRAPHICS_SOURCE_ALREADY_IN_SET\";\n        case STATUS_GRAPHICS_TARGET_ALREADY_IN_SET:\n            return \"STATUS_GRAPHICS_TARGET_ALREADY_IN_SET\";\n        case STATUS_GRAPHICS_INVALID_VIDPN_PRESENT_PATH:\n            return \"STATUS_GRAPHICS_INVALID_VIDPN_PRESENT_PATH\";\n        case STATUS_GRAPHICS_NO_RECOMMENDED_VIDPN_TOPOLOGY:\n            return \"STATUS_GRAPHICS_NO_RECOMMENDED_VIDPN_TOPOLOGY\";\n        case STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGESET:\n            return \"STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGESET\";\n        case STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE:\n            return \"STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE\";\n        case STATUS_GRAPHICS_FREQUENCYRANGE_NOT_IN_SET:\n            return \"STATUS_GRAPHICS_FREQUENCYRANGE_NOT_IN_SET\";\n        case STATUS_GRAPHICS_FREQUENCYRANGE_ALREADY_IN_SET:\n            return \"STATUS_GRAPHICS_FREQUENCYRANGE_ALREADY_IN_SET\";\n        case STATUS_GRAPHICS_STALE_MODESET:\n            return \"STATUS_GRAPHICS_STALE_MODESET\";\n        case STATUS_GRAPHICS_INVALID_MONITOR_SOURCEMODESET:\n            return \"STATUS_GRAPHICS_INVALID_MONITOR_SOURCEMODESET\";\n        case STATUS_GRAPHICS_INVALID_MONITOR_SOURCE_MODE:\n            return \"STATUS_GRAPHICS_INVALID_MONITOR_SOURCE_MODE\";\n        case STATUS_GRAPHICS_NO_RECOMMENDED_FUNCTIONAL_VIDPN:\n            return \"STATUS_GRAPHICS_NO_RECOMMENDED_FUNCTIONAL_VIDPN\";\n        case STATUS_GRAPHICS_MODE_ID_MUST_BE_UNIQUE:\n            return \"STATUS_GRAPHICS_MODE_ID_MUST_BE_UNIQUE\";\n        case STATUS_GRAPHICS_EMPTY_ADAPTER_MONITOR_MODE_SUPPORT_INTERSECTION:\n            return \"STATUS_GRAPHICS_EMPTY_ADAPTER_MONITOR_MODE_SUPPORT_INTERSECTION\";\n        case STATUS_GRAPHICS_VIDEO_PRESENT_TARGETS_LESS_THAN_SOURCES:\n            return \"STATUS_GRAPHICS_VIDEO_PRESENT_TARGETS_LESS_THAN_SOURCES\";\n        case STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY:\n            return \"STATUS_GRAPHICS_PATH_NOT_IN_TOPOLOGY\";\n        case STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_SOURCE:\n            return \"STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_SOURCE\";\n        case STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_TARGET:\n            return \"STATUS_GRAPHICS_ADAPTER_MUST_HAVE_AT_LEAST_ONE_TARGET\";\n        case STATUS_GRAPHICS_INVALID_MONITORDESCRIPTORSET:\n            return \"STATUS_GRAPHICS_INVALID_MONITORDESCRIPTORSET\";\n        case STATUS_GRAPHICS_INVALID_MONITORDESCRIPTOR:\n            return \"STATUS_GRAPHICS_INVALID_MONITORDESCRIPTOR\";\n        case STATUS_GRAPHICS_MONITORDESCRIPTOR_NOT_IN_SET:\n            return \"STATUS_GRAPHICS_MONITORDESCRIPTOR_NOT_IN_SET\";\n        case STATUS_GRAPHICS_MONITORDESCRIPTOR_ALREADY_IN_SET:\n            return \"STATUS_GRAPHICS_MONITORDESCRIPTOR_ALREADY_IN_SET\";\n        case STATUS_GRAPHICS_MONITORDESCRIPTOR_ID_MUST_BE_UNIQUE:\n            return \"STATUS_GRAPHICS_MONITORDESCRIPTOR_ID_MUST_BE_UNIQUE\";\n        case STATUS_GRAPHICS_INVALID_VIDPN_TARGET_SUBSET_TYPE:\n            return \"STATUS_GRAPHICS_INVALID_VIDPN_TARGET_SUBSET_TYPE\";\n        case STATUS_GRAPHICS_RESOURCES_NOT_RELATED:\n            return \"STATUS_GRAPHICS_RESOURCES_NOT_RELATED\";\n        case STATUS_GRAPHICS_SOURCE_ID_MUST_BE_UNIQUE:\n            return \"STATUS_GRAPHICS_SOURCE_ID_MUST_BE_UNIQUE\";\n        case STATUS_GRAPHICS_TARGET_ID_MUST_BE_UNIQUE:\n            return \"STATUS_GRAPHICS_TARGET_ID_MUST_BE_UNIQUE\";\n        case STATUS_GRAPHICS_NO_AVAILABLE_VIDPN_TARGET:\n            return \"STATUS_GRAPHICS_NO_AVAILABLE_VIDPN_TARGET\";\n        case STATUS_GRAPHICS_MONITOR_COULD_NOT_BE_ASSOCIATED_WITH_ADAPTER:\n            return \"STATUS_GRAPHICS_MONITOR_COULD_NOT_BE_ASSOCIATED_WITH_ADAPTER\";\n        case STATUS_GRAPHICS_NO_VIDPNMGR:\n            return \"STATUS_GRAPHICS_NO_VIDPNMGR\";\n        case STATUS_GRAPHICS_NO_ACTIVE_VIDPN:\n            return \"STATUS_GRAPHICS_NO_ACTIVE_VIDPN\";\n        case STATUS_GRAPHICS_STALE_VIDPN_TOPOLOGY:\n            return \"STATUS_GRAPHICS_STALE_VIDPN_TOPOLOGY\";\n        case STATUS_GRAPHICS_MONITOR_NOT_CONNECTED:\n            return \"STATUS_GRAPHICS_MONITOR_NOT_CONNECTED\";\n        case STATUS_GRAPHICS_SOURCE_NOT_IN_TOPOLOGY:\n            return \"STATUS_GRAPHICS_SOURCE_NOT_IN_TOPOLOGY\";\n        case STATUS_GRAPHICS_INVALID_PRIMARYSURFACE_SIZE:\n            return \"STATUS_GRAPHICS_INVALID_PRIMARYSURFACE_SIZE\";\n        case STATUS_GRAPHICS_INVALID_VISIBLEREGION_SIZE:\n            return \"STATUS_GRAPHICS_INVALID_VISIBLEREGION_SIZE\";\n        case STATUS_GRAPHICS_INVALID_STRIDE:\n            return \"STATUS_GRAPHICS_INVALID_STRIDE\";\n        case STATUS_GRAPHICS_INVALID_PIXELFORMAT:\n            return \"STATUS_GRAPHICS_INVALID_PIXELFORMAT\";\n        case STATUS_GRAPHICS_INVALID_COLORBASIS:\n            return \"STATUS_GRAPHICS_INVALID_COLORBASIS\";\n        case STATUS_GRAPHICS_INVALID_PIXELVALUEACCESSMODE:\n            return \"STATUS_GRAPHICS_INVALID_PIXELVALUEACCESSMODE\";\n        case STATUS_GRAPHICS_TARGET_NOT_IN_TOPOLOGY:\n            return \"STATUS_GRAPHICS_TARGET_NOT_IN_TOPOLOGY\";\n        case STATUS_GRAPHICS_NO_DISPLAY_MODE_MANAGEMENT_SUPPORT:\n            return \"STATUS_GRAPHICS_NO_DISPLAY_MODE_MANAGEMENT_SUPPORT\";\n        case STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE:\n            return \"STATUS_GRAPHICS_VIDPN_SOURCE_IN_USE\";\n        case STATUS_GRAPHICS_CANT_ACCESS_ACTIVE_VIDPN:\n            return \"STATUS_GRAPHICS_CANT_ACCESS_ACTIVE_VIDPN\";\n        case STATUS_GRAPHICS_INVALID_PATH_IMPORTANCE_ORDINAL:\n            return \"STATUS_GRAPHICS_INVALID_PATH_IMPORTANCE_ORDINAL\";\n        case STATUS_GRAPHICS_INVALID_PATH_CONTENT_GEOMETRY_TRANSFORMATION:\n            return \"STATUS_GRAPHICS_INVALID_PATH_CONTENT_GEOMETRY_TRANSFORMATION\";\n        case STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_PATH_CONTENT_GEOMETRY_TRANSFORMATION_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_INVALID_GAMMA_RAMP:\n            return \"STATUS_GRAPHICS_INVALID_GAMMA_RAMP\";\n        case STATUS_GRAPHICS_GAMMA_RAMP_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_GAMMA_RAMP_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_MULTISAMPLING_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_MULTISAMPLING_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_MODE_NOT_IN_MODESET:\n            return \"STATUS_GRAPHICS_MODE_NOT_IN_MODESET\";\n        case STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY_RECOMMENDATION_REASON:\n            return \"STATUS_GRAPHICS_INVALID_VIDPN_TOPOLOGY_RECOMMENDATION_REASON\";\n        case STATUS_GRAPHICS_INVALID_PATH_CONTENT_TYPE:\n            return \"STATUS_GRAPHICS_INVALID_PATH_CONTENT_TYPE\";\n        case STATUS_GRAPHICS_INVALID_COPYPROTECTION_TYPE:\n            return \"STATUS_GRAPHICS_INVALID_COPYPROTECTION_TYPE\";\n        case STATUS_GRAPHICS_UNASSIGNED_MODESET_ALREADY_EXISTS:\n            return \"STATUS_GRAPHICS_UNASSIGNED_MODESET_ALREADY_EXISTS\";\n        case STATUS_GRAPHICS_INVALID_SCANLINE_ORDERING:\n            return \"STATUS_GRAPHICS_INVALID_SCANLINE_ORDERING\";\n        case STATUS_GRAPHICS_TOPOLOGY_CHANGES_NOT_ALLOWED:\n            return \"STATUS_GRAPHICS_TOPOLOGY_CHANGES_NOT_ALLOWED\";\n        case STATUS_GRAPHICS_NO_AVAILABLE_IMPORTANCE_ORDINALS:\n            return \"STATUS_GRAPHICS_NO_AVAILABLE_IMPORTANCE_ORDINALS\";\n        case STATUS_GRAPHICS_INCOMPATIBLE_PRIVATE_FORMAT:\n            return \"STATUS_GRAPHICS_INCOMPATIBLE_PRIVATE_FORMAT\";\n        case STATUS_GRAPHICS_INVALID_MODE_PRUNING_ALGORITHM:\n            return \"STATUS_GRAPHICS_INVALID_MODE_PRUNING_ALGORITHM\";\n        case STATUS_GRAPHICS_INVALID_MONITOR_CAPABILITY_ORIGIN:\n            return \"STATUS_GRAPHICS_INVALID_MONITOR_CAPABILITY_ORIGIN\";\n        case STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE_CONSTRAINT:\n            return \"STATUS_GRAPHICS_INVALID_MONITOR_FREQUENCYRANGE_CONSTRAINT\";\n        case STATUS_GRAPHICS_MAX_NUM_PATHS_REACHED:\n            return \"STATUS_GRAPHICS_MAX_NUM_PATHS_REACHED\";\n        case STATUS_GRAPHICS_CANCEL_VIDPN_TOPOLOGY_AUGMENTATION:\n            return \"STATUS_GRAPHICS_CANCEL_VIDPN_TOPOLOGY_AUGMENTATION\";\n        case STATUS_GRAPHICS_INVALID_CLIENT_TYPE:\n            return \"STATUS_GRAPHICS_INVALID_CLIENT_TYPE\";\n        case STATUS_GRAPHICS_CLIENTVIDPN_NOT_SET:\n            return \"STATUS_GRAPHICS_CLIENTVIDPN_NOT_SET\";\n        case STATUS_GRAPHICS_SPECIFIED_CHILD_ALREADY_CONNECTED:\n            return \"STATUS_GRAPHICS_SPECIFIED_CHILD_ALREADY_CONNECTED\";\n        case STATUS_GRAPHICS_CHILD_DESCRIPTOR_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_CHILD_DESCRIPTOR_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_NOT_A_LINKED_ADAPTER:\n            return \"STATUS_GRAPHICS_NOT_A_LINKED_ADAPTER\";\n        case STATUS_GRAPHICS_LEADLINK_NOT_ENUMERATED:\n            return \"STATUS_GRAPHICS_LEADLINK_NOT_ENUMERATED\";\n        case STATUS_GRAPHICS_CHAINLINKS_NOT_ENUMERATED:\n            return \"STATUS_GRAPHICS_CHAINLINKS_NOT_ENUMERATED\";\n        case STATUS_GRAPHICS_ADAPTER_CHAIN_NOT_READY:\n            return \"STATUS_GRAPHICS_ADAPTER_CHAIN_NOT_READY\";\n        case STATUS_GRAPHICS_CHAINLINKS_NOT_STARTED:\n            return \"STATUS_GRAPHICS_CHAINLINKS_NOT_STARTED\";\n        case STATUS_GRAPHICS_CHAINLINKS_NOT_POWERED_ON:\n            return \"STATUS_GRAPHICS_CHAINLINKS_NOT_POWERED_ON\";\n        case STATUS_GRAPHICS_INCONSISTENT_DEVICE_LINK_STATE:\n            return \"STATUS_GRAPHICS_INCONSISTENT_DEVICE_LINK_STATE\";\n        case STATUS_GRAPHICS_NOT_POST_DEVICE_DRIVER:\n            return \"STATUS_GRAPHICS_NOT_POST_DEVICE_DRIVER\";\n        case STATUS_GRAPHICS_ADAPTER_ACCESS_NOT_EXCLUDED:\n            return \"STATUS_GRAPHICS_ADAPTER_ACCESS_NOT_EXCLUDED\";\n        case STATUS_GRAPHICS_OPM_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_OPM_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_COPP_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_COPP_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_UAB_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_UAB_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_OPM_INVALID_ENCRYPTED_PARAMETERS:\n            return \"STATUS_GRAPHICS_OPM_INVALID_ENCRYPTED_PARAMETERS\";\n        case STATUS_GRAPHICS_OPM_NO_PROTECTED_OUTPUTS_EXIST:\n            return \"STATUS_GRAPHICS_OPM_NO_PROTECTED_OUTPUTS_EXIST\";\n        case STATUS_GRAPHICS_OPM_INTERNAL_ERROR:\n            return \"STATUS_GRAPHICS_OPM_INTERNAL_ERROR\";\n        case STATUS_GRAPHICS_OPM_INVALID_HANDLE:\n            return \"STATUS_GRAPHICS_OPM_INVALID_HANDLE\";\n        case STATUS_GRAPHICS_PVP_INVALID_CERTIFICATE_LENGTH:\n            return \"STATUS_GRAPHICS_PVP_INVALID_CERTIFICATE_LENGTH\";\n        case STATUS_GRAPHICS_OPM_SPANNING_MODE_ENABLED:\n            return \"STATUS_GRAPHICS_OPM_SPANNING_MODE_ENABLED\";\n        case STATUS_GRAPHICS_OPM_THEATER_MODE_ENABLED:\n            return \"STATUS_GRAPHICS_OPM_THEATER_MODE_ENABLED\";\n        case STATUS_GRAPHICS_PVP_HFS_FAILED:\n            return \"STATUS_GRAPHICS_PVP_HFS_FAILED\";\n        case STATUS_GRAPHICS_OPM_INVALID_SRM:\n            return \"STATUS_GRAPHICS_OPM_INVALID_SRM\";\n        case STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_HDCP:\n            return \"STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_HDCP\";\n        case STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_ACP:\n            return \"STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_ACP\";\n        case STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_CGMSA:\n            return \"STATUS_GRAPHICS_OPM_OUTPUT_DOES_NOT_SUPPORT_CGMSA\";\n        case STATUS_GRAPHICS_OPM_HDCP_SRM_NEVER_SET:\n            return \"STATUS_GRAPHICS_OPM_HDCP_SRM_NEVER_SET\";\n        case STATUS_GRAPHICS_OPM_RESOLUTION_TOO_HIGH:\n            return \"STATUS_GRAPHICS_OPM_RESOLUTION_TOO_HIGH\";\n        case STATUS_GRAPHICS_OPM_ALL_HDCP_HARDWARE_ALREADY_IN_USE:\n            return \"STATUS_GRAPHICS_OPM_ALL_HDCP_HARDWARE_ALREADY_IN_USE\";\n        case STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_NO_LONGER_EXISTS:\n            return \"STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_NO_LONGER_EXISTS\";\n        case STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_COPP_SEMANTICS:\n            return \"STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_COPP_SEMANTICS\";\n        case STATUS_GRAPHICS_OPM_INVALID_INFORMATION_REQUEST:\n            return \"STATUS_GRAPHICS_OPM_INVALID_INFORMATION_REQUEST\";\n        case STATUS_GRAPHICS_OPM_DRIVER_INTERNAL_ERROR:\n            return \"STATUS_GRAPHICS_OPM_DRIVER_INTERNAL_ERROR\";\n        case STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_OPM_SEMANTICS:\n            return \"STATUS_GRAPHICS_OPM_PROTECTED_OUTPUT_DOES_NOT_HAVE_OPM_SEMANTICS\";\n        case STATUS_GRAPHICS_OPM_SIGNALING_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_OPM_SIGNALING_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_OPM_INVALID_CONFIGURATION_REQUEST:\n            return \"STATUS_GRAPHICS_OPM_INVALID_CONFIGURATION_REQUEST\";\n        case STATUS_GRAPHICS_I2C_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_I2C_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_I2C_DEVICE_DOES_NOT_EXIST:\n            return \"STATUS_GRAPHICS_I2C_DEVICE_DOES_NOT_EXIST\";\n        case STATUS_GRAPHICS_I2C_ERROR_TRANSMITTING_DATA:\n            return \"STATUS_GRAPHICS_I2C_ERROR_TRANSMITTING_DATA\";\n        case STATUS_GRAPHICS_I2C_ERROR_RECEIVING_DATA:\n            return \"STATUS_GRAPHICS_I2C_ERROR_RECEIVING_DATA\";\n        case STATUS_GRAPHICS_DDCCI_VCP_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_DDCCI_VCP_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_DDCCI_INVALID_DATA:\n            return \"STATUS_GRAPHICS_DDCCI_INVALID_DATA\";\n        case STATUS_GRAPHICS_DDCCI_MONITOR_RETURNED_INVALID_TIMING_STATUS_BYTE:\n            return \"STATUS_GRAPHICS_DDCCI_MONITOR_RETURNED_INVALID_TIMING_STATUS_BYTE\";\n        case STATUS_GRAPHICS_DDCCI_INVALID_CAPABILITIES_STRING:\n            return \"STATUS_GRAPHICS_DDCCI_INVALID_CAPABILITIES_STRING\";\n        case STATUS_GRAPHICS_MCA_INTERNAL_ERROR:\n            return \"STATUS_GRAPHICS_MCA_INTERNAL_ERROR\";\n        case STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_COMMAND:\n            return \"STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_COMMAND\";\n        case STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_LENGTH:\n            return \"STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_LENGTH\";\n        case STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_CHECKSUM:\n            return \"STATUS_GRAPHICS_DDCCI_INVALID_MESSAGE_CHECKSUM\";\n        case STATUS_GRAPHICS_INVALID_PHYSICAL_MONITOR_HANDLE:\n            return \"STATUS_GRAPHICS_INVALID_PHYSICAL_MONITOR_HANDLE\";\n        case STATUS_GRAPHICS_MONITOR_NO_LONGER_EXISTS:\n            return \"STATUS_GRAPHICS_MONITOR_NO_LONGER_EXISTS\";\n        case STATUS_GRAPHICS_ONLY_CONSOLE_SESSION_SUPPORTED:\n            return \"STATUS_GRAPHICS_ONLY_CONSOLE_SESSION_SUPPORTED\";\n        case STATUS_GRAPHICS_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME:\n            return \"STATUS_GRAPHICS_NO_DISPLAY_DEVICE_CORRESPONDS_TO_NAME\";\n        case STATUS_GRAPHICS_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP:\n            return \"STATUS_GRAPHICS_DISPLAY_DEVICE_NOT_ATTACHED_TO_DESKTOP\";\n        case STATUS_GRAPHICS_MIRRORING_DEVICES_NOT_SUPPORTED:\n            return \"STATUS_GRAPHICS_MIRRORING_DEVICES_NOT_SUPPORTED\";\n        case STATUS_GRAPHICS_INVALID_POINTER:\n            return \"STATUS_GRAPHICS_INVALID_POINTER\";\n        case STATUS_GRAPHICS_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE:\n            return \"STATUS_GRAPHICS_NO_MONITORS_CORRESPOND_TO_DISPLAY_DEVICE\";\n        case STATUS_GRAPHICS_PARAMETER_ARRAY_TOO_SMALL:\n            return \"STATUS_GRAPHICS_PARAMETER_ARRAY_TOO_SMALL\";\n        case STATUS_GRAPHICS_INTERNAL_ERROR:\n            return \"STATUS_GRAPHICS_INTERNAL_ERROR\";\n        case STATUS_GRAPHICS_SESSION_TYPE_CHANGE_IN_PROGRESS:\n            return \"STATUS_GRAPHICS_SESSION_TYPE_CHANGE_IN_PROGRESS\";\n        case STATUS_FVE_LOCKED_VOLUME:\n            return \"STATUS_FVE_LOCKED_VOLUME\";\n        case STATUS_FVE_NOT_ENCRYPTED:\n            return \"STATUS_FVE_NOT_ENCRYPTED\";\n        case STATUS_FVE_BAD_INFORMATION:\n            return \"STATUS_FVE_BAD_INFORMATION\";\n        case STATUS_FVE_TOO_SMALL:\n            return \"STATUS_FVE_TOO_SMALL\";\n        case STATUS_FVE_FAILED_WRONG_FS:\n            return \"STATUS_FVE_FAILED_WRONG_FS\";\n        case STATUS_FVE_FS_NOT_EXTENDED:\n            return \"STATUS_FVE_FS_NOT_EXTENDED\";\n        case STATUS_FVE_FS_MOUNTED:\n            return \"STATUS_FVE_FS_MOUNTED\";\n        case STATUS_FVE_NO_LICENSE:\n            return \"STATUS_FVE_NO_LICENSE\";\n        case STATUS_FVE_ACTION_NOT_ALLOWED:\n            return \"STATUS_FVE_ACTION_NOT_ALLOWED\";\n        case STATUS_FVE_BAD_DATA:\n            return \"STATUS_FVE_BAD_DATA\";\n        case STATUS_FVE_VOLUME_NOT_BOUND:\n            return \"STATUS_FVE_VOLUME_NOT_BOUND\";\n        case STATUS_FVE_NOT_DATA_VOLUME:\n            return \"STATUS_FVE_NOT_DATA_VOLUME\";\n        case STATUS_FVE_CONV_READ_ERROR:\n            return \"STATUS_FVE_CONV_READ_ERROR\";\n        case STATUS_FVE_CONV_WRITE_ERROR:\n            return \"STATUS_FVE_CONV_WRITE_ERROR\";\n        case STATUS_FVE_OVERLAPPED_UPDATE:\n            return \"STATUS_FVE_OVERLAPPED_UPDATE\";\n        case STATUS_FVE_FAILED_SECTOR_SIZE:\n            return \"STATUS_FVE_FAILED_SECTOR_SIZE\";\n        case STATUS_FVE_FAILED_AUTHENTICATION:\n            return \"STATUS_FVE_FAILED_AUTHENTICATION\";\n        case STATUS_FVE_NOT_OS_VOLUME:\n            return \"STATUS_FVE_NOT_OS_VOLUME\";\n        case STATUS_FVE_KEYFILE_NOT_FOUND:\n            return \"STATUS_FVE_KEYFILE_NOT_FOUND\";\n        case STATUS_FVE_KEYFILE_INVALID:\n            return \"STATUS_FVE_KEYFILE_INVALID\";\n        case STATUS_FVE_KEYFILE_NO_VMK:\n            return \"STATUS_FVE_KEYFILE_NO_VMK\";\n        case STATUS_FVE_TPM_DISABLED:\n            return \"STATUS_FVE_TPM_DISABLED\";\n        case STATUS_FVE_TPM_SRK_AUTH_NOT_ZERO:\n            return \"STATUS_FVE_TPM_SRK_AUTH_NOT_ZERO\";\n        case STATUS_FVE_TPM_INVALID_PCR:\n            return \"STATUS_FVE_TPM_INVALID_PCR\";\n        case STATUS_FVE_TPM_NO_VMK:\n            return \"STATUS_FVE_TPM_NO_VMK\";\n        case STATUS_FVE_PIN_INVALID:\n            return \"STATUS_FVE_PIN_INVALID\";\n        case STATUS_FVE_AUTH_INVALID_APPLICATION:\n            return \"STATUS_FVE_AUTH_INVALID_APPLICATION\";\n        case STATUS_FVE_AUTH_INVALID_CONFIG:\n            return \"STATUS_FVE_AUTH_INVALID_CONFIG\";\n        case STATUS_FVE_DEBUGGER_ENABLED:\n            return \"STATUS_FVE_DEBUGGER_ENABLED\";\n        case STATUS_FVE_DRY_RUN_FAILED:\n            return \"STATUS_FVE_DRY_RUN_FAILED\";\n        case STATUS_FVE_BAD_METADATA_POINTER:\n            return \"STATUS_FVE_BAD_METADATA_POINTER\";\n        case STATUS_FVE_OLD_METADATA_COPY:\n            return \"STATUS_FVE_OLD_METADATA_COPY\";\n        case STATUS_FVE_REBOOT_REQUIRED:\n            return \"STATUS_FVE_REBOOT_REQUIRED\";\n        case STATUS_FVE_RAW_ACCESS:\n            return \"STATUS_FVE_RAW_ACCESS\";\n        case STATUS_FVE_RAW_BLOCKED:\n            return \"STATUS_FVE_RAW_BLOCKED\";\n        case STATUS_FVE_NO_FEATURE_LICENSE:\n            return \"STATUS_FVE_NO_FEATURE_LICENSE\";\n        case STATUS_FVE_POLICY_USER_DISABLE_RDV_NOT_ALLOWED:\n            return \"STATUS_FVE_POLICY_USER_DISABLE_RDV_NOT_ALLOWED\";\n        case STATUS_FVE_CONV_RECOVERY_FAILED:\n            return \"STATUS_FVE_CONV_RECOVERY_FAILED\";\n        case STATUS_FVE_VIRTUALIZED_SPACE_TOO_BIG:\n            return \"STATUS_FVE_VIRTUALIZED_SPACE_TOO_BIG\";\n        case STATUS_FVE_VOLUME_TOO_SMALL:\n            return \"STATUS_FVE_VOLUME_TOO_SMALL\";\n        case STATUS_FWP_CALLOUT_NOT_FOUND:\n            return \"STATUS_FWP_CALLOUT_NOT_FOUND\";\n        case STATUS_FWP_CONDITION_NOT_FOUND:\n            return \"STATUS_FWP_CONDITION_NOT_FOUND\";\n        case STATUS_FWP_FILTER_NOT_FOUND:\n            return \"STATUS_FWP_FILTER_NOT_FOUND\";\n        case STATUS_FWP_LAYER_NOT_FOUND:\n            return \"STATUS_FWP_LAYER_NOT_FOUND\";\n        case STATUS_FWP_PROVIDER_NOT_FOUND:\n            return \"STATUS_FWP_PROVIDER_NOT_FOUND\";\n        case STATUS_FWP_PROVIDER_CONTEXT_NOT_FOUND:\n            return \"STATUS_FWP_PROVIDER_CONTEXT_NOT_FOUND\";\n        case STATUS_FWP_SUBLAYER_NOT_FOUND:\n            return \"STATUS_FWP_SUBLAYER_NOT_FOUND\";\n        case STATUS_FWP_NOT_FOUND:\n            return \"STATUS_FWP_NOT_FOUND\";\n        case STATUS_FWP_ALREADY_EXISTS:\n            return \"STATUS_FWP_ALREADY_EXISTS\";\n        case STATUS_FWP_IN_USE:\n            return \"STATUS_FWP_IN_USE\";\n        case STATUS_FWP_DYNAMIC_SESSION_IN_PROGRESS:\n            return \"STATUS_FWP_DYNAMIC_SESSION_IN_PROGRESS\";\n        case STATUS_FWP_WRONG_SESSION:\n            return \"STATUS_FWP_WRONG_SESSION\";\n        case STATUS_FWP_NO_TXN_IN_PROGRESS:\n            return \"STATUS_FWP_NO_TXN_IN_PROGRESS\";\n        case STATUS_FWP_TXN_IN_PROGRESS:\n            return \"STATUS_FWP_TXN_IN_PROGRESS\";\n        case STATUS_FWP_TXN_ABORTED:\n            return \"STATUS_FWP_TXN_ABORTED\";\n        case STATUS_FWP_SESSION_ABORTED:\n            return \"STATUS_FWP_SESSION_ABORTED\";\n        case STATUS_FWP_INCOMPATIBLE_TXN:\n            return \"STATUS_FWP_INCOMPATIBLE_TXN\";\n        case STATUS_FWP_TIMEOUT:\n            return \"STATUS_FWP_TIMEOUT\";\n        case STATUS_FWP_NET_EVENTS_DISABLED:\n            return \"STATUS_FWP_NET_EVENTS_DISABLED\";\n        case STATUS_FWP_INCOMPATIBLE_LAYER:\n            return \"STATUS_FWP_INCOMPATIBLE_LAYER\";\n        case STATUS_FWP_KM_CLIENTS_ONLY:\n            return \"STATUS_FWP_KM_CLIENTS_ONLY\";\n        case STATUS_FWP_LIFETIME_MISMATCH:\n            return \"STATUS_FWP_LIFETIME_MISMATCH\";\n        case STATUS_FWP_BUILTIN_OBJECT:\n            return \"STATUS_FWP_BUILTIN_OBJECT\";\n        case STATUS_FWP_NOTIFICATION_DROPPED:\n            return \"STATUS_FWP_NOTIFICATION_DROPPED\";\n        case STATUS_FWP_TRAFFIC_MISMATCH:\n            return \"STATUS_FWP_TRAFFIC_MISMATCH\";\n        case STATUS_FWP_INCOMPATIBLE_SA_STATE:\n            return \"STATUS_FWP_INCOMPATIBLE_SA_STATE\";\n        case STATUS_FWP_NULL_POINTER:\n            return \"STATUS_FWP_NULL_POINTER\";\n        case STATUS_FWP_INVALID_ENUMERATOR:\n            return \"STATUS_FWP_INVALID_ENUMERATOR\";\n        case STATUS_FWP_INVALID_FLAGS:\n            return \"STATUS_FWP_INVALID_FLAGS\";\n        case STATUS_FWP_INVALID_NET_MASK:\n            return \"STATUS_FWP_INVALID_NET_MASK\";\n        case STATUS_FWP_INVALID_RANGE:\n            return \"STATUS_FWP_INVALID_RANGE\";\n        case STATUS_FWP_INVALID_INTERVAL:\n            return \"STATUS_FWP_INVALID_INTERVAL\";\n        case STATUS_FWP_ZERO_LENGTH_ARRAY:\n            return \"STATUS_FWP_ZERO_LENGTH_ARRAY\";\n        case STATUS_FWP_NULL_DISPLAY_NAME:\n            return \"STATUS_FWP_NULL_DISPLAY_NAME\";\n        case STATUS_FWP_INVALID_ACTION_TYPE:\n            return \"STATUS_FWP_INVALID_ACTION_TYPE\";\n        case STATUS_FWP_INVALID_WEIGHT:\n            return \"STATUS_FWP_INVALID_WEIGHT\";\n        case STATUS_FWP_MATCH_TYPE_MISMATCH:\n            return \"STATUS_FWP_MATCH_TYPE_MISMATCH\";\n        case STATUS_FWP_TYPE_MISMATCH:\n            return \"STATUS_FWP_TYPE_MISMATCH\";\n        case STATUS_FWP_OUT_OF_BOUNDS:\n            return \"STATUS_FWP_OUT_OF_BOUNDS\";\n        case STATUS_FWP_RESERVED:\n            return \"STATUS_FWP_RESERVED\";\n        case STATUS_FWP_DUPLICATE_CONDITION:\n            return \"STATUS_FWP_DUPLICATE_CONDITION\";\n        case STATUS_FWP_DUPLICATE_KEYMOD:\n            return \"STATUS_FWP_DUPLICATE_KEYMOD\";\n        case STATUS_FWP_ACTION_INCOMPATIBLE_WITH_LAYER:\n            return \"STATUS_FWP_ACTION_INCOMPATIBLE_WITH_LAYER\";\n        case STATUS_FWP_ACTION_INCOMPATIBLE_WITH_SUBLAYER:\n            return \"STATUS_FWP_ACTION_INCOMPATIBLE_WITH_SUBLAYER\";\n        case STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_LAYER:\n            return \"STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_LAYER\";\n        case STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_CALLOUT:\n            return \"STATUS_FWP_CONTEXT_INCOMPATIBLE_WITH_CALLOUT\";\n        case STATUS_FWP_INCOMPATIBLE_AUTH_METHOD:\n            return \"STATUS_FWP_INCOMPATIBLE_AUTH_METHOD\";\n        case STATUS_FWP_INCOMPATIBLE_DH_GROUP:\n            return \"STATUS_FWP_INCOMPATIBLE_DH_GROUP\";\n        case STATUS_FWP_EM_NOT_SUPPORTED:\n            return \"STATUS_FWP_EM_NOT_SUPPORTED\";\n        case STATUS_FWP_NEVER_MATCH:\n            return \"STATUS_FWP_NEVER_MATCH\";\n        case STATUS_FWP_PROVIDER_CONTEXT_MISMATCH:\n            return \"STATUS_FWP_PROVIDER_CONTEXT_MISMATCH\";\n        case STATUS_FWP_INVALID_PARAMETER:\n            return \"STATUS_FWP_INVALID_PARAMETER\";\n        case STATUS_FWP_TOO_MANY_SUBLAYERS:\n            return \"STATUS_FWP_TOO_MANY_SUBLAYERS\";\n        case STATUS_FWP_CALLOUT_NOTIFICATION_FAILED:\n            return \"STATUS_FWP_CALLOUT_NOTIFICATION_FAILED\";\n        case STATUS_FWP_DUPLICATE_AUTH_METHOD:\n            return \"STATUS_FWP_DUPLICATE_AUTH_METHOD\";\n        case STATUS_FWP_TCPIP_NOT_READY:\n            return \"STATUS_FWP_TCPIP_NOT_READY\";\n        case STATUS_FWP_INJECT_HANDLE_CLOSING:\n            return \"STATUS_FWP_INJECT_HANDLE_CLOSING\";\n        case STATUS_FWP_INJECT_HANDLE_STALE:\n            return \"STATUS_FWP_INJECT_HANDLE_STALE\";\n        case STATUS_FWP_CANNOT_PEND:\n            return \"STATUS_FWP_CANNOT_PEND\";\n        case STATUS_NDIS_CLOSING:\n            return \"STATUS_NDIS_CLOSING\";\n        case STATUS_NDIS_BAD_VERSION:\n            return \"STATUS_NDIS_BAD_VERSION\";\n        case STATUS_NDIS_BAD_CHARACTERISTICS:\n            return \"STATUS_NDIS_BAD_CHARACTERISTICS\";\n        case STATUS_NDIS_ADAPTER_NOT_FOUND:\n            return \"STATUS_NDIS_ADAPTER_NOT_FOUND\";\n        case STATUS_NDIS_OPEN_FAILED:\n            return \"STATUS_NDIS_OPEN_FAILED\";\n        case STATUS_NDIS_DEVICE_FAILED:\n            return \"STATUS_NDIS_DEVICE_FAILED\";\n        case STATUS_NDIS_MULTICAST_FULL:\n            return \"STATUS_NDIS_MULTICAST_FULL\";\n        case STATUS_NDIS_MULTICAST_EXISTS:\n            return \"STATUS_NDIS_MULTICAST_EXISTS\";\n        case STATUS_NDIS_MULTICAST_NOT_FOUND:\n            return \"STATUS_NDIS_MULTICAST_NOT_FOUND\";\n        case STATUS_NDIS_REQUEST_ABORTED:\n            return \"STATUS_NDIS_REQUEST_ABORTED\";\n        case STATUS_NDIS_RESET_IN_PROGRESS:\n            return \"STATUS_NDIS_RESET_IN_PROGRESS\";\n        case STATUS_NDIS_INVALID_PACKET:\n            return \"STATUS_NDIS_INVALID_PACKET\";\n        case STATUS_NDIS_INVALID_DEVICE_REQUEST:\n            return \"STATUS_NDIS_INVALID_DEVICE_REQUEST\";\n        case STATUS_NDIS_ADAPTER_NOT_READY:\n            return \"STATUS_NDIS_ADAPTER_NOT_READY\";\n        case STATUS_NDIS_INVALID_LENGTH:\n            return \"STATUS_NDIS_INVALID_LENGTH\";\n        case STATUS_NDIS_INVALID_DATA:\n            return \"STATUS_NDIS_INVALID_DATA\";\n        case STATUS_NDIS_BUFFER_TOO_SHORT:\n            return \"STATUS_NDIS_BUFFER_TOO_SHORT\";\n        case STATUS_NDIS_INVALID_OID:\n            return \"STATUS_NDIS_INVALID_OID\";\n        case STATUS_NDIS_ADAPTER_REMOVED:\n            return \"STATUS_NDIS_ADAPTER_REMOVED\";\n        case STATUS_NDIS_UNSUPPORTED_MEDIA:\n            return \"STATUS_NDIS_UNSUPPORTED_MEDIA\";\n        case STATUS_NDIS_GROUP_ADDRESS_IN_USE:\n            return \"STATUS_NDIS_GROUP_ADDRESS_IN_USE\";\n        case STATUS_NDIS_FILE_NOT_FOUND:\n            return \"STATUS_NDIS_FILE_NOT_FOUND\";\n        case STATUS_NDIS_ERROR_READING_FILE:\n            return \"STATUS_NDIS_ERROR_READING_FILE\";\n        case STATUS_NDIS_ALREADY_MAPPED:\n            return \"STATUS_NDIS_ALREADY_MAPPED\";\n        case STATUS_NDIS_RESOURCE_CONFLICT:\n            return \"STATUS_NDIS_RESOURCE_CONFLICT\";\n        case STATUS_NDIS_MEDIA_DISCONNECTED:\n            return \"STATUS_NDIS_MEDIA_DISCONNECTED\";\n        case STATUS_NDIS_INVALID_ADDRESS:\n            return \"STATUS_NDIS_INVALID_ADDRESS\";\n        case STATUS_NDIS_PAUSED:\n            return \"STATUS_NDIS_PAUSED\";\n        case STATUS_NDIS_INTERFACE_NOT_FOUND:\n            return \"STATUS_NDIS_INTERFACE_NOT_FOUND\";\n        case STATUS_NDIS_UNSUPPORTED_REVISION:\n            return \"STATUS_NDIS_UNSUPPORTED_REVISION\";\n        case STATUS_NDIS_INVALID_PORT:\n            return \"STATUS_NDIS_INVALID_PORT\";\n        case STATUS_NDIS_INVALID_PORT_STATE:\n            return \"STATUS_NDIS_INVALID_PORT_STATE\";\n        case STATUS_NDIS_LOW_POWER_STATE:\n            return \"STATUS_NDIS_LOW_POWER_STATE\";\n        case STATUS_NDIS_NOT_SUPPORTED:\n            return \"STATUS_NDIS_NOT_SUPPORTED\";\n        case STATUS_NDIS_OFFLOAD_POLICY:\n            return \"STATUS_NDIS_OFFLOAD_POLICY\";\n        case STATUS_NDIS_OFFLOAD_CONNECTION_REJECTED:\n            return \"STATUS_NDIS_OFFLOAD_CONNECTION_REJECTED\";\n        case STATUS_NDIS_OFFLOAD_PATH_REJECTED:\n            return \"STATUS_NDIS_OFFLOAD_PATH_REJECTED\";\n        case STATUS_NDIS_DOT11_AUTO_CONFIG_ENABLED:\n            return \"STATUS_NDIS_DOT11_AUTO_CONFIG_ENABLED\";\n        case STATUS_NDIS_DOT11_MEDIA_IN_USE:\n            return \"STATUS_NDIS_DOT11_MEDIA_IN_USE\";\n        case STATUS_NDIS_DOT11_POWER_STATE_INVALID:\n            return \"STATUS_NDIS_DOT11_POWER_STATE_INVALID\";\n        case STATUS_NDIS_PM_WOL_PATTERN_LIST_FULL:\n            return \"STATUS_NDIS_PM_WOL_PATTERN_LIST_FULL\";\n        case STATUS_NDIS_PM_PROTOCOL_OFFLOAD_LIST_FULL:\n            return \"STATUS_NDIS_PM_PROTOCOL_OFFLOAD_LIST_FULL\";\n        case STATUS_IPSEC_BAD_SPI:\n            return \"STATUS_IPSEC_BAD_SPI\";\n        case STATUS_IPSEC_SA_LIFETIME_EXPIRED:\n            return \"STATUS_IPSEC_SA_LIFETIME_EXPIRED\";\n        case STATUS_IPSEC_WRONG_SA:\n            return \"STATUS_IPSEC_WRONG_SA\";\n        case STATUS_IPSEC_REPLAY_CHECK_FAILED:\n            return \"STATUS_IPSEC_REPLAY_CHECK_FAILED\";\n        case STATUS_IPSEC_INVALID_PACKET:\n            return \"STATUS_IPSEC_INVALID_PACKET\";\n        case STATUS_IPSEC_INTEGRITY_CHECK_FAILED:\n            return \"STATUS_IPSEC_INTEGRITY_CHECK_FAILED\";\n        case STATUS_IPSEC_CLEAR_TEXT_DROP:\n            return \"STATUS_IPSEC_CLEAR_TEXT_DROP\";\n        case STATUS_IPSEC_AUTH_FIREWALL_DROP:\n            return \"STATUS_IPSEC_AUTH_FIREWALL_DROP\";\n        case STATUS_IPSEC_THROTTLE_DROP:\n            return \"STATUS_IPSEC_THROTTLE_DROP\";\n        case STATUS_IPSEC_DOSP_BLOCK:\n            return \"STATUS_IPSEC_DOSP_BLOCK\";\n        case STATUS_IPSEC_DOSP_RECEIVED_MULTICAST:\n            return \"STATUS_IPSEC_DOSP_RECEIVED_MULTICAST\";\n        case STATUS_IPSEC_DOSP_INVALID_PACKET:\n            return \"STATUS_IPSEC_DOSP_INVALID_PACKET\";\n        case STATUS_IPSEC_DOSP_STATE_LOOKUP_FAILED:\n            return \"STATUS_IPSEC_DOSP_STATE_LOOKUP_FAILED\";\n        case STATUS_IPSEC_DOSP_MAX_ENTRIES:\n            return \"STATUS_IPSEC_DOSP_MAX_ENTRIES\";\n        case STATUS_IPSEC_DOSP_KEYMOD_NOT_ALLOWED:\n            return \"STATUS_IPSEC_DOSP_KEYMOD_NOT_ALLOWED\";\n        case STATUS_IPSEC_DOSP_MAX_PER_IP_RATELIMIT_QUEUES:\n            return \"STATUS_IPSEC_DOSP_MAX_PER_IP_RATELIMIT_QUEUES\";\n        case STATUS_VOLMGR_MIRROR_NOT_SUPPORTED:\n            return \"STATUS_VOLMGR_MIRROR_NOT_SUPPORTED\";\n        case STATUS_VOLMGR_RAID5_NOT_SUPPORTED:\n            return \"STATUS_VOLMGR_RAID5_NOT_SUPPORTED\";\n        case STATUS_VIRTDISK_PROVIDER_NOT_FOUND:\n            return \"STATUS_VIRTDISK_PROVIDER_NOT_FOUND\";\n        case STATUS_VIRTDISK_NOT_VIRTUAL_DISK:\n            return \"STATUS_VIRTDISK_NOT_VIRTUAL_DISK\";\n        case STATUS_VHD_PARENT_VHD_ACCESS_DENIED:\n            return \"STATUS_VHD_PARENT_VHD_ACCESS_DENIED\";\n        case STATUS_VHD_CHILD_PARENT_SIZE_MISMATCH:\n            return \"STATUS_VHD_CHILD_PARENT_SIZE_MISMATCH\";\n        case STATUS_VHD_DIFFERENCING_CHAIN_CYCLE_DETECTED:\n            return \"STATUS_VHD_DIFFERENCING_CHAIN_CYCLE_DETECTED\";\n        case STATUS_VHD_DIFFERENCING_CHAIN_ERROR_IN_PARENT:\n            return \"STATUS_VHD_DIFFERENCING_CHAIN_ERROR_IN_PARENT\";\n        case STATUS_CASE_DIFFERING_NAMES_IN_DIR:\n            return \"STATUS_CASE_DIFFERING_NAMES_IN_DIR\";\n        default:\n            return std::format(\"Status {:08x}\", (uint32_t)s);\n    }\n}\n\nclass ntstatus_error : public std::exception {\npublic:\n    ntstatus_error(NTSTATUS Status) : Status(Status) {\n        msg = ntstatus_to_string(Status);\n    }\n\n    const char* what() const noexcept override {\n        return msg.c_str();\n    }\n\n    NTSTATUS Status;\n    std::string msg;\n};\n\nclass formatted_error : public std::exception {\npublic:\n    template<typename... Args>\n    formatted_error(std::string_view s, Args&&... args) : msg(std::vformat(s, std::make_format_args(args...))) {\n    }\n\n    const char* what() const noexcept {\n        return msg.c_str();\n    }\n\nprivate:\n    std::string msg;\n};\n\ntemplate<typename T>\nT query_information(HANDLE h);\n\ntemplate<typename T>\nclass varbuf {\npublic:\n    T* operator*() {\n        return (T*)buf.data();\n    }\n\n    operator T*() {\n        return (T*)buf.data();\n    }\n\n    operator const T*() const {\n        return (const T*)buf.data();\n    }\n\n    std::vector<uint8_t> buf;\n};\n\n// test.cpp\nunique_handle create_file(std::u16string_view path, ACCESS_MASK access, ULONG atts, ULONG share,\n                          ULONG dispo, ULONG options, ULONG_PTR exp_info, std::optional<uint64_t> allocation = std::nullopt);\n\ntemplate<typename T>\nstd::vector<varbuf<T>> query_dir(const std::u16string& dir, std::u16string_view filter);\nvarbuf<FILE_ALL_INFORMATION> query_all_information(HANDLE h);\nvoid test(const std::string& msg, const std::function<void()>& func);\nvoid exp_status(const std::function<void()>& func, NTSTATUS Status);\nstd::u16string query_file_name_information(HANDLE h, bool normalized = false);\nvoid disable_token_privileges(HANDLE token);\nstd::string u16string_to_string(std::u16string_view sv);\n\nextern enum fs_type fstype;\n\n// create.cpp\nvoid test_create(HANDLE token, const std::u16string& dir);\nvoid test_open_id(HANDLE token, const std::u16string& dir);\n\n// supersede.cpp\nvoid test_supersede(const std::u16string& dir);\n\n// overwrite.cpp\nvoid test_overwrite(const std::u16string& dir);\n\n// io.cpp\nvoid test_io(HANDLE token, const std::u16string& dir);\nstd::vector<uint8_t> random_data(size_t len);\nvoid write_file(HANDLE h, std::span<const uint8_t> data, std::optional<uint64_t> offset = std::nullopt);\nvoid set_end_of_file(HANDLE h, uint64_t eof);\nstd::vector<uint8_t> read_file(HANDLE h, ULONG len, std::optional<uint64_t> offset = std::nullopt);\nvoid write_file_wait(HANDLE h, std::span<const uint8_t> data, std::optional<uint64_t> offset = std::nullopt);\nstd::vector<uint8_t> read_file_wait(HANDLE h, ULONG len, std::optional<uint64_t> offset = std::nullopt);\nvoid set_allocation(HANDLE h, uint64_t alloc);\nvoid set_valid_data_length(HANDLE h, uint64_t vdl);\nvoid set_zero_data(HANDLE h, uint64_t start, uint64_t end);\nunique_handle create_event();\nvoid adjust_token_privileges(HANDLE token, const LUID_AND_ATTRIBUTES& priv);\n\n// mmap.cpp\nvoid test_mmap(const std::u16string& dir);\nunique_handle create_section(ACCESS_MASK access, std::optional<uint64_t> max_size, ULONG prot,\n                             ULONG atts, HANDLE file);\nstd::vector<uint8_t> pe_image(std::span<const std::byte> data);\n\n// rename.cpp\nvoid test_rename(const std::u16string& dir);\nvoid test_rename_ex(HANDLE token, const std::u16string& dir);\nvoid set_rename_information(HANDLE h, bool replace_if_exists, HANDLE root_dir, std::u16string_view filename);\n\n// delete.cpp\nvoid test_delete(const std::u16string& dir);\nvoid test_delete_ex(HANDLE token, const std::u16string& dir);\nvoid set_disposition_information(HANDLE h, bool delete_file);\nvoid set_disposition_information_ex(HANDLE h, uint32_t flags);\n\n// links.cpp\nvoid test_links(HANDLE token, const std::u16string& dir);\nvoid test_links_ex(HANDLE token, const std::u16string& dir);\nstd::vector<std::pair<int64_t, std::u16string>> query_links(HANDLE h);\nvoid set_link_information(HANDLE h, bool replace_if_exists, HANDLE root_dir, std::u16string_view filename);\n\n// oplock.cpp\nvoid test_oplocks_i(HANDLE token, const std::u16string& dir);\nvoid test_oplocks_ii(HANDLE token, const std::u16string& dir);\nvoid test_oplocks_batch(HANDLE token, const std::u16string& dir);\nvoid test_oplocks_filter(HANDLE token, const std::u16string& dir);\nvoid test_oplocks_r(HANDLE token, const std::u16string& dir);\nvoid test_oplocks_rw(HANDLE token, const std::u16string& dir);\nvoid test_oplocks_rh(HANDLE token, const std::u16string& dir);\nvoid test_oplocks_rwh(HANDLE token, const std::u16string& dir);\n\n// cs.cpp\nvoid test_cs(const std::u16string& dir);\n\n// reparse.cpp\nvoid test_reparse(HANDLE token, const std::u16string& dir);\n\n// streams.cpp\nvoid test_streams(const std::u16string& dir);\n\n// ea.cpp\nvoid test_ea(const std::u16string& dir);\nvoid write_ea(HANDLE h, std::string_view name, std::string_view value, bool need_ea = false);\n\n// fileinfo.cpp\nvoid test_fileinfo(const std::u16string& dir);\nvoid set_basic_information(HANDLE h, int64_t creation_time, int64_t last_access_time,\n                           int64_t last_write_time, int64_t change_time, uint32_t attributes);\n\n// security.cpp\nvoid test_security(HANDLE token, const std::u16string& dir);\nvoid set_dacl(HANDLE h, ACCESS_MASK access);\n"
  },
  {
    "path": "src/tests/test.rc.in",
    "content": "#define APSTUDIO_READONLY_SYMBOLS\n#include <winresrc.h>\n#undef APSTUDIO_READONLY_SYMBOLS\n\n/////////////////////////////////////////////////////////////////////////////\n// English (United Kingdom) resources\n\n#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)\nLANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK\n#pragma code_page(1252)\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// Version\n//\n\nVS_VERSION_INFO VERSIONINFO\n FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0\n PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0\n FILEFLAGSMASK 0x17L\n#ifdef _DEBUG\n FILEFLAGS 0x1L\n#else\n FILEFLAGS 0x0L\n#endif\n FILEOS 0x4L\n FILETYPE 0x1L\n FILESUBTYPE 0x0L\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"080904b0\"\n        BEGIN\n            VALUE \"FileDescription\", \"WinBtrfs test program\"\n            VALUE \"FileVersion\", \"@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@\"\n            VALUE \"InternalName\", \"test\"\n            VALUE \"LegalCopyright\", \"Copyright (c) Mark Harmstone 2021-24\"\n            VALUE \"OriginalFilename\", \"test.exe\"\n            VALUE \"ProductName\", \"WinBtrfs\"\n            VALUE \"ProductVersion\", \"@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x809, 1200\n    END\nEND\n\n1 RT_MANIFEST \"@CMAKE_CURRENT_SOURCE_DIR@/src/tests/manifest.xml\"\n\n#endif    // English (United Kingdom) resources\n/////////////////////////////////////////////////////////////////////////////\n"
  },
  {
    "path": "src/treefuncs.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include \"crc32c.h\"\n\n__attribute__((nonnull(1,3,4,5)))\nNTSTATUS load_tree(device_extension* Vcb, uint64_t addr, uint8_t* buf, root* r, tree** pt) {\n    tree_header* th;\n    tree* t;\n    tree_data* td;\n    uint8_t h;\n    bool inserted;\n    LIST_ENTRY* le;\n\n    th = (tree_header*)buf;\n\n    t = ExAllocatePoolWithTag(PagedPool, sizeof(tree), ALLOC_TAG);\n    if (!t) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    if (th->level > 0) {\n        t->nonpaged = ExAllocatePoolWithTag(NonPagedPool, sizeof(tree_nonpaged), ALLOC_TAG);\n        if (!t->nonpaged) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(t);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        ExInitializeFastMutex(&t->nonpaged->mutex);\n    } else\n        t->nonpaged = NULL;\n\n    RtlCopyMemory(&t->header, th, sizeof(tree_header));\n    t->hash = calc_crc32c(0xffffffff, (uint8_t*)&addr, sizeof(uint64_t));\n    t->has_address = true;\n    t->Vcb = Vcb;\n    t->parent = NULL;\n    t->root = r;\n    t->paritem = NULL;\n    t->size = 0;\n    t->new_address = 0;\n    t->has_new_address = false;\n    t->updated_extents = false;\n    t->write = false;\n    t->uniqueness_determined = false;\n\n    InitializeListHead(&t->itemlist);\n\n    if (t->header.level == 0) { // leaf node\n        leaf_node* ln = (leaf_node*)(buf + sizeof(tree_header));\n        unsigned int i;\n\n        if ((t->header.num_items * sizeof(leaf_node)) + sizeof(tree_header) > Vcb->superblock.node_size) {\n            ERR(\"tree at %I64x has more items than expected (%x)\\n\", addr, t->header.num_items);\n            ExFreePool(t);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        for (i = 0; i < t->header.num_items; i++) {\n            td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);\n            if (!td) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(t);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            td->key = ln[i].key;\n\n            if (ln[i].size > 0)\n                td->data = buf + sizeof(tree_header) + ln[i].offset;\n            else\n                td->data = NULL;\n\n            if (ln[i].size + sizeof(tree_header) + sizeof(leaf_node) > Vcb->superblock.node_size) {\n                ERR(\"overlarge item in tree %I64x: %u > %Iu\\n\", addr, ln[i].size, Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node));\n                ExFreeToPagedLookasideList(&t->Vcb->tree_data_lookaside, td);\n                ExFreePool(t);\n                return STATUS_INTERNAL_ERROR;\n            }\n\n            td->size = (uint16_t)ln[i].size;\n            td->ignore = false;\n            td->inserted = false;\n\n            InsertTailList(&t->itemlist, &td->list_entry);\n\n            t->size += ln[i].size;\n        }\n\n        t->size += t->header.num_items * sizeof(leaf_node);\n        t->buf = buf;\n    } else {\n        internal_node* in = (internal_node*)(buf + sizeof(tree_header));\n        unsigned int i;\n\n        if ((t->header.num_items * sizeof(internal_node)) + sizeof(tree_header) > Vcb->superblock.node_size) {\n            ERR(\"tree at %I64x has more items than expected (%x)\\n\", addr, t->header.num_items);\n            ExFreePool(t);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        for (i = 0; i < t->header.num_items; i++) {\n            td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);\n            if (!td) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(t);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            td->key = in[i].key;\n\n            td->treeholder.address = in[i].address;\n            td->treeholder.generation = in[i].generation;\n            td->treeholder.tree = NULL;\n            td->ignore = false;\n            td->inserted = false;\n\n            InsertTailList(&t->itemlist, &td->list_entry);\n        }\n\n        t->size = t->header.num_items * sizeof(internal_node);\n        t->buf = NULL;\n    }\n\n    ExAcquireFastMutex(&Vcb->trees_list_mutex);\n\n    InsertTailList(&Vcb->trees, &t->list_entry);\n\n    h = t->hash >> 24;\n\n    if (!Vcb->trees_ptrs[h]) {\n        uint8_t h2 = h;\n\n        le = Vcb->trees_hash.Flink;\n\n        if (h2 > 0) {\n            h2--;\n            do {\n                if (Vcb->trees_ptrs[h2]) {\n                    le = Vcb->trees_ptrs[h2];\n                    break;\n                }\n\n                h2--;\n            } while (h2 > 0);\n        }\n    } else\n        le = Vcb->trees_ptrs[h];\n\n    inserted = false;\n    while (le != &Vcb->trees_hash) {\n        tree* t2 = CONTAINING_RECORD(le, tree, list_entry_hash);\n\n        if (t2->hash >= t->hash) {\n            InsertHeadList(le->Blink, &t->list_entry_hash);\n            inserted = true;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!inserted)\n        InsertTailList(&Vcb->trees_hash, &t->list_entry_hash);\n\n    if (!Vcb->trees_ptrs[h] || t->list_entry_hash.Flink == Vcb->trees_ptrs[h])\n        Vcb->trees_ptrs[h] = &t->list_entry_hash;\n\n    ExReleaseFastMutex(&Vcb->trees_list_mutex);\n\n    TRACE(\"returning %p\\n\", t);\n\n    *pt = t;\n\n    return STATUS_SUCCESS;\n}\n\n__attribute__((nonnull(1,2,3,4)))\nstatic NTSTATUS do_load_tree2(device_extension* Vcb, tree_holder* th, uint8_t* buf, root* r, tree* t, tree_data* td) {\n    if (!th->tree) {\n        NTSTATUS Status;\n        tree* nt;\n\n        Status = load_tree(Vcb, th->address, buf, r, &nt);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"load_tree returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        nt->parent = t;\n\n#ifdef DEBUG_PARANOID\n        if (t && t->header.level <= nt->header.level) int3;\n#endif\n\n        nt->paritem = td;\n\n        th->tree = nt;\n    }\n\n    return STATUS_SUCCESS;\n}\n\n__attribute__((nonnull(1,2,3)))\nNTSTATUS do_load_tree(device_extension* Vcb, tree_holder* th, root* r, tree* t, tree_data* td, PIRP Irp) {\n    NTSTATUS Status;\n    uint8_t* buf;\n    chunk* c;\n\n    buf = ExAllocatePoolWithTag(PagedPool, Vcb->superblock.node_size, ALLOC_TAG);\n    if (!buf) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    Status = read_data(Vcb, th->address, Vcb->superblock.node_size, NULL, true, buf, NULL,\n                       &c, Irp, th->generation, false, NormalPagePriority);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"read_data returned 0x%08lx\\n\", Status);\n        ExFreePool(buf);\n        return Status;\n    }\n\n    if (t)\n        ExAcquireFastMutex(&t->nonpaged->mutex);\n    else\n        ExAcquireResourceExclusiveLite(&r->nonpaged->load_tree_lock, true);\n\n    Status = do_load_tree2(Vcb, th, buf, r, t, td);\n\n    if (t)\n        ExReleaseFastMutex(&t->nonpaged->mutex);\n    else\n        ExReleaseResourceLite(&r->nonpaged->load_tree_lock);\n\n    if (!th->tree || th->tree->buf != buf)\n        ExFreePool(buf);\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"do_load_tree2 returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    return Status;\n}\n\n__attribute__((nonnull(1)))\nvoid free_tree(tree* t) {\n    tree* par;\n    root* r = t->root;\n\n    // No need to acquire lock, as this is only ever called while Vcb->tree_lock held exclusively\n\n    par = t->parent;\n\n    if (r && r->treeholder.tree != t)\n        r = NULL;\n\n    if (par) {\n        if (t->paritem)\n            t->paritem->treeholder.tree = NULL;\n    }\n\n    while (!IsListEmpty(&t->itemlist)) {\n        tree_data* td = CONTAINING_RECORD(RemoveHeadList(&t->itemlist), tree_data, list_entry);\n\n        if (t->header.level == 0 && td->data && td->inserted)\n            ExFreePool(td->data);\n\n        ExFreeToPagedLookasideList(&t->Vcb->tree_data_lookaside, td);\n    }\n\n    RemoveEntryList(&t->list_entry);\n\n    if (r)\n        r->treeholder.tree = NULL;\n\n    if (t->list_entry_hash.Flink) {\n        uint8_t h = t->hash >> 24;\n        if (t->Vcb->trees_ptrs[h] == &t->list_entry_hash) {\n            if (t->list_entry_hash.Flink != &t->Vcb->trees_hash) {\n                tree* t2 = CONTAINING_RECORD(t->list_entry_hash.Flink, tree, list_entry_hash);\n\n                if ((t2->hash >> 24) == h)\n                    t->Vcb->trees_ptrs[h] = &t2->list_entry_hash;\n                else\n                    t->Vcb->trees_ptrs[h] = NULL;\n            } else\n                t->Vcb->trees_ptrs[h] = NULL;\n        }\n\n        RemoveEntryList(&t->list_entry_hash);\n    }\n\n    if (t->buf)\n        ExFreePool(t->buf);\n\n    if (t->nonpaged)\n        ExFreePool(t->nonpaged);\n\n    ExFreePool(t);\n}\n\n__attribute__((nonnull(1)))\nstatic __inline tree_data* first_item(tree* t) {\n    LIST_ENTRY* le = t->itemlist.Flink;\n\n    if (le == &t->itemlist)\n        return NULL;\n\n    return CONTAINING_RECORD(le, tree_data, list_entry);\n}\n\n__attribute__((nonnull(1,2)))\nstatic __inline tree_data* prev_item(tree* t, tree_data* td) {\n    LIST_ENTRY* le = td->list_entry.Blink;\n\n    if (le == &t->itemlist)\n        return NULL;\n\n    return CONTAINING_RECORD(le, tree_data, list_entry);\n}\n\n__attribute__((nonnull(1,2)))\nstatic __inline tree_data* next_item(tree* t, tree_data* td) {\n    LIST_ENTRY* le = td->list_entry.Flink;\n\n    if (le == &t->itemlist)\n        return NULL;\n\n    return CONTAINING_RECORD(le, tree_data, list_entry);\n}\n\n__attribute__((nonnull(1,2,3,4)))\nstatic NTSTATUS next_item2(device_extension* Vcb, tree* t, tree_data* td, traverse_ptr* tp) {\n    tree_data* td2 = next_item(t, td);\n    tree* t2;\n\n    if (td2) {\n        tp->tree = t;\n        tp->item = td2;\n        return STATUS_SUCCESS;\n    }\n\n    t2 = t;\n\n    do {\n        td2 = t2->paritem;\n        t2 = t2->parent;\n    } while (td2 && !next_item(t2, td2));\n\n    if (!td2)\n        return STATUS_NOT_FOUND;\n\n    td2 = next_item(t2, td2);\n\n    return find_item_to_level(Vcb, t2->root, tp, &td2->key, false, t->header.level, NULL);\n}\n\n__attribute__((nonnull(1,2,3,4,5)))\nNTSTATUS skip_to_difference(device_extension* Vcb, traverse_ptr* tp, traverse_ptr* tp2, bool* ended1, bool* ended2) {\n    NTSTATUS Status;\n    tree *t1, *t2;\n    tree_data *td1, *td2;\n\n    t1 = tp->tree;\n    t2 = tp2->tree;\n\n    do {\n        td1 = t1->paritem;\n        td2 = t2->paritem;\n        t1 = t1->parent;\n        t2 = t2->parent;\n    } while (t1 && t2 && t1->header.address == t2->header.address);\n\n    while (true) {\n        traverse_ptr tp3, tp4;\n\n        Status = next_item2(Vcb, t1, td1, &tp3);\n        if (Status == STATUS_NOT_FOUND)\n            *ended1 = true;\n        else if (!NT_SUCCESS(Status)) {\n            ERR(\"next_item2 returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        Status = next_item2(Vcb, t2, td2, &tp4);\n        if (Status == STATUS_NOT_FOUND)\n            *ended2 = true;\n        else if (!NT_SUCCESS(Status)) {\n            ERR(\"next_item2 returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (*ended1 || *ended2) {\n            if (!*ended1) {\n                Status = find_item(Vcb, t1->root, tp, &tp3.item->key, false, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"find_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n            } else if (!*ended2) {\n                Status = find_item(Vcb, t2->root, tp2, &tp4.item->key, false, NULL);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"find_item returned %08lx\\n\", Status);\n                    return Status;\n                }\n            }\n\n            return STATUS_SUCCESS;\n        }\n\n        if (tp3.tree->header.address != tp4.tree->header.address) {\n            Status = find_item(Vcb, t1->root, tp, &tp3.item->key, false, NULL);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"find_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            Status = find_item(Vcb, t2->root, tp2, &tp4.item->key, false, NULL);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"find_item returned %08lx\\n\", Status);\n                return Status;\n            }\n\n            return STATUS_SUCCESS;\n        }\n\n        t1 = tp3.tree;\n        td1 = tp3.item;\n        t2 = tp4.tree;\n        td2 = tp4.item;\n    }\n}\n\n__attribute__((nonnull(1,2,3,4)))\nstatic NTSTATUS find_item_in_tree(device_extension* Vcb, tree* t, traverse_ptr* tp, const KEY* searchkey, bool ignore, uint8_t level, PIRP Irp) {\n    int cmp;\n    tree_data *td, *lasttd;\n    KEY key2;\n\n    cmp = 1;\n    td = first_item(t);\n    lasttd = NULL;\n\n    if (!td) return STATUS_NOT_FOUND;\n\n    key2 = *searchkey;\n\n    do {\n        cmp = keycmp(key2, td->key);\n\n        if (cmp == 1) {\n            lasttd = td;\n            td = next_item(t, td);\n        }\n\n        if (t->header.level == 0 && cmp == 0 && !ignore && td && td->ignore) {\n            tree_data* origtd = td;\n\n            while (td && td->ignore)\n                td = next_item(t, td);\n\n            if (td) {\n                cmp = keycmp(key2, td->key);\n\n                if (cmp != 0) {\n                    td = origtd;\n                    cmp = 0;\n                }\n            } else\n                td = origtd;\n        }\n    } while (td && cmp == 1);\n\n    if ((cmp == -1 || !td) && lasttd)\n        td = lasttd;\n\n    if (t->header.level == 0) {\n        if (td->ignore && !ignore) {\n            traverse_ptr oldtp;\n\n            oldtp.tree = t;\n            oldtp.item = td;\n\n            while (find_prev_item(Vcb, &oldtp, tp, Irp)) {\n                if (!tp->item->ignore)\n                    return STATUS_SUCCESS;\n\n                oldtp = *tp;\n            }\n\n            // if no valid entries before where item should be, look afterwards instead\n\n            oldtp.tree = t;\n            oldtp.item = td;\n\n            while (find_next_item(Vcb, &oldtp, tp, true, Irp)) {\n                if (!tp->item->ignore)\n                    return STATUS_SUCCESS;\n\n                oldtp = *tp;\n            }\n\n            return STATUS_NOT_FOUND;\n        } else {\n            tp->tree = t;\n            tp->item = td;\n        }\n\n        return STATUS_SUCCESS;\n    } else {\n        NTSTATUS Status;\n\n        while (td && td->treeholder.tree && IsListEmpty(&td->treeholder.tree->itemlist)) {\n            td = prev_item(t, td);\n        }\n\n        if (!td)\n            return STATUS_NOT_FOUND;\n\n        if (t->header.level <= level) {\n            tp->tree = t;\n            tp->item = td;\n            return STATUS_SUCCESS;\n        }\n\n        if (!td->treeholder.tree) {\n            Status = do_load_tree(Vcb, &td->treeholder, t->root, t, td, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"do_load_tree returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        Status = find_item_in_tree(Vcb, td->treeholder.tree, tp, searchkey, ignore, level, Irp);\n\n        return Status;\n    }\n}\n\n__attribute__((nonnull(1,2,3,4)))\nNTSTATUS find_item(_In_ _Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _Out_ traverse_ptr* tp,\n                   _In_ const KEY* searchkey, _In_ bool ignore, _In_opt_ PIRP Irp) {\n    NTSTATUS Status;\n\n    if (!r->treeholder.tree) {\n        Status = do_load_tree(Vcb, &r->treeholder, r, NULL, NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"do_load_tree returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    Status = find_item_in_tree(Vcb, r->treeholder.tree, tp, searchkey, ignore, 0, Irp);\n    if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) {\n        ERR(\"find_item_in_tree returned %08lx\\n\", Status);\n    }\n\n    return Status;\n}\n\n__attribute__((nonnull(1,2,3,4)))\nNTSTATUS find_item_to_level(device_extension* Vcb, root* r, traverse_ptr* tp, const KEY* searchkey, bool ignore, uint8_t level, PIRP Irp) {\n    NTSTATUS Status;\n\n    if (!r->treeholder.tree) {\n        Status = do_load_tree(Vcb, &r->treeholder, r, NULL, NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"do_load_tree returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n    Status = find_item_in_tree(Vcb, r->treeholder.tree, tp, searchkey, ignore, level, Irp);\n    if (!NT_SUCCESS(Status) && Status != STATUS_NOT_FOUND) {\n        ERR(\"find_item_in_tree returned %08lx\\n\", Status);\n    }\n\n    if (Status == STATUS_NOT_FOUND) {\n        tp->tree = r->treeholder.tree;\n        tp->item = NULL;\n    }\n\n    return Status;\n}\n\n__attribute__((nonnull(1,2,3)))\nbool find_next_item(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, const traverse_ptr* tp, traverse_ptr* next_tp, bool ignore, PIRP Irp) {\n    tree* t;\n    tree_data *td = NULL, *next;\n    NTSTATUS Status;\n\n    next = next_item(tp->tree, tp->item);\n\n    if (!ignore) {\n        while (next && next->ignore)\n            next = next_item(tp->tree, next);\n    }\n\n    if (next) {\n        next_tp->tree = tp->tree;\n        next_tp->item = next;\n\n#ifdef DEBUG_PARANOID\n        if (!ignore && next_tp->item->ignore) {\n            ERR(\"error - returning ignored item\\n\");\n            int3;\n        }\n#endif\n\n        return true;\n    }\n\n    if (!tp->tree->parent)\n        return false;\n\n    t = tp->tree;\n    do {\n        if (t->parent) {\n            td = next_item(t->parent, t->paritem);\n\n            if (td) break;\n        }\n\n        t = t->parent;\n    } while (t);\n\n    if (!t)\n        return false;\n\n    if (!td->treeholder.tree) {\n        Status = do_load_tree(Vcb, &td->treeholder, t->parent->root, t->parent, td, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"do_load_tree returned %08lx\\n\", Status);\n            return false;\n        }\n    }\n\n    t = td->treeholder.tree;\n\n    while (t->header.level != 0) {\n        tree_data* fi;\n\n        fi = first_item(t);\n\n        if (!fi)\n            return false;\n\n        if (!fi->treeholder.tree) {\n            Status = do_load_tree(Vcb, &fi->treeholder, t->parent->root, t, fi, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"do_load_tree returned %08lx\\n\", Status);\n                return false;\n            }\n        }\n\n        t = fi->treeholder.tree;\n    }\n\n    next_tp->tree = t;\n    next_tp->item = first_item(t);\n\n    if (!next_tp->item)\n        return false;\n\n    if (!ignore && next_tp->item->ignore) {\n        traverse_ptr ntp2;\n        bool b;\n\n        while ((b = find_next_item(Vcb, next_tp, &ntp2, true, Irp))) {\n            *next_tp = ntp2;\n\n            if (!next_tp->item->ignore)\n                break;\n        }\n\n        if (!b)\n            return false;\n    }\n\n#ifdef DEBUG_PARANOID\n    if (!ignore && next_tp->item->ignore) {\n        ERR(\"error - returning ignored item\\n\");\n        int3;\n    }\n#endif\n\n    return true;\n}\n\n__attribute__((nonnull(1)))\nstatic __inline tree_data* last_item(tree* t) {\n    LIST_ENTRY* le = t->itemlist.Blink;\n\n    if (le == &t->itemlist)\n        return NULL;\n\n    return CONTAINING_RECORD(le, tree_data, list_entry);\n}\n\n__attribute__((nonnull(1,2,3)))\nbool find_prev_item(_Requires_lock_held_(_Curr_->tree_lock) device_extension* Vcb, const traverse_ptr* tp, traverse_ptr* prev_tp, PIRP Irp) {\n    tree* t;\n    tree_data* td;\n    NTSTATUS Status;\n\n    // FIXME - support ignore flag\n    if (prev_item(tp->tree, tp->item)) {\n        prev_tp->tree = tp->tree;\n        prev_tp->item = prev_item(tp->tree, tp->item);\n\n        return true;\n    }\n\n    if (!tp->tree->parent)\n        return false;\n\n    t = tp->tree;\n    while (t && (!t->parent || !prev_item(t->parent, t->paritem))) {\n        t = t->parent;\n    }\n\n    if (!t)\n        return false;\n\n    td = prev_item(t->parent, t->paritem);\n\n    if (!td->treeholder.tree) {\n        Status = do_load_tree(Vcb, &td->treeholder, t->parent->root, t->parent, td, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"do_load_tree returned %08lx\\n\", Status);\n            return false;\n        }\n    }\n\n    t = td->treeholder.tree;\n\n    while (t->header.level != 0) {\n        tree_data* li;\n\n        li = last_item(t);\n\n        if (!li->treeholder.tree) {\n            Status = do_load_tree(Vcb, &li->treeholder, t->parent->root, t, li, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"do_load_tree returned %08lx\\n\", Status);\n                return false;\n            }\n        }\n\n        t = li->treeholder.tree;\n    }\n\n    prev_tp->tree = t;\n    prev_tp->item = last_item(t);\n\n    return true;\n}\n\n__attribute__((nonnull(1,2)))\nvoid free_trees_root(device_extension* Vcb, root* r) {\n    LIST_ENTRY* le;\n    ULONG level;\n\n    for (level = 0; level <= 255; level++) {\n        bool empty = true;\n\n        le = Vcb->trees.Flink;\n\n        while (le != &Vcb->trees) {\n            LIST_ENTRY* nextle = le->Flink;\n            tree* t = CONTAINING_RECORD(le, tree, list_entry);\n\n            if (t->root == r) {\n                if (t->header.level == level) {\n                    bool top = !t->paritem;\n\n                    empty = false;\n\n                    free_tree(t);\n                    if (top && r->treeholder.tree == t)\n                        r->treeholder.tree = NULL;\n\n                    if (IsListEmpty(&Vcb->trees))\n                        return;\n                } else if (t->header.level > level)\n                    empty = false;\n            }\n\n            le = nextle;\n        }\n\n        if (empty)\n            break;\n    }\n}\n\n__attribute__((nonnull(1)))\nvoid free_trees(device_extension* Vcb) {\n    LIST_ENTRY* le;\n    ULONG level;\n\n    for (level = 0; level <= 255; level++) {\n        bool empty = true;\n\n        le = Vcb->trees.Flink;\n\n        while (le != &Vcb->trees) {\n            LIST_ENTRY* nextle = le->Flink;\n            tree* t = CONTAINING_RECORD(le, tree, list_entry);\n            root* r = t->root;\n\n            if (t->header.level == level) {\n                bool top = !t->paritem;\n\n                empty = false;\n\n                free_tree(t);\n                if (top && r->treeholder.tree == t)\n                    r->treeholder.tree = NULL;\n\n                if (IsListEmpty(&Vcb->trees))\n                    break;\n            } else if (t->header.level > level)\n                empty = false;\n\n            le = nextle;\n        }\n\n        if (empty)\n            break;\n    }\n\n    reap_filerefs(Vcb, Vcb->root_fileref);\n    reap_fcbs(Vcb);\n}\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(suppress: 28194)\n#endif\n__attribute__((nonnull(1,3)))\nvoid add_rollback(_In_ LIST_ENTRY* rollback, _In_ enum rollback_type type, _In_ __drv_aliasesMem void* ptr) {\n    rollback_item* ri;\n\n    ri = ExAllocatePoolWithTag(PagedPool, sizeof(rollback_item), ALLOC_TAG);\n    if (!ri) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    ri->type = type;\n    ri->ptr = ptr;\n    InsertTailList(rollback, &ri->list_entry);\n}\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(suppress: 28194)\n#endif\n__attribute__((nonnull(1,2)))\nNTSTATUS insert_tree_item(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _In_ root* r, _In_ uint64_t obj_id,\n                          _In_ uint8_t obj_type, _In_ uint64_t offset, _In_reads_bytes_opt_(size) _When_(return >= 0, __drv_aliasesMem) void* data,\n                          _In_ uint16_t size, _Out_opt_ traverse_ptr* ptp, _In_opt_ PIRP Irp) {\n    traverse_ptr tp;\n    KEY searchkey;\n    int cmp;\n    tree_data *td, *paritem;\n    tree* t;\n#ifdef _DEBUG\n    LIST_ENTRY* le;\n    KEY firstitem = {0xcccccccccccccccc,0xcc,0xcccccccccccccccc};\n#endif\n    NTSTATUS Status;\n\n    TRACE(\"(%p, %p, %I64x, %x, %I64x, %p, %x, %p)\\n\", Vcb, r, obj_id, obj_type, offset, data, size, ptp);\n\n    searchkey.obj_id = obj_id;\n    searchkey.obj_type = obj_type;\n    searchkey.offset = offset;\n\n    Status = find_item(Vcb, r, &tp, &searchkey, true, Irp);\n    if (Status == STATUS_NOT_FOUND) {\n        if (!r->treeholder.tree) {\n            Status = do_load_tree(Vcb, &r->treeholder, r, NULL, NULL, Irp);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"do_load_tree returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        if (r->treeholder.tree && r->treeholder.tree->header.num_items == 0) {\n            tp.tree = r->treeholder.tree;\n            tp.item = NULL;\n        } else {\n            ERR(\"error: unable to load tree for root %I64x\\n\", r->id);\n            return STATUS_INTERNAL_ERROR;\n        }\n    } else if (!NT_SUCCESS(Status)) {\n        ERR(\"find_item returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    TRACE(\"tp.item = %p\\n\", tp.item);\n\n    if (tp.item) {\n        TRACE(\"tp.item->key = %p\\n\", &tp.item->key);\n        cmp = keycmp(searchkey, tp.item->key);\n\n        if (cmp == 0 && !tp.item->ignore) {\n            ERR(\"error: key (%I64x,%x,%I64x) already present\\n\", obj_id, obj_type, offset);\n#ifdef DEBUG_PARANOID\n            int3;\n#endif\n            return STATUS_INTERNAL_ERROR;\n        }\n    } else\n        cmp = -1;\n\n    td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);\n    if (!td) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    td->key = searchkey;\n    td->size = size;\n    td->data = data;\n    td->ignore = false;\n    td->inserted = true;\n\n#ifdef _DEBUG\n    le = tp.tree->itemlist.Flink;\n    while (le != &tp.tree->itemlist) {\n        tree_data* td2 = CONTAINING_RECORD(le, tree_data, list_entry);\n        firstitem = td2->key;\n        break;\n    }\n\n    TRACE(\"inserting %I64x,%x,%I64x into tree beginning %I64x,%x,%I64x (num_items %x)\\n\", obj_id, obj_type, offset, firstitem.obj_id, firstitem.obj_type, firstitem.offset, tp.tree->header.num_items);\n#endif\n\n    if (cmp == -1) { // very first key in root\n        InsertHeadList(&tp.tree->itemlist, &td->list_entry);\n\n        paritem = tp.tree->paritem;\n        while (paritem) {\n            if (!keycmp(paritem->key, tp.item->key)) {\n                paritem->key = searchkey;\n            } else\n                break;\n\n            paritem = paritem->treeholder.tree->paritem;\n        }\n    } else if (cmp == 0)\n        InsertHeadList(tp.item->list_entry.Blink, &td->list_entry); // make sure non-deleted item is before deleted ones\n    else\n        InsertHeadList(&tp.item->list_entry, &td->list_entry);\n\n    tp.tree->header.num_items++;\n    tp.tree->size += size + sizeof(leaf_node);\n\n    if (!tp.tree->write) {\n        tp.tree->write = true;\n        Vcb->need_write = true;\n    }\n\n    if (ptp)\n        *ptp = tp;\n\n    t = tp.tree;\n    while (t) {\n        if (t->paritem && t->paritem->ignore) {\n            t->paritem->ignore = false;\n            t->parent->header.num_items++;\n            t->parent->size += sizeof(internal_node);\n        }\n\n        t->header.generation = Vcb->superblock.generation;\n        t = t->parent;\n    }\n\n    return STATUS_SUCCESS;\n}\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n__attribute__((nonnull(1,2)))\nNTSTATUS delete_tree_item(_In_ _Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, _Inout_ traverse_ptr* tp) {\n    tree* t;\n    uint64_t gen;\n\n    TRACE(\"deleting item %I64x,%x,%I64x (ignore = %s)\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset, tp->item->ignore ? \"true\" : \"false\");\n\n#ifdef DEBUG_PARANOID\n    if (tp->item->ignore) {\n        ERR(\"trying to delete already-deleted item %I64x,%x,%I64x\\n\", tp->item->key.obj_id, tp->item->key.obj_type, tp->item->key.offset);\n        int3;\n        return STATUS_INTERNAL_ERROR;\n    }\n#endif\n\n    tp->item->ignore = true;\n\n    if (!tp->tree->write) {\n        tp->tree->write = true;\n        Vcb->need_write = true;\n    }\n\n    tp->tree->header.num_items--;\n\n    if (tp->tree->header.level == 0)\n        tp->tree->size -= sizeof(leaf_node) + tp->item->size;\n    else\n        tp->tree->size -= sizeof(internal_node);\n\n    gen = tp->tree->Vcb->superblock.generation;\n\n    t = tp->tree;\n    while (t) {\n        t->header.generation = gen;\n        t = t->parent;\n    }\n\n    return STATUS_SUCCESS;\n}\n\n__attribute__((nonnull(1)))\nvoid clear_rollback(LIST_ENTRY* rollback) {\n    while (!IsListEmpty(rollback)) {\n        LIST_ENTRY* le = RemoveHeadList(rollback);\n        rollback_item* ri = CONTAINING_RECORD(le, rollback_item, list_entry);\n\n        switch (ri->type) {\n            case ROLLBACK_ADD_SPACE:\n            case ROLLBACK_SUBTRACT_SPACE:\n            case ROLLBACK_INSERT_EXTENT:\n            case ROLLBACK_DELETE_EXTENT:\n                ExFreePool(ri->ptr);\n                break;\n\n            default:\n                break;\n        }\n\n        ExFreePool(ri);\n    }\n}\n\n__attribute__((nonnull(1,2)))\nvoid do_rollback(device_extension* Vcb, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    rollback_item* ri;\n\n    while (!IsListEmpty(rollback)) {\n        LIST_ENTRY* le = RemoveTailList(rollback);\n        ri = CONTAINING_RECORD(le, rollback_item, list_entry);\n\n        switch (ri->type) {\n            case ROLLBACK_INSERT_EXTENT:\n            {\n                rollback_extent* re = ri->ptr;\n\n                re->ext->ignore = true;\n\n                switch (re->ext->extent_data.type) {\n                    case EXTENT_TYPE_REGULAR:\n                    case EXTENT_TYPE_PREALLOC: {\n                        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)re->ext->extent_data.data;\n\n                        if (ed2->size != 0) {\n                            chunk* c = get_chunk_from_address(Vcb, ed2->address);\n\n                            if (c) {\n                                Status = update_changed_extent_ref(Vcb, c, ed2->address, ed2->size, re->fcb->subvol->id,\n                                                                re->fcb->inode, re->ext->offset - ed2->offset, -1,\n                                                                re->fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, NULL);\n\n                                if (!NT_SUCCESS(Status))\n                                    ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                            }\n\n                            re->fcb->inode_item.st_blocks -= ed2->num_bytes;\n                        }\n\n                        break;\n                    }\n\n                    case EXTENT_TYPE_INLINE:\n                        re->fcb->inode_item.st_blocks -= re->ext->extent_data.decoded_size;\n                    break;\n                }\n\n                ExFreePool(re);\n                break;\n            }\n\n            case ROLLBACK_DELETE_EXTENT:\n            {\n                rollback_extent* re = ri->ptr;\n\n                re->ext->ignore = false;\n\n                switch (re->ext->extent_data.type) {\n                    case EXTENT_TYPE_REGULAR:\n                    case EXTENT_TYPE_PREALLOC: {\n                        EXTENT_DATA2* ed2 = (EXTENT_DATA2*)re->ext->extent_data.data;\n\n                        if (ed2->size != 0) {\n                            chunk* c = get_chunk_from_address(Vcb, ed2->address);\n\n                            if (c) {\n                                Status = update_changed_extent_ref(Vcb, c, ed2->address, ed2->size, re->fcb->subvol->id,\n                                                                re->fcb->inode, re->ext->offset - ed2->offset, 1,\n                                                                re->fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, NULL);\n\n                                if (!NT_SUCCESS(Status))\n                                    ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                            }\n\n                            re->fcb->inode_item.st_blocks += ed2->num_bytes;\n                        }\n\n                        break;\n                    }\n\n                    case EXTENT_TYPE_INLINE:\n                        re->fcb->inode_item.st_blocks += re->ext->extent_data.decoded_size;\n                    break;\n                }\n\n                ExFreePool(re);\n                break;\n            }\n\n            case ROLLBACK_ADD_SPACE:\n            case ROLLBACK_SUBTRACT_SPACE:\n            {\n                rollback_space* rs = ri->ptr;\n\n                if (rs->chunk)\n                    acquire_chunk_lock(rs->chunk, Vcb);\n\n                if (ri->type == ROLLBACK_ADD_SPACE)\n                    space_list_subtract2(rs->list, rs->list_size, rs->address, rs->length, NULL, NULL);\n                else\n                    space_list_add2(rs->list, rs->list_size, rs->address, rs->length, NULL, NULL);\n\n                if (rs->chunk) {\n                    if (ri->type == ROLLBACK_ADD_SPACE)\n                        rs->chunk->used += rs->length;\n                    else\n                        rs->chunk->used -= rs->length;\n                }\n\n                if (rs->chunk) {\n                    LIST_ENTRY* le2 = le->Blink;\n\n                    while (le2 != rollback) {\n                        LIST_ENTRY* le3 = le2->Blink;\n                        rollback_item* ri2 = CONTAINING_RECORD(le2, rollback_item, list_entry);\n\n                        if (ri2->type == ROLLBACK_ADD_SPACE || ri2->type == ROLLBACK_SUBTRACT_SPACE) {\n                            rollback_space* rs2 = ri2->ptr;\n\n                            if (rs2->chunk == rs->chunk) {\n                                if (ri2->type == ROLLBACK_ADD_SPACE) {\n                                    space_list_subtract2(rs2->list, rs2->list_size, rs2->address, rs2->length, NULL, NULL);\n                                    rs->chunk->used += rs2->length;\n                                } else {\n                                    space_list_add2(rs2->list, rs2->list_size, rs2->address, rs2->length, NULL, NULL);\n                                    rs->chunk->used -= rs2->length;\n                                }\n\n                                ExFreePool(rs2);\n                                RemoveEntryList(&ri2->list_entry);\n                                ExFreePool(ri2);\n                            }\n                        }\n\n                        le2 = le3;\n                    }\n\n                    release_chunk_lock(rs->chunk, Vcb);\n                }\n\n                ExFreePool(rs);\n\n                break;\n            }\n        }\n\n        ExFreePool(ri);\n    }\n}\n\n__attribute__((nonnull(1,2,3)))\nstatic NTSTATUS find_tree_end(tree* t, KEY* tree_end, bool* no_end) {\n    tree* p;\n\n    p = t;\n    do {\n        tree_data* pi;\n\n        if (!p->parent) {\n            tree_end->obj_id = 0xffffffffffffffff;\n            tree_end->obj_type = 0xff;\n            tree_end->offset = 0xffffffffffffffff;\n            *no_end = true;\n            return STATUS_SUCCESS;\n        }\n\n        pi = p->paritem;\n\n        if (pi->list_entry.Flink != &p->parent->itemlist) {\n            tree_data* td = CONTAINING_RECORD(pi->list_entry.Flink, tree_data, list_entry);\n\n            *tree_end = td->key;\n            *no_end = false;\n            return STATUS_SUCCESS;\n        }\n\n        p = p->parent;\n    } while (p);\n\n    return STATUS_INTERNAL_ERROR;\n}\n\n__attribute__((nonnull(1,2)))\nvoid clear_batch_list(device_extension* Vcb, LIST_ENTRY* batchlist) {\n    while (!IsListEmpty(batchlist)) {\n        LIST_ENTRY* le = RemoveHeadList(batchlist);\n        batch_root* br = CONTAINING_RECORD(le, batch_root, list_entry);\n\n        while (!IsListEmpty(&br->items_ind)) {\n            batch_item_ind* bii = CONTAINING_RECORD(RemoveHeadList(&br->items_ind), batch_item_ind, list_entry);\n\n            while (!IsListEmpty(&bii->items)) {\n                batch_item* bi = CONTAINING_RECORD(RemoveHeadList(&bii->items), batch_item, list_entry);\n\n                ExFreeToPagedLookasideList(&Vcb->batch_item_lookaside, bi);\n            }\n\n            ExFreePool(bii);\n        }\n\n        ExFreePool(br);\n    }\n}\n\n__attribute__((nonnull(1,2,3)))\nstatic void add_delete_inode_extref(device_extension* Vcb, batch_item* bi, LIST_ENTRY* listhead) {\n    batch_item* bi2;\n    LIST_ENTRY* le;\n    INODE_REF* delir = (INODE_REF*)bi->data;\n    INODE_EXTREF* ier;\n\n    TRACE(\"entry in INODE_REF not found, adding Batch_DeleteInodeExtRef entry\\n\");\n\n    bi2 = ExAllocateFromPagedLookasideList(&Vcb->batch_item_lookaside);\n    if (!bi2) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    ier = ExAllocatePoolWithTag(PagedPool, sizeof(INODE_EXTREF) - 1 + delir->n, ALLOC_TAG);\n    if (!ier) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(bi2);\n        return;\n    }\n\n    ier->dir = bi->key.offset;\n    ier->index = delir->index;\n    ier->n = delir->n;\n    RtlCopyMemory(ier->name, delir->name, delir->n);\n\n    bi2->key.obj_id = bi->key.obj_id;\n    bi2->key.obj_type = TYPE_INODE_EXTREF;\n    bi2->key.offset = calc_crc32c((uint32_t)bi->key.offset, (uint8_t*)ier->name, ier->n);\n    bi2->data = ier;\n    bi2->datalen = sizeof(INODE_EXTREF) - 1 + ier->n;\n    bi2->operation = Batch_DeleteInodeExtRef;\n\n    le = bi->list_entry.Flink;\n    while (le != listhead) {\n        batch_item* bi3 = CONTAINING_RECORD(le, batch_item, list_entry);\n\n        if (keycmp(bi3->key, bi2->key) != -1) {\n            InsertHeadList(le->Blink, &bi2->list_entry);\n            return;\n        }\n\n        le = le->Flink;\n    }\n\n    InsertTailList(listhead, &bi2->list_entry);\n}\n\n__attribute__((nonnull(1,2,3,4,6,7)))\nstatic NTSTATUS handle_batch_collision(device_extension* Vcb, batch_item* bi, tree* t, tree_data* td, tree_data* newtd, LIST_ENTRY* listhead, bool* ignore) {\n    if (bi->operation == Batch_Delete || bi->operation == Batch_SetXattr || bi->operation == Batch_DirItem || bi->operation == Batch_InodeRef ||\n        bi->operation == Batch_InodeExtRef || bi->operation == Batch_DeleteDirItem || bi->operation == Batch_DeleteInodeRef ||\n        bi->operation == Batch_DeleteInodeExtRef || bi->operation == Batch_DeleteXattr) {\n        uint16_t maxlen = (uint16_t)(Vcb->superblock.node_size - sizeof(tree_header) - sizeof(leaf_node));\n\n        switch (bi->operation) {\n            case Batch_SetXattr: {\n                if (td->size < sizeof(DIR_ITEM)) {\n                    ERR(\"(%I64x,%x,%I64x) was %u bytes, expected at least %Iu\\n\", bi->key.obj_id, bi->key.obj_type, bi->key.offset, td->size, sizeof(DIR_ITEM));\n                } else {\n                    uint8_t* newdata;\n                    ULONG size = td->size;\n                    DIR_ITEM* newxa = (DIR_ITEM*)bi->data;\n                    DIR_ITEM* xa = (DIR_ITEM*)td->data;\n\n                    while (true) {\n                        ULONG oldxasize;\n\n                        if (size < sizeof(DIR_ITEM) || size < sizeof(DIR_ITEM) - 1 + xa->m + xa->n) {\n                            ERR(\"(%I64x,%x,%I64x) was truncated\\n\", bi->key.obj_id, bi->key.obj_type, bi->key.offset);\n                            break;\n                        }\n\n                        oldxasize = sizeof(DIR_ITEM) - 1 + xa->m + xa->n;\n\n                        if (xa->n == newxa->n && RtlCompareMemory(newxa->name, xa->name, xa->n) == xa->n) {\n                            uint64_t pos;\n\n                            // replace\n\n                            if (td->size + bi->datalen - oldxasize > maxlen)\n                                ERR(\"DIR_ITEM would be over maximum size, truncating (%u + %u - %lu > %u)\\n\", td->size, bi->datalen, oldxasize, maxlen);\n\n                            newdata = ExAllocatePoolWithTag(PagedPool, td->size + bi->datalen - oldxasize, ALLOC_TAG);\n                            if (!newdata) {\n                                ERR(\"out of memory\\n\");\n                                return STATUS_INSUFFICIENT_RESOURCES;\n                            }\n\n                            pos = (uint8_t*)xa - td->data;\n                            if (pos + oldxasize < td->size) // copy after changed xattr\n                                RtlCopyMemory(newdata + pos + bi->datalen, td->data + pos + oldxasize, (ULONG)(td->size - pos - oldxasize));\n\n                            if (pos > 0) { // copy before changed xattr\n                                RtlCopyMemory(newdata, td->data, (ULONG)pos);\n                                xa = (DIR_ITEM*)(newdata + pos);\n                            } else\n                                xa = (DIR_ITEM*)newdata;\n\n                            RtlCopyMemory(xa, bi->data, bi->datalen);\n\n                            bi->datalen = (uint16_t)min(td->size + bi->datalen - oldxasize, maxlen);\n\n                            ExFreePool(bi->data);\n                            bi->data = newdata;\n\n                            break;\n                        }\n\n                        if ((uint8_t*)xa - (uint8_t*)td->data + oldxasize >= size) {\n                            // not found, add to end of data\n\n                            if (td->size + bi->datalen > maxlen)\n                                ERR(\"DIR_ITEM would be over maximum size, truncating (%u + %u > %u)\\n\", td->size, bi->datalen, maxlen);\n\n                            newdata = ExAllocatePoolWithTag(PagedPool, td->size + bi->datalen, ALLOC_TAG);\n                            if (!newdata) {\n                                ERR(\"out of memory\\n\");\n                                return STATUS_INSUFFICIENT_RESOURCES;\n                            }\n\n                            RtlCopyMemory(newdata, td->data, td->size);\n\n                            xa = (DIR_ITEM*)((uint8_t*)newdata + td->size);\n                            RtlCopyMemory(xa, bi->data, bi->datalen);\n\n                            bi->datalen = min(bi->datalen + td->size, maxlen);\n\n                            ExFreePool(bi->data);\n                            bi->data = newdata;\n\n                            break;\n                        } else {\n                            xa = (DIR_ITEM*)&xa->name[xa->m + xa->n];\n                            size -= oldxasize;\n                        }\n                    }\n                }\n                break;\n            }\n\n            case Batch_DirItem: {\n                uint8_t* newdata;\n\n                if (td->size + bi->datalen > maxlen) {\n                    ERR(\"DIR_ITEM would be over maximum size (%u + %u > %u)\\n\", td->size, bi->datalen, maxlen);\n                    return STATUS_INTERNAL_ERROR;\n                }\n\n                newdata = ExAllocatePoolWithTag(PagedPool, td->size + bi->datalen, ALLOC_TAG);\n                if (!newdata) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(newdata, td->data, td->size);\n\n                RtlCopyMemory(newdata + td->size, bi->data, bi->datalen);\n\n                bi->datalen += td->size;\n\n                ExFreePool(bi->data);\n                bi->data = newdata;\n\n                break;\n            }\n\n            case Batch_InodeRef: {\n                uint8_t* newdata;\n\n                if (td->size + bi->datalen > maxlen) {\n                    if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {\n                        INODE_REF* ir = (INODE_REF*)bi->data;\n                        INODE_EXTREF* ier;\n                        uint16_t ierlen;\n                        batch_item* bi2;\n                        LIST_ENTRY* le;\n                        bool inserted = false;\n\n                        TRACE(\"INODE_REF would be too long, adding INODE_EXTREF instead\\n\");\n\n                        ierlen = (uint16_t)(offsetof(INODE_EXTREF, name[0]) + ir->n);\n\n                        ier = ExAllocatePoolWithTag(PagedPool, ierlen, ALLOC_TAG);\n                        if (!ier) {\n                            ERR(\"out of memory\\n\");\n                            return STATUS_INSUFFICIENT_RESOURCES;\n                        }\n\n                        ier->dir = bi->key.offset;\n                        ier->index = ir->index;\n                        ier->n = ir->n;\n                        RtlCopyMemory(ier->name, ir->name, ier->n);\n\n                        bi2 = ExAllocateFromPagedLookasideList(&Vcb->batch_item_lookaside);\n                        if (!bi2) {\n                            ERR(\"out of memory\\n\");\n                            ExFreePool(ier);\n                            return STATUS_INSUFFICIENT_RESOURCES;\n                        }\n\n                        bi2->key.obj_id = bi->key.obj_id;\n                        bi2->key.obj_type = TYPE_INODE_EXTREF;\n                        bi2->key.offset = calc_crc32c((uint32_t)ier->dir, (uint8_t*)ier->name, ier->n);\n                        bi2->data = ier;\n                        bi2->datalen = ierlen;\n                        bi2->operation = Batch_InodeExtRef;\n\n                        le = bi->list_entry.Flink;\n                        while (le != listhead) {\n                            batch_item* bi3 = CONTAINING_RECORD(le, batch_item, list_entry);\n\n                            if (keycmp(bi3->key, bi2->key) != -1) {\n                                InsertHeadList(le->Blink, &bi2->list_entry);\n                                inserted = true;\n                            }\n\n                            le = le->Flink;\n                        }\n\n                        if (!inserted)\n                            InsertTailList(listhead, &bi2->list_entry);\n\n                        *ignore = true;\n                        return STATUS_SUCCESS;\n                    } else {\n                        ERR(\"INODE_REF would be over maximum size (%u + %u > %u)\\n\", td->size, bi->datalen, maxlen);\n                        return STATUS_INTERNAL_ERROR;\n                    }\n                }\n\n                newdata = ExAllocatePoolWithTag(PagedPool, td->size + bi->datalen, ALLOC_TAG);\n                if (!newdata) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(newdata, td->data, td->size);\n\n                RtlCopyMemory(newdata + td->size, bi->data, bi->datalen);\n\n                bi->datalen += td->size;\n\n                ExFreePool(bi->data);\n                bi->data = newdata;\n\n                break;\n            }\n\n            case Batch_InodeExtRef: {\n                uint8_t* newdata;\n\n                if (td->size + bi->datalen > maxlen) {\n                    ERR(\"INODE_EXTREF would be over maximum size (%u + %u > %u)\\n\", td->size, bi->datalen, maxlen);\n                    return STATUS_INTERNAL_ERROR;\n                }\n\n                newdata = ExAllocatePoolWithTag(PagedPool, td->size + bi->datalen, ALLOC_TAG);\n                if (!newdata) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                RtlCopyMemory(newdata, td->data, td->size);\n\n                RtlCopyMemory(newdata + td->size, bi->data, bi->datalen);\n\n                bi->datalen += td->size;\n\n                ExFreePool(bi->data);\n                bi->data = newdata;\n\n                break;\n            }\n\n            case Batch_DeleteDirItem: {\n                if (td->size < sizeof(DIR_ITEM)) {\n                    ERR(\"DIR_ITEM was %u bytes, expected at least %Iu\\n\", td->size, sizeof(DIR_ITEM));\n                    return STATUS_INTERNAL_ERROR;\n                } else {\n                    DIR_ITEM *di, *deldi;\n                    LONG len;\n\n                    deldi = (DIR_ITEM*)bi->data;\n                    di = (DIR_ITEM*)td->data;\n                    len = td->size;\n\n                    do {\n                        if (di->m == deldi->m && di->n == deldi->n && RtlCompareMemory(di->name, deldi->name, di->n + di->m) == di->n + di->m) {\n                            uint16_t newlen = td->size - (sizeof(DIR_ITEM) - sizeof(char) + di->n + di->m);\n\n                            if (newlen == 0) {\n                                TRACE(\"deleting DIR_ITEM\\n\");\n                            } else {\n                                uint8_t *newdi = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *dioff;\n                                tree_data* td2;\n\n                                if (!newdi) {\n                                    ERR(\"out of memory\\n\");\n                                    return STATUS_INSUFFICIENT_RESOURCES;\n                                }\n\n                                TRACE(\"modifying DIR_ITEM\\n\");\n\n                                if ((uint8_t*)di > td->data) {\n                                    RtlCopyMemory(newdi, td->data, (uint8_t*)di - td->data);\n                                    dioff = newdi + ((uint8_t*)di - td->data);\n                                } else {\n                                    dioff = newdi;\n                                }\n\n                                if ((uint8_t*)&di->name[di->n + di->m] < td->data + td->size)\n                                    RtlCopyMemory(dioff, &di->name[di->n + di->m], td->size - ((uint8_t*)&di->name[di->n + di->m] - td->data));\n\n                                td2 = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);\n                                if (!td2) {\n                                    ERR(\"out of memory\\n\");\n                                    ExFreePool(newdi);\n                                    return STATUS_INSUFFICIENT_RESOURCES;\n                                }\n\n                                td2->key = bi->key;\n                                td2->size = newlen;\n                                td2->data = newdi;\n                                td2->ignore = false;\n                                td2->inserted = true;\n\n                                InsertHeadList(td->list_entry.Blink, &td2->list_entry);\n\n                                t->header.num_items++;\n                                t->size += newlen + sizeof(leaf_node);\n                                t->write = true;\n                            }\n\n                            break;\n                        }\n\n                        len -= sizeof(DIR_ITEM) - sizeof(char) + di->n + di->m;\n                        di = (DIR_ITEM*)&di->name[di->n + di->m];\n\n                        if (len == 0) {\n                            TRACE(\"could not find DIR_ITEM to delete\\n\");\n                            *ignore = true;\n                            return STATUS_SUCCESS;\n                        }\n                    } while (len > 0);\n                }\n                break;\n            }\n\n            case Batch_DeleteInodeRef: {\n                if (td->size < sizeof(INODE_REF)) {\n                    ERR(\"INODE_REF was %u bytes, expected at least %Iu\\n\", td->size, sizeof(INODE_REF));\n                    return STATUS_INTERNAL_ERROR;\n                } else {\n                    INODE_REF *ir, *delir;\n                    ULONG len;\n                    bool changed = false;\n\n                    delir = (INODE_REF*)bi->data;\n                    ir = (INODE_REF*)td->data;\n                    len = td->size;\n\n                    do {\n                        uint16_t itemlen;\n\n                        if (len < sizeof(INODE_REF) || len < offsetof(INODE_REF, name[0]) + ir->n) {\n                            ERR(\"INODE_REF was truncated\\n\");\n                            break;\n                        }\n\n                        itemlen = (uint16_t)offsetof(INODE_REF, name[0]) + ir->n;\n\n                        if (ir->n == delir->n && RtlCompareMemory(ir->name, delir->name, ir->n) == ir->n) {\n                            uint16_t newlen = td->size - itemlen;\n\n                            changed = true;\n\n                            if (newlen == 0)\n                                TRACE(\"deleting INODE_REF\\n\");\n                            else {\n                                uint8_t *newir = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *iroff;\n                                tree_data* td2;\n\n                                if (!newir) {\n                                    ERR(\"out of memory\\n\");\n                                    return STATUS_INSUFFICIENT_RESOURCES;\n                                }\n\n                                TRACE(\"modifying INODE_REF\\n\");\n\n                                if ((uint8_t*)ir > td->data) {\n                                    RtlCopyMemory(newir, td->data, (uint8_t*)ir - td->data);\n                                    iroff = newir + ((uint8_t*)ir - td->data);\n                                } else {\n                                    iroff = newir;\n                                }\n\n                                if ((uint8_t*)&ir->name[ir->n] < td->data + td->size)\n                                    RtlCopyMemory(iroff, &ir->name[ir->n], td->size - ((uint8_t*)&ir->name[ir->n] - td->data));\n\n                                td2 = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);\n                                if (!td2) {\n                                    ERR(\"out of memory\\n\");\n                                    ExFreePool(newir);\n                                    return STATUS_INSUFFICIENT_RESOURCES;\n                                }\n\n                                td2->key = bi->key;\n                                td2->size = newlen;\n                                td2->data = newir;\n                                td2->ignore = false;\n                                td2->inserted = true;\n\n                                InsertHeadList(td->list_entry.Blink, &td2->list_entry);\n\n                                t->header.num_items++;\n                                t->size += newlen + sizeof(leaf_node);\n                                t->write = true;\n                            }\n\n                            break;\n                        }\n\n                        if (len > itemlen) {\n                            len -= itemlen;\n                            ir = (INODE_REF*)&ir->name[ir->n];\n                        } else\n                            break;\n                    } while (len > 0);\n\n                    if (!changed) {\n                        if (Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {\n                            TRACE(\"entry in INODE_REF not found, adding Batch_DeleteInodeExtRef entry\\n\");\n\n                            add_delete_inode_extref(Vcb, bi, listhead);\n\n                            *ignore = true;\n                            return STATUS_SUCCESS;\n                        } else\n                            WARN(\"entry not found in INODE_REF\\n\");\n                    }\n                }\n\n                break;\n            }\n\n            case Batch_DeleteInodeExtRef: {\n                if (td->size < sizeof(INODE_EXTREF)) {\n                    ERR(\"INODE_EXTREF was %u bytes, expected at least %Iu\\n\", td->size, sizeof(INODE_EXTREF));\n                    return STATUS_INTERNAL_ERROR;\n                } else {\n                    INODE_EXTREF *ier, *delier;\n                    ULONG len;\n\n                    delier = (INODE_EXTREF*)bi->data;\n                    ier = (INODE_EXTREF*)td->data;\n                    len = td->size;\n\n                    do {\n                        uint16_t itemlen;\n\n                        if (len < sizeof(INODE_EXTREF) || len < offsetof(INODE_EXTREF, name[0]) + ier->n) {\n                            ERR(\"INODE_REF was truncated\\n\");\n                            break;\n                        }\n\n                        itemlen = (uint16_t)offsetof(INODE_EXTREF, name[0]) + ier->n;\n\n                        if (ier->dir == delier->dir && ier->n == delier->n && RtlCompareMemory(ier->name, delier->name, ier->n) == ier->n) {\n                            uint16_t newlen = td->size - itemlen;\n\n                            if (newlen == 0)\n                                TRACE(\"deleting INODE_EXTREF\\n\");\n                            else {\n                                uint8_t *newier = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *ieroff;\n                                tree_data* td2;\n\n                                if (!newier) {\n                                    ERR(\"out of memory\\n\");\n                                    return STATUS_INSUFFICIENT_RESOURCES;\n                                }\n\n                                TRACE(\"modifying INODE_EXTREF\\n\");\n\n                                if ((uint8_t*)ier > td->data) {\n                                    RtlCopyMemory(newier, td->data, (uint8_t*)ier - td->data);\n                                    ieroff = newier + ((uint8_t*)ier - td->data);\n                                } else {\n                                    ieroff = newier;\n                                }\n\n                                if ((uint8_t*)&ier->name[ier->n] < td->data + td->size)\n                                    RtlCopyMemory(ieroff, &ier->name[ier->n], td->size - ((uint8_t*)&ier->name[ier->n] - td->data));\n\n                                td2 = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);\n                                if (!td2) {\n                                    ERR(\"out of memory\\n\");\n                                    ExFreePool(newier);\n                                    return STATUS_INSUFFICIENT_RESOURCES;\n                                }\n\n                                td2->key = bi->key;\n                                td2->size = newlen;\n                                td2->data = newier;\n                                td2->ignore = false;\n                                td2->inserted = true;\n\n                                InsertHeadList(td->list_entry.Blink, &td2->list_entry);\n\n                                t->header.num_items++;\n                                t->size += newlen + sizeof(leaf_node);\n                                t->write = true;\n                            }\n\n                            break;\n                        }\n\n                        if (len > itemlen) {\n                            len -= itemlen;\n                            ier = (INODE_EXTREF*)&ier->name[ier->n];\n                        } else\n                            break;\n                    } while (len > 0);\n                }\n                break;\n            }\n\n            case Batch_DeleteXattr: {\n                if (td->size < sizeof(DIR_ITEM)) {\n                    ERR(\"XATTR_ITEM was %u bytes, expected at least %Iu\\n\", td->size, sizeof(DIR_ITEM));\n                    return STATUS_INTERNAL_ERROR;\n                } else {\n                    DIR_ITEM *di, *deldi;\n                    LONG len;\n\n                    deldi = (DIR_ITEM*)bi->data;\n                    di = (DIR_ITEM*)td->data;\n                    len = td->size;\n\n                    do {\n                        if (di->n == deldi->n && RtlCompareMemory(di->name, deldi->name, di->n) == di->n) {\n                            uint16_t newlen = td->size - ((uint16_t)offsetof(DIR_ITEM, name[0]) + di->n + di->m);\n\n                            if (newlen == 0)\n                                TRACE(\"deleting XATTR_ITEM\\n\");\n                            else {\n                                uint8_t *newdi = ExAllocatePoolWithTag(PagedPool, newlen, ALLOC_TAG), *dioff;\n                                tree_data* td2;\n\n                                if (!newdi) {\n                                    ERR(\"out of memory\\n\");\n                                    return STATUS_INSUFFICIENT_RESOURCES;\n                                }\n\n                                TRACE(\"modifying XATTR_ITEM\\n\");\n\n                                if ((uint8_t*)di > td->data) {\n                                    RtlCopyMemory(newdi, td->data, (uint8_t*)di - td->data);\n                                    dioff = newdi + ((uint8_t*)di - td->data);\n                                } else\n                                    dioff = newdi;\n\n                                if ((uint8_t*)&di->name[di->n + di->m] < td->data + td->size)\n                                    RtlCopyMemory(dioff, &di->name[di->n + di->m], td->size - ((uint8_t*)&di->name[di->n + di->m] - td->data));\n\n                                td2 = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);\n                                if (!td2) {\n                                    ERR(\"out of memory\\n\");\n                                    ExFreePool(newdi);\n                                    return STATUS_INSUFFICIENT_RESOURCES;\n                                }\n\n                                td2->key = bi->key;\n                                td2->size = newlen;\n                                td2->data = newdi;\n                                td2->ignore = false;\n                                td2->inserted = true;\n\n                                InsertHeadList(td->list_entry.Blink, &td2->list_entry);\n\n                                t->header.num_items++;\n                                t->size += newlen + sizeof(leaf_node);\n                                t->write = true;\n                            }\n\n                            break;\n                        }\n\n                        len -= sizeof(DIR_ITEM) - sizeof(char) + di->n + di->m;\n                        di = (DIR_ITEM*)&di->name[di->n + di->m];\n\n                        if (len == 0) {\n                            TRACE(\"could not find DIR_ITEM to delete\\n\");\n                            *ignore = true;\n                            return STATUS_SUCCESS;\n                        }\n                    } while (len > 0);\n                }\n                break;\n            }\n\n            case Batch_Delete:\n                break;\n\n            default:\n                ERR(\"unexpected batch operation type\\n\");\n                return STATUS_INTERNAL_ERROR;\n        }\n\n        // delete old item\n        if (!td->ignore) {\n            td->ignore = true;\n\n            t->header.num_items--;\n            t->size -= sizeof(leaf_node) + td->size;\n            t->write = true;\n        }\n\n        if (newtd) {\n            newtd->data = bi->data;\n            newtd->size = bi->datalen;\n            InsertHeadList(td->list_entry.Blink, &newtd->list_entry);\n        }\n    } else {\n        ERR(\"(%I64x,%x,%I64x) already exists\\n\", bi->key.obj_id, bi->key.obj_type, bi->key.offset);\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    *ignore = false;\n    return STATUS_SUCCESS;\n}\n\n__attribute__((nonnull(1,2)))\nstatic NTSTATUS commit_batch_list_root(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, batch_root* br, PIRP Irp) {\n    LIST_ENTRY items;\n    LIST_ENTRY* le;\n    NTSTATUS Status;\n\n    TRACE(\"root: %I64x\\n\", br->r->id);\n\n    InitializeListHead(&items);\n\n    // move sub-lists into one big list\n\n    while (!IsListEmpty(&br->items_ind)) {\n        batch_item_ind* bii = CONTAINING_RECORD(RemoveHeadList(&br->items_ind), batch_item_ind, list_entry);\n\n        items.Blink->Flink = bii->items.Flink;\n        bii->items.Flink->Blink = items.Blink;\n        items.Blink = bii->items.Blink;\n        bii->items.Blink->Flink = &items;\n\n        ExFreePool(bii);\n    }\n\n    le = items.Flink;\n    while (le != &items) {\n        batch_item* bi = CONTAINING_RECORD(le, batch_item, list_entry);\n        LIST_ENTRY* le2;\n        traverse_ptr tp;\n        KEY tree_end;\n        bool no_end;\n        tree_data *td, *listhead;\n        int cmp;\n        tree* t;\n        bool ignore = false;\n\n        TRACE(\"(%I64x,%x,%I64x)\\n\", bi->key.obj_id, bi->key.obj_type, bi->key.offset);\n\n        Status = find_item(Vcb, br->r, &tp, &bi->key, true, Irp);\n        if (!NT_SUCCESS(Status)) { // FIXME - handle STATUS_NOT_FOUND\n            ERR(\"find_item returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        Status = find_tree_end(tp.tree, &tree_end, &no_end);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"find_tree_end returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (bi->operation == Batch_DeleteInode) {\n            if (tp.item->key.obj_id == bi->key.obj_id) {\n                bool ended = false;\n\n                td = tp.item;\n\n                if (!tp.item->ignore) {\n                    tp.item->ignore = true;\n                    tp.tree->header.num_items--;\n                    tp.tree->size -= tp.item->size + sizeof(leaf_node);\n                    tp.tree->write = true;\n                }\n\n                le2 = tp.item->list_entry.Flink;\n                while (le2 != &tp.tree->itemlist) {\n                    td = CONTAINING_RECORD(le2, tree_data, list_entry);\n\n                    if (td->key.obj_id == bi->key.obj_id) {\n                        if (!td->ignore) {\n                            td->ignore = true;\n                            tp.tree->header.num_items--;\n                            tp.tree->size -= td->size + sizeof(leaf_node);\n                            tp.tree->write = true;\n                        }\n                    } else {\n                        ended = true;\n                        break;\n                    }\n\n                    le2 = le2->Flink;\n                }\n\n                while (!ended) {\n                    traverse_ptr next_tp;\n\n                    tp.item = td;\n\n                    if (!find_next_item(Vcb, &tp, &next_tp, false, Irp))\n                        break;\n\n                    tp = next_tp;\n\n                    le2 = &tp.item->list_entry;\n                    while (le2 != &tp.tree->itemlist) {\n                        td = CONTAINING_RECORD(le2, tree_data, list_entry);\n\n                        if (td->key.obj_id == bi->key.obj_id) {\n                            if (!td->ignore) {\n                                td->ignore = true;\n                                tp.tree->header.num_items--;\n                                tp.tree->size -= td->size + sizeof(leaf_node);\n                                tp.tree->write = true;\n                            }\n                        } else {\n                            ended = true;\n                            break;\n                        }\n\n                        le2 = le2->Flink;\n                    }\n                }\n            }\n        } else if (bi->operation == Batch_DeleteExtentData) {\n            if (tp.item->key.obj_id < bi->key.obj_id || (tp.item->key.obj_id == bi->key.obj_id && tp.item->key.obj_type < bi->key.obj_type)) {\n                traverse_ptr tp2;\n\n                if (find_next_item(Vcb, &tp, &tp2, false, Irp)) {\n                    if (tp2.item->key.obj_id == bi->key.obj_id && tp2.item->key.obj_type == bi->key.obj_type) {\n                        tp = tp2;\n                        Status = find_tree_end(tp.tree, &tree_end, &no_end);\n                        if (!NT_SUCCESS(Status)) {\n                            ERR(\"find_tree_end returned %08lx\\n\", Status);\n                            return Status;\n                        }\n                    }\n                }\n            }\n\n            if (tp.item->key.obj_id == bi->key.obj_id && tp.item->key.obj_type == bi->key.obj_type) {\n                bool ended = false;\n\n                td = tp.item;\n\n                if (!tp.item->ignore) {\n                    tp.item->ignore = true;\n                    tp.tree->header.num_items--;\n                    tp.tree->size -= tp.item->size + sizeof(leaf_node);\n                    tp.tree->write = true;\n                }\n\n                le2 = tp.item->list_entry.Flink;\n                while (le2 != &tp.tree->itemlist) {\n                    td = CONTAINING_RECORD(le2, tree_data, list_entry);\n\n                    if (td->key.obj_id == bi->key.obj_id && td->key.obj_type == bi->key.obj_type) {\n                        if (!td->ignore) {\n                            td->ignore = true;\n                            tp.tree->header.num_items--;\n                            tp.tree->size -= td->size + sizeof(leaf_node);\n                            tp.tree->write = true;\n                        }\n                    } else {\n                        ended = true;\n                        break;\n                    }\n\n                    le2 = le2->Flink;\n                }\n\n                while (!ended) {\n                    traverse_ptr next_tp;\n\n                    tp.item = td;\n\n                    if (!find_next_item(Vcb, &tp, &next_tp, false, Irp))\n                        break;\n\n                    tp = next_tp;\n\n                    le2 = &tp.item->list_entry;\n                    while (le2 != &tp.tree->itemlist) {\n                        td = CONTAINING_RECORD(le2, tree_data, list_entry);\n\n                        if (td->key.obj_id == bi->key.obj_id && td->key.obj_type == bi->key.obj_type) {\n                            if (!td->ignore) {\n                                td->ignore = true;\n                                tp.tree->header.num_items--;\n                                tp.tree->size -= td->size + sizeof(leaf_node);\n                                tp.tree->write = true;\n                            }\n                        } else {\n                            ended = true;\n                            break;\n                        }\n\n                        le2 = le2->Flink;\n                    }\n                }\n            }\n        } else if (bi->operation == Batch_DeleteFreeSpace) {\n            if (tp.item->key.obj_id >= bi->key.obj_id && tp.item->key.obj_id < bi->key.obj_id + bi->key.offset) {\n                bool ended = false;\n\n                td = tp.item;\n\n                if (!tp.item->ignore) {\n                    tp.item->ignore = true;\n                    tp.tree->header.num_items--;\n                    tp.tree->size -= tp.item->size + sizeof(leaf_node);\n                    tp.tree->write = true;\n                }\n\n                le2 = tp.item->list_entry.Flink;\n                while (le2 != &tp.tree->itemlist) {\n                    td = CONTAINING_RECORD(le2, tree_data, list_entry);\n\n                    if (td->key.obj_id >= bi->key.obj_id && td->key.obj_id < bi->key.obj_id + bi->key.offset) {\n                        if (!td->ignore) {\n                            td->ignore = true;\n                            tp.tree->header.num_items--;\n                            tp.tree->size -= td->size + sizeof(leaf_node);\n                            tp.tree->write = true;\n                        }\n                    } else {\n                        ended = true;\n                        break;\n                    }\n\n                    le2 = le2->Flink;\n                }\n\n                while (!ended) {\n                    traverse_ptr next_tp;\n\n                    tp.item = td;\n\n                    if (!find_next_item(Vcb, &tp, &next_tp, false, Irp))\n                        break;\n\n                    tp = next_tp;\n\n                    le2 = &tp.item->list_entry;\n                    while (le2 != &tp.tree->itemlist) {\n                        td = CONTAINING_RECORD(le2, tree_data, list_entry);\n\n                        if (td->key.obj_id >= bi->key.obj_id && td->key.obj_id < bi->key.obj_id + bi->key.offset) {\n                            if (!td->ignore) {\n                                td->ignore = true;\n                                tp.tree->header.num_items--;\n                                tp.tree->size -= td->size + sizeof(leaf_node);\n                                tp.tree->write = true;\n                            }\n                        } else {\n                            ended = true;\n                            break;\n                        }\n\n                        le2 = le2->Flink;\n                    }\n                }\n            }\n        } else {\n            if (bi->operation == Batch_Delete || bi->operation == Batch_DeleteDirItem || bi->operation == Batch_DeleteInodeRef ||\n                bi->operation == Batch_DeleteInodeExtRef || bi->operation == Batch_DeleteXattr)\n                td = NULL;\n            else {\n                td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);\n                if (!td) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                td->key = bi->key;\n                td->size = bi->datalen;\n                td->data = bi->data;\n                td->ignore = false;\n                td->inserted = true;\n            }\n\n            cmp = keycmp(bi->key, tp.item->key);\n\n            if (cmp == -1) { // very first key in root\n                if (td) {\n                    tree_data* paritem;\n\n                    InsertHeadList(&tp.tree->itemlist, &td->list_entry);\n\n                    paritem = tp.tree->paritem;\n                    while (paritem) {\n                        if (!keycmp(paritem->key, tp.item->key)) {\n                            paritem->key = bi->key;\n                        } else\n                            break;\n\n                        paritem = paritem->treeholder.tree->paritem;\n                    }\n                }\n            } else if (cmp == 0) { // item already exists\n                if (tp.item->ignore) {\n                    if (td)\n                        InsertHeadList(tp.item->list_entry.Blink, &td->list_entry);\n                } else {\n                    Status = handle_batch_collision(Vcb, bi, tp.tree, tp.item, td, &items, &ignore);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"handle_batch_collision returned %08lx\\n\", Status);\n#ifdef _DEBUG\n                        int3;\n#endif\n\n                        if (td)\n                            ExFreeToPagedLookasideList(&Vcb->tree_data_lookaside, td);\n\n                        return Status;\n                    }\n                }\n            } else if (td) {\n                InsertHeadList(&tp.item->list_entry, &td->list_entry);\n            }\n\n            if (bi->operation == Batch_DeleteInodeRef && cmp != 0 && Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {\n                add_delete_inode_extref(Vcb, bi, &items);\n            }\n\n            if (!ignore && td) {\n                tp.tree->header.num_items++;\n                tp.tree->size += bi->datalen + sizeof(leaf_node);\n                tp.tree->write = true;\n\n                listhead = td;\n            } else\n                listhead = tp.item;\n\n            while (listhead->list_entry.Blink != &tp.tree->itemlist) {\n                tree_data* prevtd = CONTAINING_RECORD(listhead->list_entry.Blink, tree_data, list_entry);\n\n                if (!keycmp(prevtd->key, listhead->key))\n                    listhead = prevtd;\n                else\n                    break;\n            }\n\n            le2 = le->Flink;\n            while (le2 != &items) {\n                batch_item* bi2 = CONTAINING_RECORD(le2, batch_item, list_entry);\n\n                if (bi2->operation == Batch_DeleteInode || bi2->operation == Batch_DeleteExtentData || bi2->operation == Batch_DeleteFreeSpace)\n                    break;\n\n                if (no_end || keycmp(bi2->key, tree_end) == -1) {\n                    LIST_ENTRY* le3;\n                    bool inserted = false;\n\n                    ignore = false;\n\n                    if (bi2->operation == Batch_Delete || bi2->operation == Batch_DeleteDirItem || bi2->operation == Batch_DeleteInodeRef ||\n                        bi2->operation == Batch_DeleteInodeExtRef || bi2->operation == Batch_DeleteXattr)\n                        td = NULL;\n                    else {\n                        td = ExAllocateFromPagedLookasideList(&Vcb->tree_data_lookaside);\n                        if (!td) {\n                            ERR(\"out of memory\\n\");\n                            return STATUS_INSUFFICIENT_RESOURCES;\n                        }\n\n                        td->key = bi2->key;\n                        td->size = bi2->datalen;\n                        td->data = bi2->data;\n                        td->ignore = false;\n                        td->inserted = true;\n                    }\n\n                    le3 = &listhead->list_entry;\n                    while (le3 != &tp.tree->itemlist) {\n                        tree_data* td2 = CONTAINING_RECORD(le3, tree_data, list_entry);\n\n                        cmp = keycmp(bi2->key, td2->key);\n\n                        if (cmp == 0) {\n                            if (td2->ignore) {\n                                if (td) {\n                                    InsertHeadList(le3->Blink, &td->list_entry);\n                                    inserted = true;\n                                } else if (bi2->operation == Batch_DeleteInodeRef && Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {\n                                    add_delete_inode_extref(Vcb, bi2, &items);\n                                }\n                            } else {\n                                Status = handle_batch_collision(Vcb, bi2, tp.tree, td2, td, &items, &ignore);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"handle_batch_collision returned %08lx\\n\", Status);\n#ifdef _DEBUG\n                                    int3;\n#endif\n                                    return Status;\n                                }\n                            }\n\n                            inserted = true;\n                            break;\n                        } else if (cmp == -1) {\n                            if (td) {\n                                InsertHeadList(le3->Blink, &td->list_entry);\n                                inserted = true;\n                            } else if (bi2->operation == Batch_DeleteInodeRef && Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {\n                                add_delete_inode_extref(Vcb, bi2, &items);\n                            }\n                            break;\n                        }\n\n                        le3 = le3->Flink;\n                    }\n\n                    if (td) {\n                        if (!inserted)\n                            InsertTailList(&tp.tree->itemlist, &td->list_entry);\n\n                        if (!ignore) {\n                            tp.tree->header.num_items++;\n                            tp.tree->size += bi2->datalen + sizeof(leaf_node);\n\n                            listhead = td;\n                        }\n                    } else if (!inserted && bi2->operation == Batch_DeleteInodeRef && Vcb->superblock.incompat_flags & BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF) {\n                        add_delete_inode_extref(Vcb, bi2, &items);\n                    }\n\n                    while (listhead->list_entry.Blink != &tp.tree->itemlist) {\n                        tree_data* prevtd = CONTAINING_RECORD(listhead->list_entry.Blink, tree_data, list_entry);\n\n                        if (!keycmp(prevtd->key, listhead->key))\n                            listhead = prevtd;\n                        else\n                            break;\n                    }\n\n                    le = le2;\n                } else\n                    break;\n\n                le2 = le2->Flink;\n            }\n\n            t = tp.tree;\n            while (t) {\n                if (t->paritem && t->paritem->ignore) {\n                    t->paritem->ignore = false;\n                    t->parent->header.num_items++;\n                    t->parent->size += sizeof(internal_node);\n                }\n\n                t->header.generation = Vcb->superblock.generation;\n                t = t->parent;\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    // FIXME - remove as we are going along\n    while (!IsListEmpty(&items)) {\n        batch_item* bi = CONTAINING_RECORD(RemoveHeadList(&items), batch_item, list_entry);\n\n        if ((bi->operation == Batch_DeleteDirItem || bi->operation == Batch_DeleteInodeRef ||\n            bi->operation == Batch_DeleteInodeExtRef || bi->operation == Batch_DeleteXattr) && bi->data)\n            ExFreePool(bi->data);\n\n        ExFreeToPagedLookasideList(&Vcb->batch_item_lookaside, bi);\n    }\n\n    return STATUS_SUCCESS;\n}\n\n__attribute__((nonnull(1,2)))\nNTSTATUS commit_batch_list(_Requires_exclusive_lock_held_(_Curr_->tree_lock) device_extension* Vcb, LIST_ENTRY* batchlist, PIRP Irp) {\n    NTSTATUS Status;\n\n    while (!IsListEmpty(batchlist)) {\n        LIST_ENTRY* le = RemoveHeadList(batchlist);\n        batch_root* br2 = CONTAINING_RECORD(le, batch_root, list_entry);\n\n        Status = commit_batch_list_root(Vcb, br2, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"commit_batch_list_root returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        ExFreePool(br2);\n    }\n\n    return STATUS_SUCCESS;\n}\n"
  },
  {
    "path": "src/ubtrfs/resource.h",
    "content": "//{{NO_DEPENDENCIES}}\r\n// Microsoft Visual C++ generated include file.\r\n// Used by ubtrfs.rc\r\n//\r\n\r\n// Next default values for new objects\r\n// \r\n#ifdef APSTUDIO_INVOKED\r\n#ifndef APSTUDIO_READONLY_SYMBOLS\r\n#define _APS_NEXT_RESOURCE_VALUE        101\r\n#define _APS_NEXT_COMMAND_VALUE         40001\r\n#define _APS_NEXT_CONTROL_VALUE         1001\r\n#define _APS_NEXT_SYMED_VALUE           101\r\n#endif\r\n#endif\r\n"
  },
  {
    "path": "src/ubtrfs/ubtrfs.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include <stdlib.h>\n#include <stddef.h>\n#include <time.h>\n#include <ntstatus.h>\n#define WIN32_NO_STATUS\n#include <windef.h>\n#include <winbase.h>\n#include <winternl.h>\n#include <devioctl.h>\n#include <ntdddisk.h>\n#include <ntddscsi.h>\n#include <ntddstor.h>\n#include <ata.h>\n#include <mountmgr.h>\n#include <stringapiset.h>\n#include <stdbool.h>\n#include \"../btrfs.h\"\n#include \"../btrfsioctl.h\"\n#include \"../crc32c.h\"\n#include \"../zstd/lib/common/xxhash.h\"\n\n#if defined(_X86_) || defined(_AMD64_)\n#ifndef _MSC_VER\n#include <cpuid.h>\n#else\n#include <intrin.h>\n#endif\n#endif\n\n#define SHA256_HASH_SIZE 32\nvoid calc_sha256(uint8_t* hash, const void* input, size_t len);\n\n#define BLAKE2_HASH_SIZE 32\nvoid blake2b(void *out, size_t outlen, const void* in, size_t inlen);\n\n#define FSCTL_LOCK_VOLUME               CTL_CODE(FILE_DEVICE_FILE_SYSTEM,  6, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define FSCTL_UNLOCK_VOLUME             CTL_CODE(FILE_DEVICE_FILE_SYSTEM,  7, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define FSCTL_DISMOUNT_VOLUME           CTL_CODE(FILE_DEVICE_FILE_SYSTEM,  8, METHOD_BUFFERED, FILE_ANY_ACCESS)\n\n#ifndef _MSC_VER // not in mingw yet\n#define DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED 0x80000000\n#endif\n\n#ifdef __cplusplus\nextern \"C\" {\n#endif\nNTSTATUS NTAPI NtWriteFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer,\n                           ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key);\n\nNTSTATUS NTAPI NtReadFile(HANDLE FileHandle, HANDLE Event, PIO_APC_ROUTINE ApcRoutine, PVOID ApcContext, PIO_STATUS_BLOCK IoStatusBlock, PVOID Buffer,\n                          ULONG Length, PLARGE_INTEGER ByteOffset, PULONG Key);\n#ifdef __cplusplus\n}\n#endif\n\n// These are undocumented, and what comes from format.exe\ntypedef struct {\n    void* table;\n    void* unk1;\n    WCHAR* string;\n} DSTRING;\n\ntypedef struct {\n    void* table;\n} STREAM_MESSAGE;\n\n#define FORMAT_FLAG_QUICK_FORMAT        0x00000001\n#define FORMAT_FLAG_UNKNOWN1            0x00000002\n#define FORMAT_FLAG_DISMOUNT_FIRST      0x00000004\n#define FORMAT_FLAG_UNKNOWN2            0x00000040\n#define FORMAT_FLAG_LARGE_RECORDS       0x00000100\n#define FORMAT_FLAG_INTEGRITY_DISABLE   0x00000100\n\ntypedef struct {\n    uint16_t unk1;\n    uint16_t unk2;\n    uint32_t flags;\n    DSTRING* label;\n} options;\n\nFORCEINLINE VOID InitializeListHead(PLIST_ENTRY ListHead) {\n    ListHead->Flink = ListHead->Blink = ListHead;\n}\n\nFORCEINLINE VOID InsertTailList(PLIST_ENTRY ListHead, PLIST_ENTRY Entry) {\n    PLIST_ENTRY Blink;\n\n    Blink = ListHead->Blink;\n    Entry->Flink = ListHead;\n    Entry->Blink = Blink;\n    Blink->Flink = Entry;\n    ListHead->Blink = Entry;\n}\n\ntypedef struct {\n    KEY key;\n    uint16_t size;\n    void* data;\n    LIST_ENTRY list_entry;\n} btrfs_item;\n\ntypedef struct {\n    uint64_t address;\n    uint64_t size;\n    LIST_ENTRY list_entry;\n} used_space_extent;\n\ntypedef struct {\n    uint64_t offset;\n    CHUNK_ITEM* chunk_item;\n    uint64_t lastoff;\n    uint64_t used;\n    LIST_ENTRY used_space;\n    LIST_ENTRY list_entry;\n} btrfs_chunk;\n\ntypedef struct {\n    uint64_t id;\n    tree_header header;\n    btrfs_chunk* c;\n    LIST_ENTRY items;\n    LIST_ENTRY list_entry;\n} btrfs_root;\n\ntypedef struct {\n    DEV_ITEM dev_item;\n    uint64_t last_alloc;\n} btrfs_dev;\n\n#define keycmp(key1, key2)\\\n    ((key1.obj_id < key2.obj_id) ? -1 :\\\n    ((key1.obj_id > key2.obj_id) ? 1 :\\\n    ((key1.obj_type < key2.obj_type) ? -1 :\\\n    ((key1.obj_type > key2.obj_type) ? 1 :\\\n    ((key1.offset < key2.offset) ? -1 :\\\n    ((key1.offset > key2.offset) ? 1 :\\\n    0))))))\n\nHMODULE module;\nULONG def_sector_size = 0, def_node_size = 0;\nuint64_t def_incompat_flags = BTRFS_INCOMPAT_FLAGS_EXTENDED_IREF | BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA;\nuint64_t def_compat_ro_flags = 0;\nuint16_t def_csum_type = CSUM_TYPE_CRC32C;\n\n// the following definitions come from fmifs.h in ReactOS\n\ntypedef struct {\n    ULONG Lines;\n    PCHAR Output;\n} TEXTOUTPUT, *PTEXTOUTPUT;\n\ntypedef enum {\n    FMIFS_UNKNOWN0,\n    FMIFS_UNKNOWN1,\n    FMIFS_UNKNOWN2,\n    FMIFS_UNKNOWN3,\n    FMIFS_UNKNOWN4,\n    FMIFS_UNKNOWN5,\n    FMIFS_UNKNOWN6,\n    FMIFS_UNKNOWN7,\n    FMIFS_FLOPPY,\n    FMIFS_UNKNOWN9,\n    FMIFS_UNKNOWN10,\n    FMIFS_REMOVABLE,\n    FMIFS_HARDDISK,\n    FMIFS_UNKNOWN13,\n    FMIFS_UNKNOWN14,\n    FMIFS_UNKNOWN15,\n    FMIFS_UNKNOWN16,\n    FMIFS_UNKNOWN17,\n    FMIFS_UNKNOWN18,\n    FMIFS_UNKNOWN19,\n    FMIFS_UNKNOWN20,\n    FMIFS_UNKNOWN21,\n    FMIFS_UNKNOWN22,\n    FMIFS_UNKNOWN23,\n} FMIFS_MEDIA_FLAG;\n\ntypedef enum {\n    PROGRESS,\n    DONEWITHSTRUCTURE,\n    UNKNOWN2,\n    UNKNOWN3,\n    UNKNOWN4,\n    UNKNOWN5,\n    INSUFFICIENTRIGHTS,\n    FSNOTSUPPORTED,\n    VOLUMEINUSE,\n    UNKNOWN9,\n    UNKNOWNA,\n    DONE,\n    UNKNOWNC,\n    UNKNOWND,\n    OUTPUT,\n    STRUCTUREPROGRESS,\n    CLUSTERSIZETOOSMALL,\n} CALLBACKCOMMAND;\n\ntypedef BOOLEAN (NTAPI* PFMIFSCALLBACK)(CALLBACKCOMMAND Command, ULONG SubAction, PVOID ActionInfo);\n\nstatic bool IsListEmpty(LIST_ENTRY* head) {\n    return head->Flink == head;\n}\n\nstatic LIST_ENTRY* RemoveHeadList(LIST_ENTRY* head) {\n    LIST_ENTRY *flink, *entry;\n\n    entry = head->Flink;\n    flink = entry->Flink;\n    head->Flink = flink;\n    flink->Blink = head;\n\n    return entry;\n}\n\nNTSTATUS WINAPI ChkdskEx(PUNICODE_STRING DriveRoot, BOOLEAN FixErrors, BOOLEAN Verbose, BOOLEAN CheckOnlyIfDirty,\n                         BOOLEAN ScanDrive, PFMIFSCALLBACK Callback) {\n    // STUB\n\n    if (Callback) {\n        TEXTOUTPUT TextOut;\n\n        TextOut.Lines = 1;\n        TextOut.Output = \"stub, not implemented\";\n\n        Callback(OUTPUT, 0, &TextOut);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic btrfs_root* add_root(LIST_ENTRY* roots, uint64_t id) {\n    btrfs_root* root;\n\n    root = malloc(sizeof(btrfs_root));\n\n    root->id = id;\n    RtlZeroMemory(&root->header, sizeof(tree_header));\n    InitializeListHead(&root->items);\n    InsertTailList(roots, &root->list_entry);\n\n    return root;\n}\n\nstatic void free_roots(LIST_ENTRY* roots) {\n    LIST_ENTRY* le;\n\n    le = roots->Flink;\n    while (le != roots) {\n        LIST_ENTRY *le2 = le->Flink, *le3;\n        btrfs_root* r = CONTAINING_RECORD(le, btrfs_root, list_entry);\n\n        le3 = r->items.Flink;\n        while (le3 != &r->items) {\n            LIST_ENTRY* le4 = le3->Flink;\n            btrfs_item* item = CONTAINING_RECORD(le3, btrfs_item, list_entry);\n\n            if (item->data)\n                free(item->data);\n\n            free(item);\n\n            le3 = le4;\n        }\n\n        free(r);\n\n        le = le2;\n    }\n}\n\nstatic void free_chunks(LIST_ENTRY* chunks) {\n    LIST_ENTRY* le;\n\n    le = chunks->Flink;\n    while (le != chunks) {\n        LIST_ENTRY *le2 = le->Flink;\n        btrfs_chunk* c = CONTAINING_RECORD(le, btrfs_chunk, list_entry);\n\n        while (!IsListEmpty(&c->used_space)) {\n            used_space_extent* use = CONTAINING_RECORD(RemoveHeadList(&c->used_space), used_space_extent, list_entry);\n\n            free(use);\n        }\n\n        free(c->chunk_item);\n        free(c);\n\n        le = le2;\n    }\n}\n\nstatic void add_item(btrfs_root* r, uint64_t obj_id, uint8_t obj_type, uint64_t offset, void* data, uint16_t size) {\n    LIST_ENTRY* le;\n    btrfs_item* item;\n\n    item = malloc(sizeof(btrfs_item));\n\n    item->key.obj_id = obj_id;\n    item->key.obj_type = obj_type;\n    item->key.offset = offset;\n    item->size = size;\n\n    if (size == 0)\n        item->data = NULL;\n    else {\n        item->data = malloc(size);\n        memcpy(item->data, data, size);\n    }\n\n    le = r->items.Flink;\n    while (le != &r->items) {\n        btrfs_item* i2 = CONTAINING_RECORD(le, btrfs_item, list_entry);\n\n        if (keycmp(item->key, i2->key) != 1) {\n            InsertTailList(le, &item->list_entry);\n            return;\n        }\n\n        le = le->Flink;\n    }\n\n    InsertTailList(&r->items, &item->list_entry);\n}\n\nstatic uint64_t find_chunk_offset(uint64_t size, uint64_t offset, btrfs_dev* dev, btrfs_root* dev_root, BTRFS_UUID* chunkuuid) {\n    uint64_t off;\n    DEV_EXTENT de;\n\n    off = dev->last_alloc;\n    dev->last_alloc += size;\n\n    dev->dev_item.bytes_used += size;\n\n    de.chunktree = BTRFS_ROOT_CHUNK;\n    de.objid = 0x100;\n    de.address = offset;\n    de.length = size;\n    de.chunktree_uuid = *chunkuuid;\n\n    add_item(dev_root, dev->dev_item.dev_id, TYPE_DEV_EXTENT, off, &de, sizeof(DEV_EXTENT));\n\n    return off;\n}\n\nstatic btrfs_chunk* add_chunk(LIST_ENTRY* chunks, uint64_t flags, btrfs_root* chunk_root, btrfs_dev* dev, btrfs_root* dev_root, BTRFS_UUID* chunkuuid, uint32_t sector_size) {\n    uint64_t off, size;\n    uint16_t stripes, i;\n    btrfs_chunk* c;\n    LIST_ENTRY* le;\n    CHUNK_ITEM_STRIPE* cis;\n    uint64_t stripe_length = max(sector_size, 0x10000);\n\n    off = 0xc00000;\n    le = chunks->Flink;\n    while (le != chunks) {\n        c = CONTAINING_RECORD(le, btrfs_chunk, list_entry);\n\n        if (c->offset + c->chunk_item->size > off)\n            off = c->offset + c->chunk_item->size;\n\n        le = le->Flink;\n    }\n\n    if (flags & BLOCK_FLAG_METADATA) {\n        if (dev->dev_item.num_bytes > 0xC80000000) // 50 GB\n            size = 0x40000000; // 1 GB\n        else\n            size = 0x10000000; // 256 MB\n    } else // BLOCK_FLAG_SYSTEM\n        size = 0x800000;\n\n    size = min(size, dev->dev_item.num_bytes / 10); // cap at 10%\n    size &= ~(stripe_length - 1);\n\n    stripes = flags & BLOCK_FLAG_DUPLICATE ? 2 : 1;\n\n    if (dev->dev_item.num_bytes - dev->dev_item.bytes_used < stripes * size) // not enough space\n        return NULL;\n\n    c = malloc(sizeof(btrfs_chunk));\n    c->offset = off;\n    c->lastoff = off;\n    c->used = 0;\n    InitializeListHead(&c->used_space);\n\n    c->chunk_item = malloc(sizeof(CHUNK_ITEM) + (stripes * sizeof(CHUNK_ITEM_STRIPE)));\n\n    c->chunk_item->size = size;\n    c->chunk_item->root_id = BTRFS_ROOT_EXTENT;\n    c->chunk_item->stripe_length = stripe_length;\n    c->chunk_item->type = flags;\n    c->chunk_item->opt_io_alignment = stripe_length;\n    c->chunk_item->opt_io_width = stripe_length;\n    c->chunk_item->sector_size = sector_size;\n    c->chunk_item->num_stripes = stripes;\n    c->chunk_item->sub_stripes = 0;\n\n    cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n\n    for (i = 0; i < stripes; i++) {\n        cis[i].dev_id = dev->dev_item.dev_id;\n        cis[i].offset = find_chunk_offset(size, c->offset, dev, dev_root, chunkuuid);\n        cis[i].dev_uuid = dev->dev_item.device_uuid;\n    }\n\n    add_item(chunk_root, 0x100, TYPE_CHUNK_ITEM, c->offset, c->chunk_item, sizeof(CHUNK_ITEM) + (stripes * sizeof(CHUNK_ITEM_STRIPE)));\n\n    InsertTailList(chunks, &c->list_entry);\n\n    return c;\n}\n\nstatic bool superblock_collision(btrfs_chunk* c, uint64_t address) {\n    CHUNK_ITEM_STRIPE* cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n    uint64_t stripe = (address - c->offset) / c->chunk_item->stripe_length;\n    uint16_t i, j;\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        j = 0;\n        while (superblock_addrs[j] != 0) {\n            if (superblock_addrs[j] >= cis[i].offset) {\n                uint64_t stripe2 = (superblock_addrs[j] - cis[i].offset) / c->chunk_item->stripe_length;\n\n                if (stripe2 == stripe)\n                    return true;\n            }\n            j++;\n        }\n    }\n\n    return false;\n}\n\nstatic uint64_t get_next_address(btrfs_chunk* c) {\n    uint64_t addr;\n\n    addr = c->lastoff;\n\n    while (superblock_collision(c, addr)) {\n        addr = addr - ((addr - c->offset) % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n\n        if (addr >= c->offset + c->chunk_item->size) // chunk has been exhausted\n            return 0;\n    }\n\n    return addr;\n}\n\ntypedef struct {\n    EXTENT_ITEM ei;\n    uint8_t type;\n    TREE_BLOCK_REF tbr;\n} EXTENT_ITEM_METADATA;\n\ntypedef struct {\n    EXTENT_ITEM ei;\n    EXTENT_ITEM2 ei2;\n    uint8_t type;\n    TREE_BLOCK_REF tbr;\n} EXTENT_ITEM_METADATA2;\n\nstatic void assign_addresses(LIST_ENTRY* roots, btrfs_chunk* sys_chunk, btrfs_chunk* metadata_chunk, uint32_t node_size,\n                             btrfs_root* root_root, btrfs_root* extent_root, bool skinny) {\n    LIST_ENTRY* le;\n\n    le = roots->Flink;\n    while (le != roots) {\n        btrfs_root* r = CONTAINING_RECORD(le, btrfs_root, list_entry);\n        btrfs_chunk* c = r->id == BTRFS_ROOT_CHUNK ? sys_chunk : metadata_chunk;\n        bool done_use = false;\n\n        r->header.address = get_next_address(c);\n\n        if (!IsListEmpty(&c->used_space)) {\n            used_space_extent* use = CONTAINING_RECORD(c->used_space.Blink, used_space_extent, list_entry);\n\n            if (use->address + use->size == r->header.address) {\n                use->size += node_size;\n                done_use = true;\n            }\n        }\n\n        if (!done_use) {\n            used_space_extent* use = malloc(sizeof(used_space_extent));\n            use->address = r->header.address;\n            use->size = node_size;\n            InsertTailList(&c->used_space, &use->list_entry);\n        }\n\n        r->c = c;\n        c->lastoff = r->header.address + node_size;\n        c->used += node_size;\n\n        if (skinny) {\n            EXTENT_ITEM_METADATA eim;\n\n            eim.ei.refcount = 1;\n            eim.ei.generation = 1;\n            eim.ei.flags = EXTENT_ITEM_TREE_BLOCK;\n            eim.type = TYPE_TREE_BLOCK_REF;\n            eim.tbr.offset = r->id;\n\n            add_item(extent_root, r->header.address, TYPE_METADATA_ITEM, 0, &eim, sizeof(EXTENT_ITEM_METADATA));\n        } else {\n            EXTENT_ITEM_METADATA2 eim2;\n            KEY firstitem;\n\n            if (r->items.Flink == &r->items) {\n                firstitem.obj_id = 0;\n                firstitem.obj_type = 0;\n                firstitem.offset = 0;\n            } else {\n                btrfs_item* bi = CONTAINING_RECORD(r->items.Flink, btrfs_item, list_entry);\n\n                firstitem = bi->key;\n            }\n\n            eim2.ei.refcount = 1;\n            eim2.ei.generation = 1;\n            eim2.ei.flags = EXTENT_ITEM_TREE_BLOCK;\n            eim2.ei2.firstitem = firstitem;\n            eim2.ei2.level = 0;\n            eim2.type = TYPE_TREE_BLOCK_REF;\n            eim2.tbr.offset = r->id;\n\n            add_item(extent_root, r->header.address, TYPE_EXTENT_ITEM, node_size, &eim2, sizeof(EXTENT_ITEM_METADATA2));\n        }\n\n        if (r->id != BTRFS_ROOT_ROOT && r->id != BTRFS_ROOT_CHUNK) {\n            ROOT_ITEM ri;\n\n            memset(&ri, 0, sizeof(ROOT_ITEM));\n\n            ri.inode.generation = 1;\n            ri.inode.st_size = 3;\n            ri.inode.st_blocks = node_size;\n            ri.inode.st_nlink = 1;\n            ri.inode.st_mode = 040755;\n            ri.generation = 1;\n            ri.objid = r->id == 5 || r->id >= 0x100 ? SUBVOL_ROOT_INODE : 0;\n            ri.block_number = r->header.address;\n            ri.bytes_used = node_size;\n            ri.num_references = 1;\n            ri.generation2 = ri.generation;\n\n            add_item(root_root, r->id, TYPE_ROOT_ITEM, 0, &ri, sizeof(ROOT_ITEM));\n        }\n\n        le = le->Flink;\n    }\n}\n\nstatic NTSTATUS write_data(HANDLE h, uint64_t address, btrfs_chunk* c, void* data, ULONG size) {\n    NTSTATUS Status;\n    uint16_t i;\n    IO_STATUS_BLOCK iosb;\n    LARGE_INTEGER off;\n    CHUNK_ITEM_STRIPE* cis;\n\n    cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        off.QuadPart = cis[i].offset + address - c->offset;\n\n        Status = NtWriteFile(h, NULL, NULL, NULL, &iosb, data, size, &off, NULL);\n        if (!NT_SUCCESS(Status))\n            return Status;\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic void calc_tree_checksum(tree_header* th, uint32_t node_size) {\n    switch (def_csum_type) {\n        case CSUM_TYPE_CRC32C:\n            *(uint32_t*)th = ~calc_crc32c(0xffffffff, (uint8_t*)&th->fs_uuid, node_size - sizeof(th->csum));\n        break;\n\n        case CSUM_TYPE_XXHASH:\n            *(uint64_t*)th = XXH64((uint8_t*)&th->fs_uuid, node_size - sizeof(th->csum), 0);\n        break;\n\n        case CSUM_TYPE_SHA256:\n            calc_sha256((uint8_t*)th, &th->fs_uuid, node_size - sizeof(th->csum));\n        break;\n\n        case CSUM_TYPE_BLAKE2:\n            blake2b((uint8_t*)th, BLAKE2_HASH_SIZE, &th->fs_uuid, node_size - sizeof(th->csum));\n        break;\n    }\n}\n\nstatic NTSTATUS write_roots(HANDLE h, LIST_ENTRY* roots, uint32_t node_size, BTRFS_UUID* fsuuid, BTRFS_UUID* chunkuuid) {\n    LIST_ENTRY *le, *le2;\n    NTSTATUS Status;\n    uint8_t* tree;\n\n    tree = malloc(node_size);\n\n    le = roots->Flink;\n    while (le != roots) {\n        btrfs_root* r = CONTAINING_RECORD(le, btrfs_root, list_entry);\n        uint8_t* dp;\n        leaf_node* ln;\n\n        memset(tree, 0, node_size);\n\n        r->header.num_items = 0;\n        r->header.fs_uuid = *fsuuid;\n        r->header.flags = HEADER_FLAG_MIXED_BACKREF | HEADER_FLAG_WRITTEN;\n        r->header.chunk_tree_uuid = *chunkuuid;\n        r->header.generation = 1;\n        r->header.tree_id = r->id;\n\n        ln = (leaf_node*)(tree + sizeof(tree_header));\n\n        dp = tree + node_size;\n\n        le2 = r->items.Flink;\n        while (le2 != &r->items) {\n            btrfs_item* item = CONTAINING_RECORD(le2, btrfs_item, list_entry);\n\n            ln->key = item->key;\n            ln->size = item->size;\n\n            if (item->size > 0) {\n                dp -= item->size;\n                memcpy(dp, item->data, item->size);\n            }\n\n            ln->offset = (uint32_t)(dp - tree - sizeof(tree_header));\n\n            ln = &ln[1];\n\n            r->header.num_items++;\n\n            le2 = le2->Flink;\n        }\n\n        memcpy(tree, &r->header, sizeof(tree_header));\n\n        calc_tree_checksum((tree_header*)tree, node_size);\n\n        Status = write_data(h, r->header.address, r->c, tree, node_size);\n        if (!NT_SUCCESS(Status)) {\n            free(tree);\n            return Status;\n        }\n\n        le = le->Flink;\n    }\n\n    free(tree);\n\n    return STATUS_SUCCESS;\n}\n\nstatic void get_uuid(BTRFS_UUID* uuid) {\n    uint8_t i;\n\n    for (i = 0; i < 16; i+=2) {\n        ULONG r = rand();\n\n        uuid->uuid[i] = (r & 0xff00) >> 8;\n        uuid->uuid[i+1] = r & 0xff;\n    }\n}\n\nstatic void init_device(btrfs_dev* dev, uint64_t id, uint64_t size, BTRFS_UUID* fsuuid, uint32_t sector_size) {\n    dev->dev_item.dev_id = id;\n    dev->dev_item.num_bytes = size;\n    dev->dev_item.bytes_used = 0;\n    dev->dev_item.optimal_io_align = sector_size;\n    dev->dev_item.optimal_io_width = sector_size;\n    dev->dev_item.minimal_io_size = sector_size;\n    dev->dev_item.type = 0;\n    dev->dev_item.generation = 0;\n    dev->dev_item.start_offset = 0;\n    dev->dev_item.dev_group = 0;\n    dev->dev_item.seek_speed = 0;\n    dev->dev_item.bandwidth = 0;\n    get_uuid(&dev->dev_item.device_uuid);\n    dev->dev_item.fs_uuid = *fsuuid;\n\n    dev->last_alloc = 0x100000; // skip first megabyte\n}\n\nstatic void calc_superblock_checksum(superblock* sb) {\n    switch (def_csum_type) {\n        case CSUM_TYPE_CRC32C:\n            *(uint32_t*)sb = ~calc_crc32c(0xffffffff, (uint8_t*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum));\n        break;\n\n        case CSUM_TYPE_XXHASH:\n            *(uint64_t*)sb = XXH64(&sb->uuid, sizeof(superblock) - sizeof(sb->checksum), 0);\n        break;\n\n        case CSUM_TYPE_SHA256:\n            calc_sha256((uint8_t*)sb, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum));\n        break;\n\n        case CSUM_TYPE_BLAKE2:\n            blake2b((uint8_t*)sb, BLAKE2_HASH_SIZE, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum));\n        break;\n    }\n}\n\nstatic NTSTATUS write_superblocks(HANDLE h, btrfs_dev* dev, btrfs_root* chunk_root, btrfs_root* root_root, btrfs_root* extent_root,\n                                  btrfs_chunk* sys_chunk, uint32_t node_size, BTRFS_UUID* fsuuid, uint32_t sector_size, PUNICODE_STRING label,\n                                  uint64_t incompat_flags, uint64_t compat_ro_flags) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    ULONG sblen;\n    int i;\n    superblock* sb;\n    KEY* key;\n    uint64_t bytes_used;\n    LIST_ENTRY* le;\n\n    sblen = sizeof(*sb);\n    if (sblen & (sector_size - 1))\n        sblen = (sblen & sector_size) + sector_size;\n\n    bytes_used = 0;\n\n    le = extent_root->items.Flink;\n    while (le != &extent_root->items) {\n        btrfs_item* item = CONTAINING_RECORD(le, btrfs_item, list_entry);\n\n        if (item->key.obj_type == TYPE_EXTENT_ITEM)\n            bytes_used += item->key.offset;\n        else if (item->key.obj_type == TYPE_METADATA_ITEM)\n            bytes_used += node_size;\n\n        le = le->Flink;\n    }\n\n    sb = malloc(sblen);\n    memset(sb, 0, sblen);\n\n    sb->uuid = *fsuuid;\n    sb->flags = 1;\n    sb->magic = BTRFS_MAGIC;\n    sb->generation = 1;\n    sb->root_tree_addr = root_root->header.address;\n    sb->chunk_tree_addr = chunk_root->header.address;\n    sb->total_bytes = dev->dev_item.num_bytes;\n    sb->bytes_used = bytes_used;\n    sb->root_dir_objectid = BTRFS_ROOT_TREEDIR;\n    sb->num_devices = 1;\n    sb->sector_size = sector_size;\n    sb->node_size = node_size;\n    sb->leaf_size = node_size;\n    sb->stripe_size = sector_size;\n    sb->n = sizeof(KEY) + sizeof(CHUNK_ITEM) + (sys_chunk->chunk_item->num_stripes * sizeof(CHUNK_ITEM_STRIPE));\n    sb->chunk_root_generation = 1;\n    sb->compat_ro_flags = compat_ro_flags;\n    sb->incompat_flags = incompat_flags;\n    sb->csum_type = def_csum_type;\n    memcpy(&sb->dev_item, &dev->dev_item, sizeof(DEV_ITEM));\n\n    if (label->Length > 0) {\n        ULONG utf8len;\n\n        for (unsigned int i = 0; i < label->Length / sizeof(WCHAR); i++) {\n            if (label->Buffer[i] == '/' || label->Buffer[i] == '\\\\') {\n                free(sb);\n                return STATUS_INVALID_VOLUME_LABEL;\n            }\n        }\n\n        utf8len = WideCharToMultiByte(CP_UTF8, 0, label->Buffer, label->Length / sizeof(WCHAR), NULL, 0, NULL, NULL);\n\n        if (utf8len == 0 || utf8len > MAX_LABEL_SIZE) {\n            free(sb);\n            return STATUS_INVALID_VOLUME_LABEL;\n        }\n\n        if (WideCharToMultiByte(CP_UTF8, 0, label->Buffer, label->Length / sizeof(WCHAR), sb->label, utf8len, NULL, NULL) == 0) {\n            free(sb);\n            return STATUS_INVALID_VOLUME_LABEL;\n        }\n    }\n    sb->cache_generation = 0xffffffffffffffff;\n\n    key = (KEY*)sb->sys_chunk_array;\n    key->obj_id = 0x100;\n    key->obj_type = TYPE_CHUNK_ITEM;\n    key->offset = sys_chunk->offset;\n    memcpy(&key[1], sys_chunk->chunk_item, sizeof(CHUNK_ITEM) + (sys_chunk->chunk_item->num_stripes * sizeof(CHUNK_ITEM_STRIPE)));\n\n    i = 0;\n    while (superblock_addrs[i] != 0) {\n        LARGE_INTEGER off;\n\n        if (superblock_addrs[i] > dev->dev_item.num_bytes)\n            break;\n\n        sb->sb_phys_addr = superblock_addrs[i];\n\n        calc_superblock_checksum(sb);\n\n        off.QuadPart = superblock_addrs[i];\n\n        Status = NtWriteFile(h, NULL, NULL, NULL, &iosb, sb, sblen, &off, NULL);\n        if (!NT_SUCCESS(Status)) {\n            free(sb);\n            return Status;\n        }\n\n        i++;\n    }\n\n    free(sb);\n\n    return STATUS_SUCCESS;\n}\n\nstatic __inline void win_time_to_unix(LARGE_INTEGER t, BTRFS_TIME* out) {\n    ULONGLONG l = t.QuadPart - 116444736000000000;\n\n    out->seconds = l / 10000000;\n    out->nanoseconds = (l % 10000000) * 100;\n}\n\nstatic void add_inode_ref(btrfs_root* r, uint64_t inode, uint64_t parent, uint64_t index, const char* name) {\n    uint16_t name_len = (uint16_t)strlen(name);\n    INODE_REF* ir = malloc(offsetof(INODE_REF, name[0]) + name_len);\n\n    ir->index = 0;\n    ir->n = name_len;\n    memcpy(ir->name, name, name_len);\n\n    add_item(r, inode, TYPE_INODE_REF, parent, ir, (uint16_t)offsetof(INODE_REF, name[0]) + ir->n);\n\n    free(ir);\n}\n\nstatic void init_fs_tree(btrfs_root* r, uint32_t node_size) {\n    INODE_ITEM ii;\n    FILETIME filetime;\n    LARGE_INTEGER time;\n\n    memset(&ii, 0, sizeof(INODE_ITEM));\n\n    ii.generation = 1;\n    ii.st_blocks = node_size;\n    ii.st_nlink = 1;\n    ii.st_mode = 040755;\n\n    GetSystemTimeAsFileTime(&filetime);\n    time.LowPart = filetime.dwLowDateTime;\n    time.HighPart = filetime.dwHighDateTime;\n\n    win_time_to_unix(time, &ii.st_atime);\n    ii.st_ctime = ii.st_mtime = ii.st_atime;\n\n    add_item(r, SUBVOL_ROOT_INODE, TYPE_INODE_ITEM, 0, &ii, sizeof(INODE_ITEM));\n\n    add_inode_ref(r, SUBVOL_ROOT_INODE, SUBVOL_ROOT_INODE, 0, \"..\");\n}\n\nstatic void add_block_group_items(LIST_ENTRY* chunks, btrfs_root* root) {\n    LIST_ENTRY* le;\n\n    le = chunks->Flink;\n    while (le != chunks) {\n        btrfs_chunk* c = CONTAINING_RECORD(le, btrfs_chunk, list_entry);\n        BLOCK_GROUP_ITEM bgi;\n\n        bgi.used = c->used;\n        bgi.chunk_tree = 0x100;\n        bgi.flags = c->chunk_item->type;\n        add_item(root, c->offset, TYPE_BLOCK_GROUP_ITEM, c->chunk_item->size, &bgi, sizeof(BLOCK_GROUP_ITEM));\n\n        le = le->Flink;\n    }\n}\n\nstatic NTSTATUS clear_first_megabyte(HANDLE h) {\n    NTSTATUS Status;\n    IO_STATUS_BLOCK iosb;\n    LARGE_INTEGER zero;\n    uint8_t* mb;\n\n    mb = malloc(0x100000);\n    memset(mb, 0, 0x100000);\n\n    zero.QuadPart = 0;\n\n    Status = NtWriteFile(h, NULL, NULL, NULL, &iosb, mb, 0x100000, &zero, NULL);\n\n    free(mb);\n\n    return Status;\n}\n\nstatic bool is_ssd(HANDLE h) {\n    ULONG aptelen;\n    ATA_PASS_THROUGH_EX* apte;\n    IO_STATUS_BLOCK iosb;\n    NTSTATUS Status;\n    IDENTIFY_DEVICE_DATA* idd;\n\n    aptelen = sizeof(ATA_PASS_THROUGH_EX) + 512;\n    apte = malloc(aptelen);\n\n    RtlZeroMemory(apte, aptelen);\n\n    apte->Length = sizeof(ATA_PASS_THROUGH_EX);\n    apte->AtaFlags = ATA_FLAGS_DATA_IN;\n    apte->DataTransferLength = aptelen - sizeof(ATA_PASS_THROUGH_EX);\n    apte->TimeOutValue = 3;\n    apte->DataBufferOffset = apte->Length;\n    apte->CurrentTaskFile[6] = IDE_COMMAND_IDENTIFY;\n\n    Status = NtDeviceIoControlFile(h, NULL, NULL, NULL, &iosb, IOCTL_ATA_PASS_THROUGH, apte, aptelen, apte, aptelen);\n\n    if (NT_SUCCESS(Status)) {\n        idd = (IDENTIFY_DEVICE_DATA*)((uint8_t*)apte + sizeof(ATA_PASS_THROUGH_EX));\n\n        if (idd->NominalMediaRotationRate == 1) {\n            free(apte);\n            return true;\n        }\n    }\n\n    free(apte);\n\n    return false;\n}\n\nstatic void add_dir_item(btrfs_root* root, uint64_t inode, uint32_t hash, uint64_t key_objid, uint8_t key_type,\n                         uint64_t key_offset, uint64_t transid, uint8_t type, const char* name) {\n    uint16_t name_len = (uint16_t)strlen(name);\n    DIR_ITEM* di = malloc(offsetof(DIR_ITEM, name[0]) + name_len);\n\n    di->key.obj_id = key_objid;\n    di->key.obj_type = key_type;\n    di->key.offset = key_offset;\n    di->transid = transid;\n    di->m = 0;\n    di->n = name_len;\n    di->type = type;\n    memcpy(di->name, name, name_len);\n\n    add_item(root, inode, TYPE_DIR_ITEM, hash, di, (uint16_t)(offsetof(DIR_ITEM, name[0]) + di->m + di->n));\n\n    free(di);\n}\n\nstatic void set_default_subvol(btrfs_root* root_root, uint32_t node_size) {\n    INODE_ITEM ii;\n    FILETIME filetime;\n    LARGE_INTEGER time;\n\n    static const char default_subvol[] = \"default\";\n    static const uint32_t default_hash = 0x8dbfc2d2;\n\n    add_inode_ref(root_root, BTRFS_ROOT_FSTREE, BTRFS_ROOT_TREEDIR, 0, default_subvol);\n\n    memset(&ii, 0, sizeof(INODE_ITEM));\n\n    ii.generation = 1;\n    ii.st_blocks = node_size;\n    ii.st_nlink = 1;\n    ii.st_mode = 040755;\n\n    GetSystemTimeAsFileTime(&filetime);\n    time.LowPart = filetime.dwLowDateTime;\n    time.HighPart = filetime.dwHighDateTime;\n\n    win_time_to_unix(time, &ii.st_atime);\n    ii.st_ctime = ii.st_mtime = ii.otime = ii.st_atime;\n\n    add_item(root_root, BTRFS_ROOT_TREEDIR, TYPE_INODE_ITEM, 0, &ii, sizeof(INODE_ITEM));\n\n    add_inode_ref(root_root, BTRFS_ROOT_TREEDIR, BTRFS_ROOT_TREEDIR, 0, \"..\");\n\n    add_dir_item(root_root, BTRFS_ROOT_TREEDIR, default_hash, BTRFS_ROOT_FSTREE, TYPE_ROOT_ITEM,\n                 0xffffffffffffffff, 0, BTRFS_TYPE_DIRECTORY, default_subvol);\n}\n\nstatic void populate_free_space_root(LIST_ENTRY* chunks, btrfs_root* free_space_root) {\n    LIST_ENTRY* le;\n\n    le = chunks->Flink;\n    while (le != chunks) {\n        btrfs_chunk* c = CONTAINING_RECORD(le, btrfs_chunk, list_entry);\n        uint64_t last_end = c->offset;\n        LIST_ENTRY* le2;\n        FREE_SPACE_INFO fsi;\n\n        fsi.count = 0;\n        fsi.flags = 0;\n\n        le2 = c->used_space.Flink;\n        while (le2 != &c->used_space) {\n            used_space_extent* use = CONTAINING_RECORD(le2, used_space_extent, list_entry);\n\n            if (use->address > last_end) {\n                add_item(free_space_root, last_end, TYPE_FREE_SPACE_EXTENT, use->address - last_end,\n                         NULL, 0);\n                fsi.count++;\n            }\n\n            last_end = use->address + use->size;\n\n            le2 = le2->Flink;\n        }\n\n        if (c->offset + c->chunk_item->size > last_end) {\n            add_item(free_space_root, last_end, TYPE_FREE_SPACE_EXTENT, c->offset + c->chunk_item->size - last_end,\n                     NULL, 0);\n            fsi.count++;\n        }\n\n        add_item(free_space_root, c->offset, TYPE_FREE_SPACE_INFO, c->chunk_item->size, &fsi, sizeof(fsi));\n\n        le = le->Flink;\n    }\n}\n\nstatic NTSTATUS write_btrfs(HANDLE h, uint64_t size, PUNICODE_STRING label, uint32_t sector_size, uint32_t node_size, uint64_t incompat_flags,\n                            uint64_t compat_ro_flags) {\n    NTSTATUS Status;\n    LIST_ENTRY roots, chunks;\n    btrfs_root *root_root, *chunk_root, *extent_root, *dev_root, *fs_root, *reloc_root,\n               *block_group_root, *free_space_root;\n    btrfs_chunk *sys_chunk, *metadata_chunk;\n    btrfs_dev dev;\n    BTRFS_UUID fsuuid, chunkuuid;\n    bool ssd;\n    uint64_t metadata_flags;\n\n    srand((unsigned int)time(0));\n    get_uuid(&fsuuid);\n    get_uuid(&chunkuuid);\n\n    InitializeListHead(&roots);\n    InitializeListHead(&chunks);\n\n    root_root = add_root(&roots, BTRFS_ROOT_ROOT);\n    chunk_root = add_root(&roots, BTRFS_ROOT_CHUNK);\n    extent_root = add_root(&roots, BTRFS_ROOT_EXTENT);\n    dev_root = add_root(&roots, BTRFS_ROOT_DEVTREE);\n    add_root(&roots, BTRFS_ROOT_CHECKSUM);\n    fs_root = add_root(&roots, BTRFS_ROOT_FSTREE);\n    reloc_root = add_root(&roots, BTRFS_ROOT_DATA_RELOC);\n\n    if (compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_FREE_SPACE_CACHE)\n        free_space_root = add_root(&roots, BTRFS_ROOT_FREE_SPACE);\n    else\n        free_space_root = NULL;\n\n    if (compat_ro_flags & BTRFS_COMPAT_RO_FLAGS_BLOCK_GROUP_TREE)\n        block_group_root = add_root(&roots, BTRFS_ROOT_BLOCK_GROUP);\n    else\n        block_group_root = NULL;\n\n    init_device(&dev, 1, size, &fsuuid, sector_size);\n\n    ssd = is_ssd(h);\n\n    sys_chunk = add_chunk(&chunks, BLOCK_FLAG_SYSTEM | (ssd ? 0 : BLOCK_FLAG_DUPLICATE), chunk_root, &dev, dev_root, &chunkuuid, sector_size);\n    if (!sys_chunk)\n        return STATUS_INTERNAL_ERROR;\n\n    metadata_flags = BLOCK_FLAG_METADATA;\n\n    if (!ssd && !(incompat_flags & BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS))\n        metadata_flags |= BLOCK_FLAG_DUPLICATE;\n\n    if (incompat_flags & BTRFS_INCOMPAT_FLAGS_MIXED_GROUPS)\n        metadata_flags |= BLOCK_FLAG_DATA;\n\n    metadata_chunk = add_chunk(&chunks, metadata_flags, chunk_root, &dev, dev_root, &chunkuuid, sector_size);\n    if (!metadata_chunk)\n        return STATUS_INTERNAL_ERROR;\n\n    add_item(chunk_root, 1, TYPE_DEV_ITEM, dev.dev_item.dev_id, &dev.dev_item, sizeof(DEV_ITEM));\n\n    set_default_subvol(root_root, node_size);\n\n    init_fs_tree(fs_root, node_size);\n    init_fs_tree(reloc_root, node_size);\n\n    assign_addresses(&roots, sys_chunk, metadata_chunk, node_size, root_root, extent_root, incompat_flags & BTRFS_INCOMPAT_FLAGS_SKINNY_METADATA);\n\n    add_block_group_items(&chunks, block_group_root ? block_group_root : extent_root);\n\n    if (free_space_root)\n        populate_free_space_root(&chunks, free_space_root);\n\n    Status = write_roots(h, &roots, node_size, &fsuuid, &chunkuuid);\n    if (!NT_SUCCESS(Status))\n        return Status;\n\n    Status = clear_first_megabyte(h);\n    if (!NT_SUCCESS(Status))\n        return Status;\n\n    Status = write_superblocks(h, &dev, chunk_root, root_root, extent_root, sys_chunk, node_size, &fsuuid, sector_size, label,\n                               incompat_flags, compat_ro_flags);\n    if (!NT_SUCCESS(Status))\n        return Status;\n\n    free_roots(&roots);\n    free_chunks(&chunks);\n\n    return STATUS_SUCCESS;\n}\n\nstatic bool look_for_device(btrfs_filesystem* bfs, BTRFS_UUID* devuuid) {\n    uint32_t i;\n    btrfs_filesystem_device* dev;\n\n    for (i = 0; i < bfs->num_devices; i++) {\n        if (i == 0)\n            dev = &bfs->device;\n        else\n            dev = (btrfs_filesystem_device*)((uint8_t*)dev + offsetof(btrfs_filesystem_device, name[0]) + dev->name_length);\n\n        if (RtlCompareMemory(&dev->uuid, devuuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID))\n            return true;\n    }\n\n    return false;\n}\n\nstatic bool check_superblock_checksum(superblock* sb) {\n    switch (sb->csum_type) {\n        case CSUM_TYPE_CRC32C: {\n            uint32_t crc32 = ~calc_crc32c(0xffffffff, (uint8_t*)&sb->uuid, (ULONG)sizeof(superblock) - sizeof(sb->checksum));\n\n            return crc32 == *(uint32_t*)sb;\n        }\n\n        case CSUM_TYPE_XXHASH: {\n            uint64_t hash = XXH64(&sb->uuid, sizeof(superblock) - sizeof(sb->checksum), 0);\n\n            return hash == *(uint64_t*)sb;\n        }\n\n        case CSUM_TYPE_SHA256: {\n            uint8_t hash[SHA256_HASH_SIZE];\n\n            calc_sha256(hash, &sb->uuid, sizeof(superblock) - sizeof(sb->checksum));\n\n            return !memcmp(hash, sb, SHA256_HASH_SIZE);\n        }\n\n        case CSUM_TYPE_BLAKE2: {\n            uint8_t hash[BLAKE2_HASH_SIZE];\n\n            blake2b(hash, sizeof(hash), &sb->uuid, sizeof(superblock) - sizeof(sb->checksum));\n\n            return !memcmp(hash, sb, BLAKE2_HASH_SIZE);\n        }\n\n        default:\n            return false;\n    }\n}\n\nstatic bool is_mounted_multi_device(HANDLE h, uint32_t sector_size) {\n    NTSTATUS Status;\n    superblock* sb;\n    ULONG sblen;\n    IO_STATUS_BLOCK iosb;\n    LARGE_INTEGER off;\n    BTRFS_UUID fsuuid, devuuid;\n    UNICODE_STRING us;\n    OBJECT_ATTRIBUTES atts;\n    HANDLE h2;\n    btrfs_filesystem *bfs = NULL, *bfs2;\n    ULONG bfssize;\n    bool ret = false;\n\n    static WCHAR btrfs[] = L\"\\\\Btrfs\";\n\n    sblen = sizeof(*sb);\n    if (sblen & (sector_size - 1))\n        sblen = (sblen & sector_size) + sector_size;\n\n    sb = malloc(sblen);\n\n    off.QuadPart = superblock_addrs[0];\n\n    Status = NtReadFile(h, NULL, NULL, NULL, &iosb, sb, sblen, &off, NULL);\n    if (!NT_SUCCESS(Status)) {\n        free(sb);\n        return false;\n    }\n\n    if (sb->magic != BTRFS_MAGIC) {\n        free(sb);\n        return false;\n    }\n\n    if (!check_superblock_checksum(sb)) {\n        free(sb);\n        return false;\n    }\n\n    fsuuid = sb->uuid;\n    devuuid = sb->dev_item.device_uuid;\n\n    free(sb);\n\n    us.Length = us.MaximumLength = (USHORT)(wcslen(btrfs) * sizeof(WCHAR));\n    us.Buffer = btrfs;\n\n    InitializeObjectAttributes(&atts, &us, 0, NULL, NULL);\n\n    Status = NtOpenFile(&h2, SYNCHRONIZE | FILE_READ_ATTRIBUTES, &atts, &iosb,\n                        FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SYNCHRONOUS_IO_ALERT);\n    if (!NT_SUCCESS(Status)) // not a problem, it usually just means the driver isn't loaded\n        return false;\n\n    bfssize = 0;\n\n    do {\n        bfssize += 1024;\n\n        if (bfs) free(bfs);\n        bfs = malloc(bfssize);\n\n        Status = NtDeviceIoControlFile(h2, NULL, NULL, NULL, &iosb, IOCTL_BTRFS_QUERY_FILESYSTEMS, NULL, 0, bfs, bfssize);\n    } while (Status == STATUS_BUFFER_OVERFLOW);\n\n    if (!NT_SUCCESS(Status))\n        goto end;\n\n    if (bfs->num_devices != 0) {\n        bfs2 = bfs;\n        while (true) {\n            if (RtlCompareMemory(&bfs2->uuid, &fsuuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n                if (bfs2->num_devices == 1)\n                    ret = false;\n                else\n                    ret = look_for_device(bfs2, &devuuid);\n\n                goto end;\n            }\n\n            if (bfs2->next_entry == 0)\n                break;\n            else\n                bfs2 = (btrfs_filesystem*)((uint8_t*)bfs2 + bfs2->next_entry);\n        }\n    }\n\nend:\n    NtClose(h2);\n\n    if (bfs)\n        free(bfs);\n\n    return ret;\n}\n\nstatic void do_full_trim(HANDLE h) {\n    IO_STATUS_BLOCK iosb;\n    DEVICE_MANAGE_DATA_SET_ATTRIBUTES dmdsa;\n\n    RtlZeroMemory(&dmdsa, sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES));\n\n    dmdsa.Size = sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES);\n    dmdsa.Action = DeviceDsmAction_Trim;\n    dmdsa.Flags = DEVICE_DSM_FLAG_ENTIRE_DATA_SET_RANGE | DEVICE_DSM_FLAG_TRIM_NOT_FS_ALLOCATED;\n    dmdsa.ParameterBlockOffset = 0;\n    dmdsa.ParameterBlockLength = 0;\n    dmdsa.DataSetRangesOffset = 0;\n    dmdsa.DataSetRangesLength = 0;\n\n    NtDeviceIoControlFile(h, NULL, NULL, NULL, &iosb, IOCTL_STORAGE_MANAGE_DATA_SET_ATTRIBUTES, &dmdsa, sizeof(DEVICE_MANAGE_DATA_SET_ATTRIBUTES), NULL, 0);\n}\n\nstatic bool is_power_of_two(ULONG i) {\n    return ((i != 0) && !(i & (i - 1)));\n}\n\n#if defined(_X86_) || defined(_AMD64_)\nstatic void check_cpu() {\n    unsigned int cpuInfo[4];\n    bool have_sse42;\n\n#ifndef _MSC_VER\n    __get_cpuid(1, &cpuInfo[0], &cpuInfo[1], &cpuInfo[2], &cpuInfo[3]);\n    have_sse42 = cpuInfo[2] & bit_SSE4_2;\n#else\n    __cpuid(cpuInfo, 1);\n    have_sse42 = cpuInfo[2] & (1 << 20);\n#endif\n\n    if (have_sse42)\n        calc_crc32c = calc_crc32c_hw;\n}\n#endif\n\nstatic NTSTATUS NTAPI FormatEx2(PUNICODE_STRING DriveRoot, FMIFS_MEDIA_FLAG MediaFlag, PUNICODE_STRING Label,\n                                BOOLEAN QuickFormat, ULONG ClusterSize, PFMIFSCALLBACK Callback)\n{\n    NTSTATUS Status;\n    HANDLE h, btrfsh;\n    OBJECT_ATTRIBUTES attr;\n    IO_STATUS_BLOCK iosb;\n    GET_LENGTH_INFORMATION gli;\n    DISK_GEOMETRY dg;\n    uint32_t sector_size, node_size;\n    UNICODE_STRING btrfsus;\n    HANDLE token;\n    TOKEN_PRIVILEGES tp;\n    LUID luid;\n    uint64_t incompat_flags, compat_ro_flags;\n    UNICODE_STRING empty_label;\n\n    static WCHAR btrfs[] = L\"\\\\Btrfs\";\n\n    if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token))\n        return STATUS_PRIVILEGE_NOT_HELD;\n\n    if (!LookupPrivilegeValueW(NULL, L\"SeManageVolumePrivilege\", &luid)) {\n        CloseHandle(token);\n        return STATUS_PRIVILEGE_NOT_HELD;\n    }\n\n    tp.PrivilegeCount = 1;\n    tp.Privileges[0].Luid = luid;\n    tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;\n\n    if (!AdjustTokenPrivileges(token, false, &tp, sizeof(TOKEN_PRIVILEGES), NULL, NULL)) {\n        CloseHandle(token);\n        return STATUS_PRIVILEGE_NOT_HELD;\n    }\n\n    CloseHandle(token);\n\n#if defined(_X86_) || defined(_AMD64_)\n    check_cpu();\n#endif\n\n    if (def_csum_type != CSUM_TYPE_CRC32C && def_csum_type != CSUM_TYPE_XXHASH && def_csum_type != CSUM_TYPE_SHA256 &&\n        def_csum_type != CSUM_TYPE_BLAKE2)\n        return STATUS_INVALID_PARAMETER;\n\n    InitializeObjectAttributes(&attr, DriveRoot, OBJ_CASE_INSENSITIVE, NULL, NULL);\n\n    Status = NtOpenFile(&h, FILE_GENERIC_READ | FILE_GENERIC_WRITE, &attr, &iosb,\n                        FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_ALERT);\n\n    if (!NT_SUCCESS(Status))\n        return Status;\n\n    Status = NtDeviceIoControlFile(h, NULL, NULL, NULL, &iosb, IOCTL_DISK_GET_LENGTH_INFO, NULL, 0, &gli, sizeof(gli));\n    if (!NT_SUCCESS(Status)) {\n        NtClose(h);\n        return Status;\n    }\n\n    // MSDN tells us to use IOCTL_DISK_GET_DRIVE_GEOMETRY_EX, but there are\n    // some instances where it fails and IOCTL_DISK_GET_DRIVE_GEOMETRY succeeds -\n    // such as with spanned volumes.\n    Status = NtDeviceIoControlFile(h, NULL, NULL, NULL, &iosb, IOCTL_DISK_GET_DRIVE_GEOMETRY, NULL, 0, &dg, sizeof(dg));\n    if (!NT_SUCCESS(Status)) {\n        NtClose(h);\n        return Status;\n    }\n\n    if (def_sector_size == 0) {\n        sector_size = dg.BytesPerSector;\n\n        if (sector_size == 0x200 || sector_size == 0)\n            sector_size = 0x1000;\n    } else {\n        if (dg.BytesPerSector != 0 && (def_sector_size < dg.BytesPerSector || def_sector_size % dg.BytesPerSector != 0 || !is_power_of_two(def_sector_size / dg.BytesPerSector))) {\n            NtClose(h);\n            return STATUS_INVALID_PARAMETER;\n        }\n\n        sector_size = def_sector_size;\n    }\n\n    if (def_node_size == 0)\n        node_size = 0x4000;\n    else {\n        if (def_node_size < sector_size || def_node_size % sector_size != 0 || !is_power_of_two(def_node_size / sector_size)) {\n            NtClose(h);\n            return STATUS_INVALID_PARAMETER;\n        }\n\n        node_size = def_node_size;\n    }\n\n    if (Callback) {\n        ULONG pc = 0;\n        Callback(PROGRESS, 0, (PVOID)&pc);\n    }\n\n    NtFsControlFile(h, NULL, NULL, NULL, &iosb, FSCTL_LOCK_VOLUME, NULL, 0, NULL, 0);\n\n    if (is_mounted_multi_device(h, sector_size)) {\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    do_full_trim(h);\n\n    incompat_flags = def_incompat_flags;\n    incompat_flags |= BTRFS_INCOMPAT_FLAGS_MIXED_BACKREF | BTRFS_INCOMPAT_FLAGS_BIG_METADATA;\n\n    compat_ro_flags = def_compat_ro_flags;\n\n    if (!Label) {\n        empty_label.Buffer = NULL;\n        empty_label.Length = empty_label.MaximumLength = 0;\n        Label = &empty_label;\n    }\n\n    Status = write_btrfs(h, gli.Length.QuadPart, Label, sector_size, node_size, incompat_flags,\n                         compat_ro_flags);\n\n    NtFsControlFile(h, NULL, NULL, NULL, &iosb, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0);\n\nend:\n    NtFsControlFile(h, NULL, NULL, NULL, &iosb, FSCTL_UNLOCK_VOLUME, NULL, 0, NULL, 0);\n\n    NtClose(h);\n\n    if (NT_SUCCESS(Status)) {\n        btrfsus.Buffer = btrfs;\n        btrfsus.Length = btrfsus.MaximumLength = (USHORT)(wcslen(btrfs) * sizeof(WCHAR));\n\n        InitializeObjectAttributes(&attr, &btrfsus, 0, NULL, NULL);\n\n        Status = NtOpenFile(&btrfsh, FILE_GENERIC_READ | FILE_GENERIC_WRITE, &attr, &iosb,\n                            FILE_SHARE_READ, FILE_SYNCHRONOUS_IO_ALERT);\n\n        if (NT_SUCCESS(Status)) {\n            MOUNTDEV_NAME* mdn;\n            ULONG mdnsize;\n\n            mdnsize = (ULONG)(offsetof(MOUNTDEV_NAME, Name[0]) + DriveRoot->Length);\n            mdn = malloc(mdnsize);\n\n            mdn->NameLength = DriveRoot->Length;\n            memcpy(mdn->Name, DriveRoot->Buffer, DriveRoot->Length);\n\n            NtDeviceIoControlFile(btrfsh, NULL, NULL, NULL, &iosb, IOCTL_BTRFS_PROBE_VOLUME, mdn, mdnsize, NULL, 0);\n\n            free(mdn);\n\n            NtClose(btrfsh);\n        }\n\n        Status = STATUS_SUCCESS;\n    }\n\n    if (Callback) {\n        bool success = NT_SUCCESS(Status);\n        Callback(DONE, 0, (PVOID)&success);\n    }\n\n    return Status;\n}\n\nBOOL __stdcall FormatEx(DSTRING* root, STREAM_MESSAGE* message, options* opts, uint32_t unk1) {\n    UNICODE_STRING DriveRoot, Label;\n    NTSTATUS Status;\n\n    if (!root || !root->string)\n        return false;\n\n    DriveRoot.Length = DriveRoot.MaximumLength = (USHORT)(wcslen(root->string) * sizeof(WCHAR));\n    DriveRoot.Buffer = root->string;\n\n    if (opts && opts->label && opts->label->string) {\n        Label.Length = Label.MaximumLength = (USHORT)(wcslen(opts->label->string) * sizeof(WCHAR));\n        Label.Buffer = opts->label->string;\n    } else {\n        Label.Length = Label.MaximumLength = 0;\n        Label.Buffer = NULL;\n    }\n\n    Status = FormatEx2(&DriveRoot, FMIFS_HARDDISK, &Label, opts && opts->flags & FORMAT_FLAG_QUICK_FORMAT, 0, NULL);\n\n    return NT_SUCCESS(Status);\n}\n\nvoid __stdcall SetSizes(ULONG sector, ULONG node) {\n    if (sector != 0)\n        def_sector_size = sector;\n\n    if (node != 0)\n        def_node_size = node;\n}\n\nvoid __stdcall SetIncompatFlags(uint64_t incompat_flags) {\n    def_incompat_flags = incompat_flags;\n}\n\nvoid __stdcall SetCompatROFlags(uint64_t compat_ro_flags) {\n    def_compat_ro_flags = compat_ro_flags;\n}\n\nvoid __stdcall SetCsumType(uint16_t csum_type) {\n    def_csum_type = csum_type;\n}\n\nBOOL __stdcall GetFilesystemInformation(uint32_t unk1, uint32_t unk2, void* unk3) {\n    // STUB - undocumented\n\n    return true;\n}\n\nBOOL APIENTRY DllMain(HANDLE hModule, DWORD dwReason, void* lpReserved) {\n    if (dwReason == DLL_PROCESS_ATTACH)\n        module = (HMODULE)hModule;\n\n    return true;\n}\n"
  },
  {
    "path": "src/ubtrfs/ubtrfs.def",
    "content": "EXPORTS\r\n    ChkdskEx\t\t\tPRIVATE\r\n    FormatEx\t\t\tPRIVATE\r\n    GetFilesystemInformation\tPRIVATE\r\n    SetSizes\t\t\tPRIVATE\r\n    SetIncompatFlags\t\tPRIVATE\r\n    SetCsumType\t\t\tPRIVATE\r\n    SetCompatROFlags\t\tPRIVATE\r\n"
  },
  {
    "path": "src/ubtrfs/ubtrfs.rc.in",
    "content": "// Microsoft Visual C++ generated resource script.\n//\n#include \"@CMAKE_CURRENT_SOURCE_DIR@/src/ubtrfs/resource.h\"\n\n#define APSTUDIO_READONLY_SYMBOLS\n/////////////////////////////////////////////////////////////////////////////\n//\n// Generated from the TEXTINCLUDE 2 resource.\n//\n#include <winresrc.h>\n\n/////////////////////////////////////////////////////////////////////////////\n#undef APSTUDIO_READONLY_SYMBOLS\n\n/////////////////////////////////////////////////////////////////////////////\n// English (United Kingdom) resources\n\n#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENG)\nLANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_UK\n#pragma code_page(1252)\n\n#ifdef APSTUDIO_INVOKED\n/////////////////////////////////////////////////////////////////////////////\n//\n// TEXTINCLUDE\n//\n\n1 TEXTINCLUDE\nBEGIN\n    \"resource.h\\0\"\nEND\n\n2 TEXTINCLUDE\nBEGIN\n    \"#include \"\"winres.h\"\"\\r\\n\"\n    \"\\0\"\nEND\n\n3 TEXTINCLUDE\nBEGIN\n    \"\\r\\n\"\n    \"\\0\"\nEND\n\n#endif    // APSTUDIO_INVOKED\n\n\n/////////////////////////////////////////////////////////////////////////////\n//\n// Version\n//\n\nVS_VERSION_INFO VERSIONINFO\n FILEVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0\n PRODUCTVERSION @PROJECT_VERSION_MAJOR@,@PROJECT_VERSION_MINOR@,@PROJECT_VERSION_PATCH@,0\n FILEFLAGSMASK 0x17L\n#ifdef _DEBUG\n FILEFLAGS 0x1L\n#else\n FILEFLAGS 0x0L\n#endif\n FILEOS 0x4L\n FILETYPE 0x2L\n FILESUBTYPE 0x0L\nBEGIN\n    BLOCK \"StringFileInfo\"\n    BEGIN\n        BLOCK \"080904b0\"\n        BEGIN\n            VALUE \"FileDescription\", \"Btrfs utility DLL\"\n            VALUE \"FileVersion\", \"@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@\"\n            VALUE \"InternalName\", \"ubtrfs\"\n            VALUE \"LegalCopyright\", \"Copyright (c) Mark Harmstone 2016-24\"\n            VALUE \"OriginalFilename\", \"ubtrfs.dll\"\n            VALUE \"ProductName\", \"WinBtrfs\"\n            VALUE \"ProductVersion\", \"@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@\"\n        END\n    END\n    BLOCK \"VarFileInfo\"\n    BEGIN\n        VALUE \"Translation\", 0x809, 1200\n    END\nEND\n\n#endif    // English (United Kingdom) resources\n/////////////////////////////////////////////////////////////////////////////\n\n\n\n#ifndef APSTUDIO_INVOKED\n/////////////////////////////////////////////////////////////////////////////\n//\n// Generated from the TEXTINCLUDE 3 resource.\n//\n\n\n/////////////////////////////////////////////////////////////////////////////\n#endif    // not APSTUDIO_INVOKED\n\n"
  },
  {
    "path": "src/volume.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n#include <mountdev.h>\n#include <ntddvol.h>\n#include <ntddstor.h>\n#include <ntdddisk.h>\n#include <wdmguid.h>\n\n#define IOCTL_VOLUME_IS_DYNAMIC     CTL_CODE(IOCTL_VOLUME_BASE, 18, METHOD_BUFFERED, FILE_ANY_ACCESS)\n#define IOCTL_VOLUME_POST_ONLINE    CTL_CODE(IOCTL_VOLUME_BASE, 25, METHOD_BUFFERED, FILE_READ_ACCESS | FILE_WRITE_ACCESS)\n\nextern PDRIVER_OBJECT drvobj;\nextern PDEVICE_OBJECT master_devobj;\nextern PDEVICE_OBJECT busobj;\nextern ERESOURCE pdo_list_lock;\nextern LIST_ENTRY pdo_list;\nextern UNICODE_STRING registry_path;\nextern tIoUnregisterPlugPlayNotificationEx fIoUnregisterPlugPlayNotificationEx;\n\nNTSTATUS vol_create(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    volume_device_extension* vde = DeviceObject->DeviceExtension;\n\n    TRACE(\"(%p, %p)\\n\", DeviceObject, Irp);\n\n    if (vde->removing)\n        return STATUS_DEVICE_NOT_READY;\n\n    Irp->IoStatus.Information = FILE_OPENED;\n    InterlockedIncrement(&vde->open_count);\n\n    return STATUS_SUCCESS;\n}\n\nvoid free_vol(volume_device_extension* vde) {\n    PDEVICE_OBJECT pdo;\n\n    vde->dead = true;\n\n    if (vde->mounted_device) {\n        device_extension* Vcb = vde->mounted_device->DeviceExtension;\n\n        Vcb->vde = NULL;\n    }\n\n    if (vde->name.Buffer)\n        ExFreePool(vde->name.Buffer);\n\n    ExDeleteResourceLite(&vde->pdode->child_lock);\n\n    if (vde->pdo->AttachedDevice)\n        IoDetachDevice(vde->pdo);\n\n    while (!IsListEmpty(&vde->pdode->children)) {\n        volume_child* vc = CONTAINING_RECORD(RemoveHeadList(&vde->pdode->children), volume_child, list_entry);\n\n        if (vc->notification_entry) {\n            if (fIoUnregisterPlugPlayNotificationEx)\n                fIoUnregisterPlugPlayNotificationEx(vc->notification_entry);\n            else\n                IoUnregisterPlugPlayNotification(vc->notification_entry);\n        }\n\n        if (vc->pnp_name.Buffer)\n            ExFreePool(vc->pnp_name.Buffer);\n\n        ExFreePool(vc);\n    }\n\n    if (no_pnp)\n        ExFreePool(vde->pdode);\n\n    pdo = vde->pdo;\n    IoDeleteDevice(vde->device);\n\n    if (!no_pnp)\n        IoDeleteDevice(pdo);\n}\n\nNTSTATUS vol_close(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    volume_device_extension* vde = DeviceObject->DeviceExtension;\n    pdo_device_extension* pdode = vde->pdode;\n\n    TRACE(\"(%p, %p)\\n\", DeviceObject, Irp);\n\n    Irp->IoStatus.Information = 0;\n\n    if (vde->dead)\n        return STATUS_SUCCESS;\n\n    ExAcquireResourceExclusiveLite(&pdo_list_lock, true);\n\n    if (vde->dead) {\n        ExReleaseResourceLite(&pdo_list_lock);\n        return STATUS_SUCCESS;\n    }\n\n    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n    if (InterlockedDecrement(&vde->open_count) == 0 && vde->removing) {\n        ExReleaseResourceLite(&pdode->child_lock);\n\n        free_vol(vde);\n    } else\n        ExReleaseResourceLite(&pdode->child_lock);\n\n    ExReleaseResourceLite(&pdo_list_lock);\n\n    return STATUS_SUCCESS;\n}\n\ntypedef struct {\n    IO_STATUS_BLOCK iosb;\n    KEVENT Event;\n} vol_read_context;\n\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall vol_read_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {\n    vol_read_context* context = conptr;\n\n    UNUSED(DeviceObject);\n\n    context->iosb = Irp->IoStatus;\n    KeSetEvent(&context->Event, 0, false);\n\n    return STATUS_MORE_PROCESSING_REQUIRED;\n}\n\nNTSTATUS vol_read(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    volume_device_extension* vde = DeviceObject->DeviceExtension;\n    pdo_device_extension* pdode = vde->pdode;\n    volume_child* vc;\n    NTSTATUS Status;\n    PIRP Irp2;\n    vol_read_context context;\n    PIO_STACK_LOCATION IrpSp, IrpSp2;\n\n    TRACE(\"(%p, %p)\\n\", DeviceObject, Irp);\n\n    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n    if (IsListEmpty(&pdode->children)) {\n        ExReleaseResourceLite(&pdode->child_lock);\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    }\n\n    vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry);\n\n    // We can't use IoSkipCurrentIrpStackLocation as the device isn't in our stack\n\n    Irp2 = IoAllocateIrp(vc->devobj->StackSize, false);\n\n    if (!Irp2) {\n        ERR(\"IoAllocateIrp failed\\n\");\n        ExReleaseResourceLite(&pdode->child_lock);\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    IrpSp2 = IoGetNextIrpStackLocation(Irp2);\n\n    IrpSp2->MajorFunction = IRP_MJ_READ;\n    IrpSp2->FileObject = vc->fileobj;\n\n    if (vc->devobj->Flags & DO_BUFFERED_IO) {\n        Irp2->AssociatedIrp.SystemBuffer = ExAllocatePoolWithTag(NonPagedPool, IrpSp->Parameters.Read.Length, ALLOC_TAG);\n        if (!Irp2->AssociatedIrp.SystemBuffer) {\n            ERR(\"out of memory\\n\");\n            ExReleaseResourceLite(&pdode->child_lock);\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        Irp2->Flags |= IRP_BUFFERED_IO | IRP_DEALLOCATE_BUFFER | IRP_INPUT_OPERATION;\n\n        Irp2->UserBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);\n    } else if (vc->devobj->Flags & DO_DIRECT_IO)\n        Irp2->MdlAddress = Irp->MdlAddress;\n    else\n        Irp2->UserBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);\n\n    IrpSp2->Parameters.Read.Length = IrpSp->Parameters.Read.Length;\n    IrpSp2->Parameters.Read.ByteOffset.QuadPart = IrpSp->Parameters.Read.ByteOffset.QuadPart;\n\n    KeInitializeEvent(&context.Event, NotificationEvent, false);\n    Irp2->UserIosb = &context.iosb;\n\n    IoSetCompletionRoutine(Irp2, vol_read_completion, &context, true, true, true);\n\n    Status = IoCallDriver(vc->devobj, Irp2);\n\n    if (Status == STATUS_PENDING) {\n        KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n        Status = context.iosb.Status;\n    }\n\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    Irp->IoStatus.Information = context.iosb.Information;\n\nend:\n    Irp->IoStatus.Status = Status;\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\n    return Status;\n}\n\nNTSTATUS vol_write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    volume_device_extension* vde = DeviceObject->DeviceExtension;\n    pdo_device_extension* pdode = vde->pdode;\n    volume_child* vc;\n    NTSTATUS Status;\n    PIRP Irp2;\n    vol_read_context context;\n    PIO_STACK_LOCATION IrpSp, IrpSp2;\n\n    TRACE(\"(%p, %p)\\n\", DeviceObject, Irp);\n\n    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n    if (IsListEmpty(&pdode->children)) {\n        ExReleaseResourceLite(&pdode->child_lock);\n        Status = STATUS_INVALID_DEVICE_REQUEST;\n        goto end;\n    }\n\n    vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry);\n\n    if (vc->list_entry.Flink != &pdode->children) { // more than once device\n        ExReleaseResourceLite(&pdode->child_lock);\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    // We can't use IoSkipCurrentIrpStackLocation as the device isn't in our stack\n\n    Irp2 = IoAllocateIrp(vc->devobj->StackSize, false);\n\n    if (!Irp2) {\n        ERR(\"IoAllocateIrp failed\\n\");\n        ExReleaseResourceLite(&pdode->child_lock);\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    IrpSp2 = IoGetNextIrpStackLocation(Irp2);\n\n    IrpSp2->MajorFunction = IRP_MJ_WRITE;\n    IrpSp2->FileObject = vc->fileobj;\n\n    if (vc->devobj->Flags & DO_BUFFERED_IO) {\n        Irp2->AssociatedIrp.SystemBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);\n\n        Irp2->Flags |= IRP_BUFFERED_IO;\n\n        Irp2->UserBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);\n    } else if (vc->devobj->Flags & DO_DIRECT_IO)\n        Irp2->MdlAddress = Irp->MdlAddress;\n    else\n        Irp2->UserBuffer = MmGetSystemAddressForMdlSafe(Irp->MdlAddress, NormalPagePriority);\n\n    IrpSp2->Parameters.Write.Length = IrpSp->Parameters.Write.Length;\n    IrpSp2->Parameters.Write.ByteOffset.QuadPart = IrpSp->Parameters.Write.ByteOffset.QuadPart;\n\n    KeInitializeEvent(&context.Event, NotificationEvent, false);\n    Irp2->UserIosb = &context.iosb;\n\n    IoSetCompletionRoutine(Irp2, vol_read_completion, &context, true, true, true);\n\n    Status = IoCallDriver(vc->devobj, Irp2);\n\n    if (Status == STATUS_PENDING) {\n        KeWaitForSingleObject(&context.Event, Executive, KernelMode, false, NULL);\n        Status = context.iosb.Status;\n    }\n\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    Irp->IoStatus.Information = context.iosb.Information;\n\nend:\n    Irp->IoStatus.Status = Status;\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\n    return Status;\n}\n\nstatic NTSTATUS vol_query_device_name(volume_device_extension* vde, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PMOUNTDEV_NAME name;\n\n    if (IrpSp->FileObject && IrpSp->FileObject->FsContext)\n        return STATUS_INVALID_PARAMETER;\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUNTDEV_NAME)) {\n        Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME);\n        return STATUS_BUFFER_TOO_SMALL;\n    }\n\n    name = Irp->AssociatedIrp.SystemBuffer;\n    name->NameLength = vde->name.Length;\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < offsetof(MOUNTDEV_NAME, Name[0]) + name->NameLength) {\n        Irp->IoStatus.Information = sizeof(MOUNTDEV_NAME);\n        return STATUS_BUFFER_OVERFLOW;\n    }\n\n    RtlCopyMemory(name->Name, vde->name.Buffer, vde->name.Length);\n\n    Irp->IoStatus.Information = offsetof(MOUNTDEV_NAME, Name[0]) + name->NameLength;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS vol_query_unique_id(volume_device_extension* vde, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    MOUNTDEV_UNIQUE_ID* mduid;\n    pdo_device_extension* pdode;\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUNTDEV_UNIQUE_ID)) {\n        Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID);\n        return STATUS_BUFFER_TOO_SMALL;\n    }\n\n    mduid = Irp->AssociatedIrp.SystemBuffer;\n    mduid->UniqueIdLength = sizeof(BTRFS_UUID);\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < offsetof(MOUNTDEV_UNIQUE_ID, UniqueId[0]) + mduid->UniqueIdLength) {\n        Irp->IoStatus.Information = sizeof(MOUNTDEV_UNIQUE_ID);\n        return STATUS_BUFFER_OVERFLOW;\n    }\n\n    if (!vde->pdo)\n        return STATUS_INVALID_PARAMETER;\n\n    pdode = vde->pdode;\n\n    RtlCopyMemory(mduid->UniqueId, &pdode->uuid, sizeof(BTRFS_UUID));\n\n    Irp->IoStatus.Information = offsetof(MOUNTDEV_UNIQUE_ID, UniqueId[0]) + mduid->UniqueIdLength;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS vol_is_dynamic(PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    uint8_t* buf;\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength == 0 || !Irp->AssociatedIrp.SystemBuffer)\n        return STATUS_INVALID_PARAMETER;\n\n    buf = (uint8_t*)Irp->AssociatedIrp.SystemBuffer;\n\n    *buf = 1;\n\n    Irp->IoStatus.Information = 1;\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS vol_check_verify(volume_device_extension* vde) {\n    pdo_device_extension* pdode = vde->pdode;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n\n    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n    le = pdode->children.Flink;\n    while (le != &pdode->children) {\n        volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n        Status = dev_ioctl(vc->devobj, IOCTL_STORAGE_CHECK_VERIFY, NULL, 0, NULL, 0, false, NULL);\n        if (!NT_SUCCESS(Status))\n            goto end;\n\n        le = le->Flink;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS vol_get_disk_extents(volume_device_extension* vde, PIRP Irp) {\n    pdo_device_extension* pdode = vde->pdode;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    LIST_ENTRY* le;\n    ULONG num_extents = 0, i, max_extents = 1;\n    NTSTATUS Status;\n    VOLUME_DISK_EXTENTS *ext, *ext3;\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(VOLUME_DISK_EXTENTS))\n        return STATUS_BUFFER_TOO_SMALL;\n\n    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n    le = pdode->children.Flink;\n    while (le != &pdode->children) {\n        volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n        VOLUME_DISK_EXTENTS ext2;\n\n        Status = dev_ioctl(vc->devobj, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, &ext2, sizeof(VOLUME_DISK_EXTENTS), false, NULL);\n        if (!NT_SUCCESS(Status) && Status != STATUS_BUFFER_OVERFLOW) {\n            ERR(\"IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        num_extents += ext2.NumberOfDiskExtents;\n\n        if (ext2.NumberOfDiskExtents > max_extents)\n            max_extents = ext2.NumberOfDiskExtents;\n\n        le = le->Flink;\n    }\n\n    ext = Irp->AssociatedIrp.SystemBuffer;\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < offsetof(VOLUME_DISK_EXTENTS, Extents[0]) + (num_extents * sizeof(DISK_EXTENT))) {\n        Irp->IoStatus.Information = offsetof(VOLUME_DISK_EXTENTS, Extents[0]);\n        ext->NumberOfDiskExtents = num_extents;\n        Status = STATUS_BUFFER_OVERFLOW;\n        goto end;\n    }\n\n    ext3 = ExAllocatePoolWithTag(PagedPool, offsetof(VOLUME_DISK_EXTENTS, Extents[0]) + (max_extents * sizeof(DISK_EXTENT)), ALLOC_TAG);\n    if (!ext3) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    i = 0;\n    ext->NumberOfDiskExtents = 0;\n\n    le = pdode->children.Flink;\n    while (le != &pdode->children) {\n        volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n        Status = dev_ioctl(vc->devobj, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, ext3,\n                           (ULONG)offsetof(VOLUME_DISK_EXTENTS, Extents[0]) + (max_extents * sizeof(DISK_EXTENT)), false, NULL);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS returned %08lx\\n\", Status);\n            ExFreePool(ext3);\n            goto end;\n        }\n\n        if (i + ext3->NumberOfDiskExtents > num_extents) {\n            Irp->IoStatus.Information = offsetof(VOLUME_DISK_EXTENTS, Extents[0]);\n            ext->NumberOfDiskExtents = i + ext3->NumberOfDiskExtents;\n            Status = STATUS_BUFFER_OVERFLOW;\n            ExFreePool(ext3);\n            goto end;\n        }\n\n        RtlCopyMemory(&ext->Extents[i], ext3->Extents, sizeof(DISK_EXTENT) * ext3->NumberOfDiskExtents);\n        i += ext3->NumberOfDiskExtents;\n\n        le = le->Flink;\n    }\n\n    ExFreePool(ext3);\n\n    Status = STATUS_SUCCESS;\n\n    ext->NumberOfDiskExtents = i;\n    Irp->IoStatus.Information = offsetof(VOLUME_DISK_EXTENTS, Extents[0]) + (i * sizeof(DISK_EXTENT));\n\nend:\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    return Status;\n}\n\nstatic NTSTATUS vol_is_writable(volume_device_extension* vde) {\n    pdo_device_extension* pdode = vde->pdode;\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    bool writable = false;\n\n    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n    le = pdode->children.Flink;\n    while (le != &pdode->children) {\n        volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n        Status = dev_ioctl(vc->devobj, IOCTL_DISK_IS_WRITABLE, NULL, 0, NULL, 0, true, NULL);\n\n        if (NT_SUCCESS(Status)) {\n            writable = true;\n            break;\n        } else if (Status != STATUS_MEDIA_WRITE_PROTECTED)\n            goto end;\n\n        le = le->Flink;\n    }\n\n    Status = writable ? STATUS_SUCCESS : STATUS_MEDIA_WRITE_PROTECTED;\n\nend:\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS vol_get_length(volume_device_extension* vde, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    pdo_device_extension* pdode = vde->pdode;\n    GET_LENGTH_INFORMATION* gli;\n    LIST_ENTRY* le;\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(GET_LENGTH_INFORMATION))\n        return STATUS_BUFFER_TOO_SMALL;\n\n    gli = (GET_LENGTH_INFORMATION*)Irp->AssociatedIrp.SystemBuffer;\n\n    gli->Length.QuadPart = 0;\n\n    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n    le = pdode->children.Flink;\n    while (le != &pdode->children) {\n        volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n        gli->Length.QuadPart += vc->size;\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    Irp->IoStatus.Information = sizeof(GET_LENGTH_INFORMATION);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS vol_get_drive_geometry(PDEVICE_OBJECT DeviceObject, PIRP Irp) {\n    volume_device_extension* vde = DeviceObject->DeviceExtension;\n    pdo_device_extension* pdode = vde->pdode;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    DISK_GEOMETRY* geom;\n    uint64_t length;\n    LIST_ENTRY* le;\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(DISK_GEOMETRY))\n        return STATUS_BUFFER_TOO_SMALL;\n\n    length = 0;\n\n    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n    le = pdode->children.Flink;\n    while (le != &pdode->children) {\n        volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n        length += vc->size;\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    geom = (DISK_GEOMETRY*)Irp->AssociatedIrp.SystemBuffer;\n    geom->BytesPerSector = DeviceObject->SectorSize == 0 ? 0x200 : DeviceObject->SectorSize;\n    geom->SectorsPerTrack = 0x3f;\n    geom->TracksPerCylinder = 0xff;\n    geom->Cylinders.QuadPart = length / (UInt32x32To64(geom->TracksPerCylinder, geom->SectorsPerTrack) * geom->BytesPerSector);\n    geom->MediaType = DeviceObject->Characteristics & FILE_REMOVABLE_MEDIA ? RemovableMedia : FixedMedia;\n\n    Irp->IoStatus.Information = sizeof(DISK_GEOMETRY);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS vol_get_gpt_attributes(PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    VOLUME_GET_GPT_ATTRIBUTES_INFORMATION* vggai;\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(VOLUME_GET_GPT_ATTRIBUTES_INFORMATION))\n        return STATUS_BUFFER_TOO_SMALL;\n\n    vggai = (VOLUME_GET_GPT_ATTRIBUTES_INFORMATION*)Irp->AssociatedIrp.SystemBuffer;\n\n    vggai->GptAttributes = 0;\n\n    Irp->IoStatus.Information = sizeof(VOLUME_GET_GPT_ATTRIBUTES_INFORMATION);\n\n    return STATUS_SUCCESS;\n}\n\nstatic NTSTATUS vol_get_device_number(volume_device_extension* vde, PIRP Irp) {\n    pdo_device_extension* pdode = vde->pdode;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    volume_child* vc;\n    STORAGE_DEVICE_NUMBER* sdn;\n\n    // If only one device, return its disk number. This is needed for ejection to work.\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(STORAGE_DEVICE_NUMBER))\n        return STATUS_BUFFER_TOO_SMALL;\n\n    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n    if (IsListEmpty(&pdode->children) || pdode->num_children > 1) {\n        ExReleaseResourceLite(&pdode->child_lock);\n        return STATUS_INVALID_DEVICE_REQUEST;\n    }\n\n    vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry);\n\n    if (vc->disk_num == 0xffffffff) {\n        ExReleaseResourceLite(&pdode->child_lock);\n        return STATUS_INVALID_DEVICE_REQUEST;\n    }\n\n    sdn = (STORAGE_DEVICE_NUMBER*)Irp->AssociatedIrp.SystemBuffer;\n\n    sdn->DeviceType = FILE_DEVICE_DISK;\n    sdn->DeviceNumber = vc->disk_num;\n    sdn->PartitionNumber = vc->part_num;\n\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    Irp->IoStatus.Information = sizeof(STORAGE_DEVICE_NUMBER);\n\n    return STATUS_SUCCESS;\n}\n\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall vol_ioctl_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {\n    KEVENT* event = conptr;\n\n    UNUSED(DeviceObject);\n    UNUSED(Irp);\n\n    KeSetEvent(event, 0, false);\n\n    return STATUS_MORE_PROCESSING_REQUIRED;\n}\n\nstatic NTSTATUS vol_ioctl_passthrough(volume_device_extension* vde, PIRP Irp) {\n    NTSTATUS Status;\n    volume_child* vc;\n    PIRP Irp2;\n    PIO_STACK_LOCATION IrpSp, IrpSp2;\n    KEVENT Event;\n    pdo_device_extension* pdode = vde->pdode;\n\n    TRACE(\"(%p, %p)\\n\", vde, Irp);\n\n    ExAcquireResourceSharedLite(&pdode->child_lock, true);\n\n    if (IsListEmpty(&pdode->children)) {\n        ExReleaseResourceLite(&pdode->child_lock);\n        return STATUS_INVALID_DEVICE_REQUEST;\n    }\n\n    vc = CONTAINING_RECORD(pdode->children.Flink, volume_child, list_entry);\n\n    if (vc->list_entry.Flink != &pdode->children) { // more than one device\n        ExReleaseResourceLite(&pdode->child_lock);\n        return STATUS_INVALID_DEVICE_REQUEST;\n    }\n\n    Irp2 = IoAllocateIrp(vc->devobj->StackSize, false);\n\n    if (!Irp2) {\n        ERR(\"IoAllocateIrp failed\\n\");\n        ExReleaseResourceLite(&pdode->child_lock);\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    IrpSp2 = IoGetNextIrpStackLocation(Irp2);\n\n    IrpSp2->MajorFunction = IrpSp->MajorFunction;\n    IrpSp2->MinorFunction = IrpSp->MinorFunction;\n    IrpSp2->FileObject = vc->fileobj;\n\n    IrpSp2->Parameters.DeviceIoControl.OutputBufferLength = IrpSp->Parameters.DeviceIoControl.OutputBufferLength;\n    IrpSp2->Parameters.DeviceIoControl.InputBufferLength = IrpSp->Parameters.DeviceIoControl.InputBufferLength;\n    IrpSp2->Parameters.DeviceIoControl.IoControlCode = IrpSp->Parameters.DeviceIoControl.IoControlCode;\n    IrpSp2->Parameters.DeviceIoControl.Type3InputBuffer = IrpSp->Parameters.DeviceIoControl.Type3InputBuffer;\n\n    Irp2->AssociatedIrp.SystemBuffer = Irp->AssociatedIrp.SystemBuffer;\n    Irp2->MdlAddress = Irp->MdlAddress;\n    Irp2->UserBuffer = Irp->UserBuffer;\n    Irp2->Flags = Irp->Flags;\n\n    KeInitializeEvent(&Event, NotificationEvent, false);\n\n    IoSetCompletionRoutine(Irp2, vol_ioctl_completion, &Event, true, true, true);\n\n    Status = IoCallDriver(vc->devobj, Irp2);\n\n    if (Status == STATUS_PENDING) {\n        KeWaitForSingleObject(&Event, Executive, KernelMode, false, NULL);\n        Status = Irp2->IoStatus.Status;\n    }\n\n    Irp->IoStatus.Status = Irp2->IoStatus.Status;\n    Irp->IoStatus.Information = Irp2->IoStatus.Information;\n\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    IoFreeIrp(Irp2);\n\n    return Status;\n}\n\nstatic NTSTATUS vol_query_stable_guid(volume_device_extension* vde, PIRP Irp) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    MOUNTDEV_STABLE_GUID* mdsg;\n    pdo_device_extension* pdode;\n\n    if (IrpSp->Parameters.DeviceIoControl.OutputBufferLength < sizeof(MOUNTDEV_STABLE_GUID)) {\n        Irp->IoStatus.Information = sizeof(MOUNTDEV_STABLE_GUID);\n        return STATUS_BUFFER_TOO_SMALL;\n    }\n\n    mdsg = Irp->AssociatedIrp.SystemBuffer;\n\n    if (!vde->pdo)\n        return STATUS_INVALID_PARAMETER;\n\n    pdode = vde->pdode;\n\n    RtlCopyMemory(&mdsg->StableGuid, &pdode->uuid, sizeof(BTRFS_UUID));\n\n    Irp->IoStatus.Information = sizeof(MOUNTDEV_STABLE_GUID);\n\n    return STATUS_SUCCESS;\n}\n\nNTSTATUS vol_device_control(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    volume_device_extension* vde = DeviceObject->DeviceExtension;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n    TRACE(\"(%p, %p)\\n\", DeviceObject, Irp);\n\n    Irp->IoStatus.Information = 0;\n\n    switch (IrpSp->Parameters.DeviceIoControl.IoControlCode) {\n        case IOCTL_MOUNTDEV_QUERY_DEVICE_NAME:\n            return vol_query_device_name(vde, Irp);\n\n        case IOCTL_MOUNTDEV_QUERY_UNIQUE_ID:\n            return vol_query_unique_id(vde, Irp);\n\n        case IOCTL_STORAGE_GET_DEVICE_NUMBER:\n            return vol_get_device_number(vde, Irp);\n\n        case IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME:\n            TRACE(\"unhandled control code IOCTL_MOUNTDEV_QUERY_SUGGESTED_LINK_NAME\\n\");\n            break;\n\n        case IOCTL_MOUNTDEV_QUERY_STABLE_GUID:\n            return vol_query_stable_guid(vde, Irp);\n\n        case IOCTL_MOUNTDEV_LINK_CREATED:\n            TRACE(\"unhandled control code IOCTL_MOUNTDEV_LINK_CREATED\\n\");\n            break;\n\n        case IOCTL_VOLUME_GET_GPT_ATTRIBUTES:\n            return vol_get_gpt_attributes(Irp);\n\n        case IOCTL_VOLUME_IS_DYNAMIC:\n            return vol_is_dynamic(Irp);\n\n        case IOCTL_VOLUME_ONLINE:\n            Irp->IoStatus.Information = 0;\n            return STATUS_SUCCESS;\n\n        case IOCTL_VOLUME_POST_ONLINE:\n            Irp->IoStatus.Information = 0;\n            return STATUS_SUCCESS;\n\n        case IOCTL_DISK_GET_DRIVE_GEOMETRY:\n            return vol_get_drive_geometry(DeviceObject, Irp);\n\n        case IOCTL_DISK_IS_WRITABLE:\n            return vol_is_writable(vde);\n\n        case IOCTL_DISK_GET_LENGTH_INFO:\n            return vol_get_length(vde, Irp);\n\n        case IOCTL_STORAGE_CHECK_VERIFY:\n        case IOCTL_DISK_CHECK_VERIFY:\n            return vol_check_verify(vde);\n\n        case IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS:\n            return vol_get_disk_extents(vde, Irp);\n\n        default: { // pass ioctl through if only one child device\n            NTSTATUS Status = vol_ioctl_passthrough(vde, Irp);\n#ifdef _DEBUG\n            ULONG code = IrpSp->Parameters.DeviceIoControl.IoControlCode;\n\n            if (NT_SUCCESS(Status))\n                TRACE(\"passing through ioctl %lx (returning %08lx)\\n\", code, Status);\n            else\n                WARN(\"passing through ioctl %lx (returning %08lx)\\n\", code, Status);\n#endif\n\n            return Status;\n        }\n    }\n\n    return STATUS_INVALID_DEVICE_REQUEST;\n}\n\nNTSTATUS mountmgr_add_drive_letter(PDEVICE_OBJECT mountmgr, PUNICODE_STRING devpath) {\n    NTSTATUS Status;\n    ULONG mmdltsize;\n    MOUNTMGR_DRIVE_LETTER_TARGET* mmdlt;\n    MOUNTMGR_DRIVE_LETTER_INFORMATION mmdli;\n\n    mmdltsize = (ULONG)offsetof(MOUNTMGR_DRIVE_LETTER_TARGET, DeviceName[0]) + devpath->Length;\n\n    mmdlt = ExAllocatePoolWithTag(NonPagedPool, mmdltsize, ALLOC_TAG);\n    if (!mmdlt) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    mmdlt->DeviceNameLength = devpath->Length;\n    RtlCopyMemory(&mmdlt->DeviceName, devpath->Buffer, devpath->Length);\n    TRACE(\"mmdlt = %.*S\\n\", (int)(mmdlt->DeviceNameLength / sizeof(WCHAR)), mmdlt->DeviceName);\n\n    Status = dev_ioctl(mountmgr, IOCTL_MOUNTMGR_NEXT_DRIVE_LETTER, mmdlt, mmdltsize, &mmdli, sizeof(MOUNTMGR_DRIVE_LETTER_INFORMATION), false, NULL);\n\n    if (!NT_SUCCESS(Status))\n        ERR(\"IOCTL_MOUNTMGR_NEXT_DRIVE_LETTER returned %08lx\\n\", Status);\n    else\n        TRACE(\"DriveLetterWasAssigned = %u, CurrentDriveLetter = %c\\n\", mmdli.DriveLetterWasAssigned, mmdli.CurrentDriveLetter);\n\n    ExFreePool(mmdlt);\n\n    return Status;\n}\n\n_Function_class_(DRIVER_NOTIFICATION_CALLBACK_ROUTINE)\nNTSTATUS __stdcall pnp_removal(PVOID NotificationStructure, PVOID Context) {\n    TARGET_DEVICE_REMOVAL_NOTIFICATION* tdrn = (TARGET_DEVICE_REMOVAL_NOTIFICATION*)NotificationStructure;\n    pdo_device_extension* pdode = (pdo_device_extension*)Context;\n\n    if (RtlCompareMemory(&tdrn->Event, &GUID_TARGET_DEVICE_QUERY_REMOVE, sizeof(GUID)) == sizeof(GUID)) {\n        TRACE(\"GUID_TARGET_DEVICE_QUERY_REMOVE\\n\");\n\n        if (pdode->vde && pdode->vde->mounted_device)\n            pnp_query_remove_device(pdode->vde->mounted_device, NULL);\n    }\n\n    return STATUS_SUCCESS;\n}\n\nstatic bool allow_degraded_mount(BTRFS_UUID* uuid) {\n    HANDLE h;\n    NTSTATUS Status;\n    OBJECT_ATTRIBUTES oa;\n    UNICODE_STRING path, adus;\n    uint32_t degraded = mount_allow_degraded;\n    ULONG i, j, kvfilen, retlen;\n    KEY_VALUE_FULL_INFORMATION* kvfi;\n\n    path.Length = path.MaximumLength = registry_path.Length + (37 * sizeof(WCHAR));\n    path.Buffer = ExAllocatePoolWithTag(PagedPool, path.Length, ALLOC_TAG);\n\n    if (!path.Buffer) {\n        ERR(\"out of memory\\n\");\n        return false;\n    }\n\n    RtlCopyMemory(path.Buffer, registry_path.Buffer, registry_path.Length);\n    i = registry_path.Length / sizeof(WCHAR);\n\n    path.Buffer[i] = '\\\\';\n    i++;\n\n    for (j = 0; j < 16; j++) {\n        path.Buffer[i] = hex_digit((uuid->uuid[j] & 0xF0) >> 4);\n        path.Buffer[i+1] = hex_digit(uuid->uuid[j] & 0xF);\n\n        i += 2;\n\n        if (j == 3 || j == 5 || j == 7 || j == 9) {\n            path.Buffer[i] = '-';\n            i++;\n        }\n    }\n\n    InitializeObjectAttributes(&oa, &path, OBJ_CASE_INSENSITIVE | OBJ_KERNEL_HANDLE, NULL, NULL);\n\n    kvfilen = (ULONG)offsetof(KEY_VALUE_FULL_INFORMATION, Name[0]) + (255 * sizeof(WCHAR));\n    kvfi = ExAllocatePoolWithTag(PagedPool, kvfilen, ALLOC_TAG);\n    if (!kvfi) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(path.Buffer);\n        return false;\n    }\n\n    Status = ZwOpenKey(&h, KEY_QUERY_VALUE, &oa);\n    if (Status == STATUS_OBJECT_NAME_NOT_FOUND)\n        goto end;\n    else if (!NT_SUCCESS(Status)) {\n        ERR(\"ZwOpenKey returned %08lx\\n\", Status);\n        goto end;\n    }\n\n    adus.Buffer = L\"AllowDegraded\";\n    adus.Length = adus.MaximumLength = sizeof(adus.Buffer) - sizeof(WCHAR);\n\n    if (NT_SUCCESS(ZwQueryValueKey(h, &adus, KeyValueFullInformation, kvfi, kvfilen, &retlen))) {\n        if (kvfi->Type == REG_DWORD && kvfi->DataLength >= sizeof(uint32_t)) {\n            uint32_t* val = (uint32_t*)((uint8_t*)kvfi + kvfi->DataOffset);\n\n            degraded = *val;\n        }\n    }\n\n    ZwClose(h);\n\nend:\n    ExFreePool(kvfi);\n\n    ExFreePool(path.Buffer);\n\n    return degraded;\n}\n\ntypedef struct {\n    LIST_ENTRY list_entry;\n    UNICODE_STRING name;\n    NTSTATUS Status;\n    BTRFS_UUID uuid;\n} drive_letter_removal;\n\nstatic void drive_letter_callback2(pdo_device_extension* pdode, PDEVICE_OBJECT mountmgr) {\n    LIST_ENTRY* le;\n    LIST_ENTRY dlrlist;\n\n    InitializeListHead(&dlrlist);\n\n    ExAcquireResourceExclusiveLite(&pdode->child_lock, true);\n\n    le = pdode->children.Flink;\n\n    while (le != &pdode->children) {\n        drive_letter_removal* dlr;\n\n        volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n        dlr = ExAllocatePoolWithTag(PagedPool, sizeof(drive_letter_removal), ALLOC_TAG);\n        if (!dlr) {\n            ERR(\"out of memory\\n\");\n\n            while (!IsListEmpty(&dlrlist)) {\n                dlr = CONTAINING_RECORD(RemoveHeadList(&dlrlist), drive_letter_removal, list_entry);\n\n                ExFreePool(dlr->name.Buffer);\n                ExFreePool(dlr);\n            }\n\n            ExReleaseResourceLite(&pdode->child_lock);\n            return;\n        }\n\n        dlr->name.Length = dlr->name.MaximumLength = vc->pnp_name.Length + (3 * sizeof(WCHAR));\n        dlr->name.Buffer = ExAllocatePoolWithTag(PagedPool, dlr->name.Length, ALLOC_TAG);\n\n        if (!dlr->name.Buffer) {\n            ERR(\"out of memory\\n\");\n\n            ExFreePool(dlr);\n\n            while (!IsListEmpty(&dlrlist)) {\n                dlr = CONTAINING_RECORD(RemoveHeadList(&dlrlist), drive_letter_removal, list_entry);\n\n                ExFreePool(dlr->name.Buffer);\n                ExFreePool(dlr);\n            }\n\n            ExReleaseResourceLite(&pdode->child_lock);\n            return;\n        }\n\n        RtlCopyMemory(dlr->name.Buffer, L\"\\\\??\", 3 * sizeof(WCHAR));\n        RtlCopyMemory(&dlr->name.Buffer[3], vc->pnp_name.Buffer, vc->pnp_name.Length);\n\n        dlr->uuid = vc->uuid;\n\n        InsertTailList(&dlrlist, &dlr->list_entry);\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    le = dlrlist.Flink;\n    while (le != &dlrlist) {\n        drive_letter_removal* dlr = CONTAINING_RECORD(le, drive_letter_removal, list_entry);\n\n        dlr->Status = remove_drive_letter(mountmgr, &dlr->name);\n\n        if (!NT_SUCCESS(dlr->Status) && dlr->Status != STATUS_NOT_FOUND)\n            WARN(\"remove_drive_letter returned %08lx\\n\", dlr->Status);\n\n        le = le->Flink;\n    }\n\n    // set vc->had_drive_letter\n\n    ExAcquireResourceExclusiveLite(&pdode->child_lock, true);\n\n    while (!IsListEmpty(&dlrlist)) {\n        drive_letter_removal* dlr = CONTAINING_RECORD(RemoveHeadList(&dlrlist), drive_letter_removal, list_entry);\n\n        le = pdode->children.Flink;\n\n        while (le != &pdode->children) {\n            volume_child* vc = CONTAINING_RECORD(le, volume_child, list_entry);\n\n            if (RtlCompareMemory(&vc->uuid, &dlr->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n                vc->had_drive_letter = NT_SUCCESS(dlr->Status);\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        ExFreePool(dlr->name.Buffer);\n        ExFreePool(dlr);\n    }\n\n    ExReleaseResourceLite(&pdode->child_lock);\n}\n\n_Function_class_(IO_WORKITEM_ROUTINE)\nstatic void __stdcall drive_letter_callback(pdo_device_extension* pdode) {\n    NTSTATUS Status;\n    UNICODE_STRING mmdevpath;\n    PDEVICE_OBJECT mountmgr;\n    PFILE_OBJECT mountmgrfo;\n\n    RtlInitUnicodeString(&mmdevpath, MOUNTMGR_DEVICE_NAME);\n    Status = IoGetDeviceObjectPointer(&mmdevpath, FILE_READ_ATTRIBUTES, &mountmgrfo, &mountmgr);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n        return;\n    }\n\n    drive_letter_callback2(pdode, mountmgr);\n\n    ObDereferenceObject(mountmgrfo);\n}\n\nvoid add_volume_device(superblock* sb, PUNICODE_STRING devpath, uint64_t length, ULONG disk_num, ULONG part_num) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    PDEVICE_OBJECT DeviceObject;\n    volume_child* vc;\n    PFILE_OBJECT FileObject;\n    UNICODE_STRING devpath2;\n    bool inserted = false, new_pdo = false;\n    pdo_device_extension* pdode = NULL;\n    PDEVICE_OBJECT pdo = NULL;\n    bool process_drive_letters = false;\n\n    if (devpath->Length == 0)\n        return;\n\n    ExAcquireResourceExclusiveLite(&pdo_list_lock, true);\n\n    le = pdo_list.Flink;\n    while (le != &pdo_list) {\n        pdo_device_extension* pdode2 = CONTAINING_RECORD(le, pdo_device_extension, list_entry);\n\n        if (RtlCompareMemory(&pdode2->uuid, &sb->uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n            pdode = pdode2;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    Status = IoGetDeviceObjectPointer(devpath, FILE_READ_ATTRIBUTES, &FileObject, &DeviceObject);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"IoGetDeviceObjectPointer returned %08lx\\n\", Status);\n        ExReleaseResourceLite(&pdo_list_lock);\n        return;\n    }\n\n    if (!pdode) {\n        if (no_pnp) {\n            Status = IoReportDetectedDevice(drvobj, InterfaceTypeUndefined, 0xFFFFFFFF, 0xFFFFFFFF, NULL, NULL, 0, &pdo);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"IoReportDetectedDevice returned %08lx\\n\", Status);\n                ExReleaseResourceLite(&pdo_list_lock);\n                return;\n            }\n\n            pdode = ExAllocatePoolWithTag(NonPagedPool, sizeof(pdo_device_extension), ALLOC_TAG);\n\n            if (!pdode) {\n                ERR(\"out of memory\\n\");\n                ExReleaseResourceLite(&pdo_list_lock);\n                return;\n            }\n        } else {\n            Status = IoCreateDevice(drvobj, sizeof(pdo_device_extension), NULL, FILE_DEVICE_DISK,\n                                    FILE_AUTOGENERATED_DEVICE_NAME | FILE_DEVICE_SECURE_OPEN, false, &pdo);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"IoCreateDevice returned %08lx\\n\", Status);\n                ExReleaseResourceLite(&pdo_list_lock);\n                goto fail;\n            }\n\n            pdo->Flags |= DO_BUS_ENUMERATED_DEVICE;\n\n            pdode = pdo->DeviceExtension;\n        }\n\n        RtlZeroMemory(pdode, sizeof(pdo_device_extension));\n\n        pdode->type = VCB_TYPE_PDO;\n        pdode->pdo = pdo;\n        pdode->uuid = sb->uuid;\n\n        ExInitializeResourceLite(&pdode->child_lock);\n        InitializeListHead(&pdode->children);\n        pdode->num_children = sb->num_devices;\n        pdode->children_loaded = 0;\n\n        pdo->Flags &= ~DO_DEVICE_INITIALIZING;\n        pdo->SectorSize = (USHORT)sb->sector_size;\n\n        ExAcquireResourceExclusiveLite(&pdode->child_lock, true);\n\n        new_pdo = true;\n    } else {\n        ExAcquireResourceExclusiveLite(&pdode->child_lock, true);\n        ExConvertExclusiveToSharedLite(&pdo_list_lock);\n\n        le = pdode->children.Flink;\n        while (le != &pdode->children) {\n            volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry);\n\n            if (RtlCompareMemory(&vc2->uuid, &sb->dev_item.device_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n                // duplicate, ignore\n                ExReleaseResourceLite(&pdode->child_lock);\n                ExReleaseResourceLite(&pdo_list_lock);\n                goto fail;\n            }\n\n            le = le->Flink;\n        }\n    }\n\n    vc = ExAllocatePoolWithTag(PagedPool, sizeof(volume_child), ALLOC_TAG);\n    if (!vc) {\n        ERR(\"out of memory\\n\");\n\n        ExReleaseResourceLite(&pdode->child_lock);\n        ExReleaseResourceLite(&pdo_list_lock);\n\n        goto fail;\n    }\n\n    vc->uuid = sb->dev_item.device_uuid;\n    vc->devid = sb->dev_item.dev_id;\n    vc->generation = sb->generation;\n    vc->notification_entry = NULL;\n    vc->boot_volume = false;\n\n    Status = IoRegisterPlugPlayNotification(EventCategoryTargetDeviceChange, 0, FileObject,\n                                            drvobj, pnp_removal, pdode, &vc->notification_entry);\n    if (!NT_SUCCESS(Status))\n        WARN(\"IoRegisterPlugPlayNotification returned %08lx\\n\", Status);\n\n    vc->devobj = DeviceObject;\n    vc->fileobj = FileObject;\n\n    devpath2 = *devpath;\n\n    // The PNP path sometimes begins \\\\?\\ and sometimes \\??\\. We need to remove this prefix\n    // so we can compare properly if the device is removed.\n    if (devpath->Length > 4 * sizeof(WCHAR) && devpath->Buffer[0] == '\\\\' && (devpath->Buffer[1] == '\\\\' || devpath->Buffer[1] == '?') &&\n        devpath->Buffer[2] == '?' && devpath->Buffer[3] == '\\\\') {\n        devpath2.Buffer = &devpath2.Buffer[3];\n        devpath2.Length -= 3 * sizeof(WCHAR);\n        devpath2.MaximumLength -= 3 * sizeof(WCHAR);\n    }\n\n    vc->pnp_name.Length = vc->pnp_name.MaximumLength = devpath2.Length;\n    vc->pnp_name.Buffer = ExAllocatePoolWithTag(PagedPool, devpath2.Length, ALLOC_TAG);\n\n    if (vc->pnp_name.Buffer)\n        RtlCopyMemory(vc->pnp_name.Buffer, devpath2.Buffer, devpath2.Length);\n    else {\n        ERR(\"out of memory\\n\");\n        vc->pnp_name.Length = vc->pnp_name.MaximumLength = 0;\n    }\n\n    vc->size = length;\n    vc->seeding = sb->flags & BTRFS_SUPERBLOCK_FLAGS_SEEDING ? true : false;\n    vc->disk_num = disk_num;\n    vc->part_num = part_num;\n    vc->had_drive_letter = false;\n\n    le = pdode->children.Flink;\n    while (le != &pdode->children) {\n        volume_child* vc2 = CONTAINING_RECORD(le, volume_child, list_entry);\n\n        if (vc2->generation < vc->generation) {\n            if (le == pdode->children.Flink)\n                pdode->num_children = sb->num_devices;\n\n            InsertHeadList(vc2->list_entry.Blink, &vc->list_entry);\n            inserted = true;\n            break;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!inserted)\n        InsertTailList(&pdode->children, &vc->list_entry);\n\n    pdode->children_loaded++;\n\n    if (pdode->vde && pdode->vde->mounted_device) {\n        device_extension* Vcb = pdode->vde->mounted_device->DeviceExtension;\n\n        ExAcquireResourceExclusiveLite(&Vcb->tree_lock, true);\n\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n            if (!dev->devobj && RtlCompareMemory(&dev->devitem.device_uuid, &sb->dev_item.device_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID)) {\n                dev->devobj = DeviceObject;\n                dev->disk_num = disk_num;\n                dev->part_num = part_num;\n                init_device(Vcb, dev, false);\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        ExReleaseResourceLite(&Vcb->tree_lock);\n    }\n\n    if (DeviceObject->Characteristics & FILE_REMOVABLE_MEDIA) {\n        pdode->removable = true;\n\n        if (pdode->vde && pdode->vde->device)\n            pdode->vde->device->Characteristics |= FILE_REMOVABLE_MEDIA;\n    }\n\n    if (pdode->num_children == pdode->children_loaded || (pdode->children_loaded == 1 && allow_degraded_mount(&sb->uuid))) {\n        if ((!new_pdo || !no_pnp) && pdode->vde) {\n            Status = IoSetDeviceInterfaceState(&pdode->vde->bus_name, true);\n            if (!NT_SUCCESS(Status))\n                WARN(\"IoSetDeviceInterfaceState returned %08lx\\n\", Status);\n        }\n\n        process_drive_letters = true;\n    }\n\n    ExReleaseResourceLite(&pdode->child_lock);\n\n    if (new_pdo)\n        InsertTailList(&pdo_list, &pdode->list_entry);\n\n    ExReleaseResourceLite(&pdo_list_lock);\n\n    if (process_drive_letters)\n        drive_letter_callback(pdode);\n\n    if (new_pdo) {\n        if (RtlCompareMemory(&sb->uuid, &boot_uuid, sizeof(BTRFS_UUID)) == sizeof(BTRFS_UUID))\n            boot_add_device(pdo);\n        else if (no_pnp)\n            AddDevice(drvobj, pdo);\n        else {\n            bus_device_extension* bde = busobj->DeviceExtension;\n            IoInvalidateDeviceRelations(bde->buspdo, BusRelations);\n        }\n    }\n\n    return;\n\nfail:\n    ObDereferenceObject(FileObject);\n}\n"
  },
  {
    "path": "src/worker-thread.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n\ntypedef struct {\n    device_extension* Vcb;\n    PIRP Irp;\n    WORK_QUEUE_ITEM item;\n} job_info;\n\nNTSTATUS do_read_job(PIRP Irp) {\n    NTSTATUS Status;\n    ULONG bytes_read;\n    bool top_level = is_top_level(Irp);\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    fcb* fcb = FileObject->FsContext;\n    bool acquired_fcb_lock = false;\n\n    Irp->IoStatus.Information = 0;\n\n    if (!ExIsResourceAcquiredSharedLite(fcb->Header.Resource)) {\n        ExAcquireResourceSharedLite(fcb->Header.Resource, true);\n        acquired_fcb_lock = true;\n    }\n\n    try {\n        Status = do_read(Irp, true, &bytes_read);\n    } except (EXCEPTION_EXECUTE_HANDLER) {\n        Status = GetExceptionCode();\n    }\n\n    if (acquired_fcb_lock)\n        ExReleaseResourceLite(fcb->Header.Resource);\n\n    if (!NT_SUCCESS(Status))\n        ERR(\"do_read returned %08lx\\n\", Status);\n\n    Irp->IoStatus.Status = Status;\n\n    TRACE(\"read %Iu bytes\\n\", Irp->IoStatus.Information);\n\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    TRACE(\"returning %08lx\\n\", Status);\n\n    return Status;\n}\n\nNTSTATUS do_write_job(device_extension* Vcb, PIRP Irp) {\n    bool top_level = is_top_level(Irp);\n    NTSTATUS Status;\n\n    try {\n        Status = write_file(Vcb, Irp, true, true);\n    } except (EXCEPTION_EXECUTE_HANDLER) {\n        Status = GetExceptionCode();\n    }\n\n    if (!NT_SUCCESS(Status))\n        ERR(\"write_file returned %08lx\\n\", Status);\n\n    Irp->IoStatus.Status = Status;\n\n    TRACE(\"wrote %Iu bytes\\n\", Irp->IoStatus.Information);\n\n    IoCompleteRequest(Irp, IO_NO_INCREMENT);\n\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    TRACE(\"returning %08lx\\n\", Status);\n\n    return Status;\n}\n\n_Function_class_(WORKER_THREAD_ROUTINE)\nstatic void __stdcall do_job(void* context) {\n    job_info* ji = context;\n    PIO_STACK_LOCATION IrpSp = ji->Irp ? IoGetCurrentIrpStackLocation(ji->Irp) : NULL;\n\n    if (IrpSp->MajorFunction == IRP_MJ_READ) {\n        do_read_job(ji->Irp);\n    } else if (IrpSp->MajorFunction == IRP_MJ_WRITE) {\n        do_write_job(ji->Vcb, ji->Irp);\n    }\n\n    ExFreePool(ji);\n}\n\nbool add_thread_job(device_extension* Vcb, PIRP Irp) {\n    job_info* ji;\n\n    ji = ExAllocatePoolWithTag(NonPagedPool, sizeof(job_info), ALLOC_TAG);\n    if (!ji) {\n        ERR(\"out of memory\\n\");\n        return false;\n    }\n\n    ji->Vcb = Vcb;\n    ji->Irp = Irp;\n\n    if (!Irp->MdlAddress) {\n        PMDL Mdl;\n        LOCK_OPERATION op;\n        ULONG len;\n        PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n\n        if (IrpSp->MajorFunction == IRP_MJ_READ) {\n            op = IoWriteAccess;\n            len = IrpSp->Parameters.Read.Length;\n        } else if (IrpSp->MajorFunction == IRP_MJ_WRITE) {\n            op = IoReadAccess;\n            len = IrpSp->Parameters.Write.Length;\n        } else {\n            ERR(\"unexpected major function %u\\n\", IrpSp->MajorFunction);\n            ExFreePool(ji);\n            return false;\n        }\n\n        Mdl = IoAllocateMdl(Irp->UserBuffer, len, false, false, Irp);\n\n        if (!Mdl) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(ji);\n            return false;\n        }\n\n        try {\n            MmProbeAndLockPages(Mdl, Irp->RequestorMode, op);\n        } except(EXCEPTION_EXECUTE_HANDLER) {\n            ERR(\"MmProbeAndLockPages raised status %08lx\\n\", GetExceptionCode());\n\n            IoFreeMdl(Mdl);\n            Irp->MdlAddress = NULL;\n            ExFreePool(ji);\n\n            return false;\n        }\n    }\n\n    ExInitializeWorkItem(&ji->item, do_job, ji);\n    ExQueueWorkItem(&ji->item, DelayedWorkQueue);\n\n    return true;\n}\n"
  },
  {
    "path": "src/write.c",
    "content": "/* Copyright (c) Mark Harmstone 2016-17\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n#include \"btrfs_drv.h\"\n\ntypedef struct {\n    uint64_t start;\n    uint64_t end;\n    uint8_t* data;\n    PMDL mdl;\n    uint64_t irp_offset;\n} write_stripe;\n\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall write_data_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr);\n\nstatic void remove_fcb_extent(fcb* fcb, extent* ext, LIST_ENTRY* rollback) __attribute__((nonnull(1, 2, 3)));\n\nextern tPsUpdateDiskCounters fPsUpdateDiskCounters;\nextern tCcCopyWriteEx fCcCopyWriteEx;\nextern tFsRtlUpdateDiskCounters fFsRtlUpdateDiskCounters;\nextern bool diskacc;\n\n__attribute__((nonnull(1, 2, 4)))\nbool find_data_address_in_chunk(device_extension* Vcb, chunk* c, uint64_t length, uint64_t* address) {\n    LIST_ENTRY* le;\n    space* s;\n\n    TRACE(\"(%p, %I64x, %I64x, %p)\\n\", Vcb, c->offset, length, address);\n\n    if (length > c->chunk_item->size - c->used)\n        return false;\n\n    if (!c->cache_loaded) {\n        NTSTATUS Status = load_cache_chunk(Vcb, c, NULL);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"load_cache_chunk returned %08lx\\n\", Status);\n            return false;\n        }\n    }\n\n    if (IsListEmpty(&c->space_size))\n        return false;\n\n    le = c->space_size.Flink;\n    while (le != &c->space_size) {\n        s = CONTAINING_RECORD(le, space, list_entry_size);\n\n        if (s->size == length) {\n            *address = s->address;\n            return true;\n        } else if (s->size < length) {\n            if (le == c->space_size.Flink)\n                return false;\n\n            s = CONTAINING_RECORD(le->Blink, space, list_entry_size);\n\n            *address = s->address;\n            return true;\n        }\n\n        le = le->Flink;\n    }\n\n    s = CONTAINING_RECORD(c->space_size.Blink, space, list_entry_size);\n\n    if (s->size > length) {\n        *address = s->address;\n        return true;\n    }\n\n    return false;\n}\n\n__attribute__((nonnull(1)))\nchunk* get_chunk_from_address(device_extension* Vcb, uint64_t address) {\n    LIST_ENTRY* le2;\n\n    ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);\n\n    le2 = Vcb->chunks.Flink;\n    while (le2 != &Vcb->chunks) {\n        chunk* c = CONTAINING_RECORD(le2, chunk, list_entry);\n\n        if (address >= c->offset && address < c->offset + c->chunk_item->size) {\n            ExReleaseResourceLite(&Vcb->chunk_lock);\n            return c;\n        }\n\n        le2 = le2->Flink;\n    }\n\n    ExReleaseResourceLite(&Vcb->chunk_lock);\n\n    return NULL;\n}\n\ntypedef struct {\n    space* dh;\n    device* device;\n} stripe;\n\n__attribute__((nonnull(1)))\nstatic uint64_t find_new_chunk_address(device_extension* Vcb, uint64_t size) {\n    uint64_t lastaddr;\n    LIST_ENTRY* le;\n\n    lastaddr = 0xc00000;\n\n    le = Vcb->chunks.Flink;\n    while (le != &Vcb->chunks) {\n        chunk* c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (c->offset >= lastaddr + size)\n            return lastaddr;\n\n        lastaddr = c->offset + c->chunk_item->size;\n\n        le = le->Flink;\n    }\n\n    return lastaddr;\n}\n\n__attribute__((nonnull(1,2)))\nstatic bool find_new_dup_stripes(device_extension* Vcb, stripe* stripes, uint64_t max_stripe_size, bool full_size) {\n    uint64_t devusage = 0xffffffffffffffff;\n    space *devdh1 = NULL, *devdh2 = NULL;\n    LIST_ENTRY* le;\n    device* dev2 = NULL;\n\n    le = Vcb->devices.Flink;\n\n    while (le != &Vcb->devices) {\n        device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n        if (!dev->readonly && !dev->reloc && dev->devobj) {\n            uint64_t usage = (dev->devitem.bytes_used * 4096) / dev->devitem.num_bytes;\n\n            // favour devices which have been used the least\n            if (usage < devusage) {\n                if (!IsListEmpty(&dev->space)) {\n                    LIST_ENTRY* le2;\n                    space *dh1 = NULL, *dh2 = NULL;\n\n                    le2 = dev->space.Flink;\n                    while (le2 != &dev->space) {\n                        space* dh = CONTAINING_RECORD(le2, space, list_entry);\n\n                        if (dh->size >= max_stripe_size && (!dh1 || !dh2 || dh->size < dh1->size)) {\n                            dh2 = dh1;\n                            dh1 = dh;\n                        }\n\n                        le2 = le2->Flink;\n                    }\n\n                    if (dh1 && (dh2 || dh1->size >= 2 * max_stripe_size)) {\n                        dev2 = dev;\n                        devusage = usage;\n                        devdh1 = dh1;\n                        devdh2 = dh2 ? dh2 : dh1;\n                    }\n                }\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    if (!devdh1) {\n        uint64_t size = 0;\n\n        // Can't find hole of at least max_stripe_size; look for the largest one we can find\n\n        if (full_size)\n            return false;\n\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n\n            if (!dev->readonly && !dev->reloc) {\n                if (!IsListEmpty(&dev->space)) {\n                    LIST_ENTRY* le2;\n                    space *dh1 = NULL, *dh2 = NULL;\n\n                    le2 = dev->space.Flink;\n                    while (le2 != &dev->space) {\n                        space* dh = CONTAINING_RECORD(le2, space, list_entry);\n\n                        if (!dh1 || !dh2 || dh->size < dh1->size) {\n                            dh2 = dh1;\n                            dh1 = dh;\n                        }\n\n                        le2 = le2->Flink;\n                    }\n\n                    if (dh1) {\n                        uint64_t devsize;\n\n                        if (dh2)\n                            devsize = max(dh1->size / 2, min(dh1->size, dh2->size));\n                        else\n                            devsize = dh1->size / 2;\n\n                        if (devsize > size) {\n                            dev2 = dev;\n                            devdh1 = dh1;\n\n                            if (dh2 && min(dh1->size, dh2->size) > dh1->size / 2)\n                                devdh2 = dh2;\n                            else\n                                devdh2 = dh1;\n\n                            size = devsize;\n                        }\n                    }\n                }\n            }\n\n            le = le->Flink;\n        }\n\n        if (!devdh1)\n            return false;\n    }\n\n    stripes[0].device = stripes[1].device = dev2;\n    stripes[0].dh = devdh1;\n    stripes[1].dh = devdh2;\n\n    return true;\n}\n\n__attribute__((nonnull(1,2)))\nstatic bool find_new_stripe(device_extension* Vcb, stripe* stripes, uint16_t i, uint64_t max_stripe_size, bool allow_missing, bool full_size) {\n    uint64_t k, devusage = 0xffffffffffffffff;\n    space* devdh = NULL;\n    LIST_ENTRY* le;\n    device* dev2 = NULL;\n\n    le = Vcb->devices.Flink;\n    while (le != &Vcb->devices) {\n        device* dev = CONTAINING_RECORD(le, device, list_entry);\n        uint64_t usage;\n        bool skip = false;\n\n        if (dev->readonly || dev->reloc || (!dev->devobj && !allow_missing)) {\n            le = le->Flink;\n            continue;\n        }\n\n        // skip this device if it already has a stripe\n        if (i > 0) {\n            for (k = 0; k < i; k++) {\n                if (stripes[k].device == dev) {\n                    skip = true;\n                    break;\n                }\n            }\n        }\n\n        if (!skip) {\n            usage = (dev->devitem.bytes_used * 4096) / dev->devitem.num_bytes;\n\n            // favour devices which have been used the least\n            if (usage < devusage) {\n                if (!IsListEmpty(&dev->space)) {\n                    LIST_ENTRY* le2;\n\n                    le2 = dev->space.Flink;\n                    while (le2 != &dev->space) {\n                        space* dh = CONTAINING_RECORD(le2, space, list_entry);\n\n                        if ((dev2 != dev && dh->size >= max_stripe_size) ||\n                            (dev2 == dev && dh->size >= max_stripe_size && dh->size < devdh->size)\n                        ) {\n                            devdh = dh;\n                            dev2 = dev;\n                            devusage = usage;\n                        }\n\n                        le2 = le2->Flink;\n                    }\n                }\n            }\n        }\n\n        le = le->Flink;\n    }\n\n    if (!devdh) {\n        // Can't find hole of at least max_stripe_size; look for the largest one we can find\n\n        if (full_size)\n            return false;\n\n        le = Vcb->devices.Flink;\n        while (le != &Vcb->devices) {\n            device* dev = CONTAINING_RECORD(le, device, list_entry);\n            bool skip = false;\n\n            if (dev->readonly || dev->reloc || (!dev->devobj && !allow_missing)) {\n                le = le->Flink;\n                continue;\n            }\n\n            // skip this device if it already has a stripe\n            if (i > 0) {\n                for (k = 0; k < i; k++) {\n                    if (stripes[k].device == dev) {\n                        skip = true;\n                        break;\n                    }\n                }\n            }\n\n            if (!skip) {\n                if (!IsListEmpty(&dev->space)) {\n                    LIST_ENTRY* le2;\n\n                    le2 = dev->space.Flink;\n                    while (le2 != &dev->space) {\n                        space* dh = CONTAINING_RECORD(le2, space, list_entry);\n\n                        if (!devdh || devdh->size < dh->size) {\n                            devdh = dh;\n                            dev2 = dev;\n                        }\n\n                        le2 = le2->Flink;\n                    }\n                }\n            }\n\n            le = le->Flink;\n        }\n\n        if (!devdh)\n            return false;\n    }\n\n    stripes[i].dh = devdh;\n    stripes[i].device = dev2;\n\n    return true;\n}\n\n__attribute__((nonnull(1,3)))\nNTSTATUS alloc_chunk(device_extension* Vcb, uint64_t flags, chunk** pc, bool full_size) {\n    NTSTATUS Status;\n    uint64_t max_stripe_size, max_chunk_size, stripe_size, stripe_length, factor;\n    uint64_t total_size = 0, logaddr;\n    uint16_t i, type, num_stripes, sub_stripes, max_stripes, min_stripes, allowed_missing;\n    stripe* stripes = NULL;\n    uint16_t cisize;\n    CHUNK_ITEM_STRIPE* cis;\n    chunk* c = NULL;\n    space* s = NULL;\n    LIST_ENTRY* le;\n\n    le = Vcb->devices.Flink;\n    while (le != &Vcb->devices) {\n        device* dev = CONTAINING_RECORD(le, device, list_entry);\n        total_size += dev->devitem.num_bytes;\n\n        le = le->Flink;\n    }\n\n    TRACE(\"total_size = %I64x\\n\", total_size);\n\n    // We purposely check for DATA first - mixed blocks have the same size\n    // as DATA ones.\n    if (flags & BLOCK_FLAG_DATA) {\n        max_stripe_size = 0x40000000; // 1 GB\n        max_chunk_size = 10 * max_stripe_size;\n    } else if (flags & BLOCK_FLAG_METADATA) {\n        if (total_size > 0xC80000000) // 50 GB\n            max_stripe_size = 0x40000000; // 1 GB\n        else\n            max_stripe_size = 0x10000000; // 256 MB\n\n        max_chunk_size = max_stripe_size;\n    } else if (flags & BLOCK_FLAG_SYSTEM) {\n        max_stripe_size = 0x2000000; // 32 MB\n        max_chunk_size = 2 * max_stripe_size;\n    } else {\n        ERR(\"unknown chunk type\\n\");\n        return STATUS_INTERNAL_ERROR;\n    }\n\n    if (flags & BLOCK_FLAG_DUPLICATE) {\n        min_stripes = 2;\n        max_stripes = 2;\n        sub_stripes = 0;\n        type = BLOCK_FLAG_DUPLICATE;\n        allowed_missing = 0;\n    } else if (flags & BLOCK_FLAG_RAID0) {\n        min_stripes = 2;\n        max_stripes = (uint16_t)min(0xffff, Vcb->superblock.num_devices);\n        sub_stripes = 0;\n        type = BLOCK_FLAG_RAID0;\n        allowed_missing = 0;\n    } else if (flags & BLOCK_FLAG_RAID1) {\n        min_stripes = 2;\n        max_stripes = 2;\n        sub_stripes = 1;\n        type = BLOCK_FLAG_RAID1;\n        allowed_missing = 1;\n    } else if (flags & BLOCK_FLAG_RAID10) {\n        min_stripes = 4;\n        max_stripes = (uint16_t)min(0xffff, Vcb->superblock.num_devices);\n        sub_stripes = 2;\n        type = BLOCK_FLAG_RAID10;\n        allowed_missing = 1;\n    } else if (flags & BLOCK_FLAG_RAID5) {\n        min_stripes = 3;\n        max_stripes = (uint16_t)min(0xffff, Vcb->superblock.num_devices);\n        sub_stripes = 1;\n        type = BLOCK_FLAG_RAID5;\n        allowed_missing = 1;\n    } else if (flags & BLOCK_FLAG_RAID6) {\n        min_stripes = 4;\n        max_stripes = 257;\n        sub_stripes = 1;\n        type = BLOCK_FLAG_RAID6;\n        allowed_missing = 2;\n    } else if (flags & BLOCK_FLAG_RAID1C3) {\n        min_stripes = 3;\n        max_stripes = 3;\n        sub_stripes = 1;\n        type = BLOCK_FLAG_RAID1C3;\n        allowed_missing = 2;\n    } else if (flags & BLOCK_FLAG_RAID1C4) {\n        min_stripes = 4;\n        max_stripes = 4;\n        sub_stripes = 1;\n        type = BLOCK_FLAG_RAID1C4;\n        allowed_missing = 3;\n    } else { // SINGLE\n        min_stripes = 1;\n        max_stripes = 1;\n        sub_stripes = 1;\n        type = 0;\n        allowed_missing = 0;\n    }\n\n    if (max_chunk_size > total_size / 10) {  // cap at 10%\n        max_chunk_size = total_size / 10;\n        max_stripe_size = max_chunk_size / min_stripes;\n    }\n\n    if (max_stripe_size > total_size / (10 * min_stripes))\n        max_stripe_size = total_size / (10 * min_stripes);\n\n    TRACE(\"would allocate a new chunk of %I64x bytes and stripe %I64x\\n\", max_chunk_size, max_stripe_size);\n\n    stripes = ExAllocatePoolWithTag(PagedPool, sizeof(stripe) * max_stripes, ALLOC_TAG);\n    if (!stripes) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    num_stripes = 0;\n\n    if (type == BLOCK_FLAG_DUPLICATE) {\n        if (!find_new_dup_stripes(Vcb, stripes, max_stripe_size, full_size)) {\n            Status = STATUS_DISK_FULL;\n            goto end;\n        } else\n            num_stripes = max_stripes;\n    } else {\n        for (i = 0; i < max_stripes; i++) {\n            if (!find_new_stripe(Vcb, stripes, i, max_stripe_size, false, full_size))\n                break;\n            else\n                num_stripes++;\n        }\n    }\n\n    if (num_stripes < min_stripes && Vcb->options.allow_degraded && allowed_missing > 0) {\n        uint16_t added_missing = 0;\n\n        for (i = num_stripes; i < max_stripes; i++) {\n            if (!find_new_stripe(Vcb, stripes, i, max_stripe_size, true, full_size))\n                break;\n            else {\n                added_missing++;\n                if (added_missing >= allowed_missing)\n                    break;\n            }\n        }\n\n        num_stripes += added_missing;\n    }\n\n    // for RAID10, round down to an even number of stripes\n    if (type == BLOCK_FLAG_RAID10 && (num_stripes % sub_stripes) != 0) {\n        num_stripes -= num_stripes % sub_stripes;\n    }\n\n    if (num_stripes < min_stripes) {\n        WARN(\"found %u stripes, needed at least %u\\n\", num_stripes, min_stripes);\n        Status = STATUS_DISK_FULL;\n        goto end;\n    }\n\n    c = ExAllocatePoolWithTag(NonPagedPool, sizeof(chunk), ALLOC_TAG);\n    if (!c) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    c->devices = NULL;\n\n    cisize = sizeof(CHUNK_ITEM) + (num_stripes * sizeof(CHUNK_ITEM_STRIPE));\n    c->chunk_item = ExAllocatePoolWithTag(NonPagedPool, cisize, ALLOC_TAG);\n    if (!c->chunk_item) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    stripe_length = 0x10000; // FIXME? BTRFS_STRIPE_LEN in kernel\n\n    if (type == BLOCK_FLAG_DUPLICATE && stripes[1].dh == stripes[0].dh)\n        stripe_size = min(stripes[0].dh->size / 2, max_stripe_size);\n    else {\n        stripe_size = max_stripe_size;\n        for (i = 0; i < num_stripes; i++) {\n            if (stripes[i].dh->size < stripe_size)\n                stripe_size = stripes[i].dh->size;\n        }\n    }\n\n    if (type == BLOCK_FLAG_RAID0)\n        factor = num_stripes;\n    else if (type == BLOCK_FLAG_RAID10)\n        factor = num_stripes / sub_stripes;\n    else if (type == BLOCK_FLAG_RAID5)\n        factor = num_stripes - 1;\n    else if (type == BLOCK_FLAG_RAID6)\n        factor = num_stripes - 2;\n    else\n        factor = 1; // SINGLE, DUPLICATE, RAID1, RAID1C3, RAID1C4\n\n    if (stripe_size * factor > max_chunk_size)\n        stripe_size = max_chunk_size / factor;\n\n    if (stripe_size % stripe_length > 0)\n        stripe_size -= stripe_size % stripe_length;\n\n    if (stripe_size == 0) {\n        ERR(\"not enough free space found (stripe_size == 0)\\n\");\n        Status = STATUS_DISK_FULL;\n        goto end;\n    }\n\n    c->chunk_item->size = stripe_size * factor;\n    c->chunk_item->root_id = Vcb->extent_root->id;\n    c->chunk_item->stripe_length = stripe_length;\n    c->chunk_item->type = flags;\n    c->chunk_item->opt_io_alignment = (uint32_t)c->chunk_item->stripe_length;\n    c->chunk_item->opt_io_width = (uint32_t)c->chunk_item->stripe_length;\n    c->chunk_item->sector_size = stripes[0].device->devitem.minimal_io_size;\n    c->chunk_item->num_stripes = num_stripes;\n    c->chunk_item->sub_stripes = sub_stripes;\n\n    c->devices = ExAllocatePoolWithTag(NonPagedPool, sizeof(device*) * num_stripes, ALLOC_TAG);\n    if (!c->devices) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n    for (i = 0; i < num_stripes; i++) {\n        cis[i].dev_id = stripes[i].device->devitem.dev_id;\n\n        if (type == BLOCK_FLAG_DUPLICATE && i == 1 && stripes[i].dh == stripes[0].dh)\n            cis[i].offset = stripes[0].dh->address + stripe_size;\n        else\n            cis[i].offset = stripes[i].dh->address;\n\n        cis[i].dev_uuid = stripes[i].device->devitem.device_uuid;\n\n        c->devices[i] = stripes[i].device;\n    }\n\n    logaddr = find_new_chunk_address(Vcb, c->chunk_item->size);\n\n    Vcb->superblock.chunk_root_generation = Vcb->superblock.generation;\n\n    c->size = cisize;\n    c->offset = logaddr;\n    c->used = c->oldused = 0;\n    c->cache = c->old_cache = NULL;\n    c->readonly = false;\n    c->reloc = false;\n    c->last_alloc_set = false;\n    c->last_stripe = 0;\n    c->cache_loaded = true;\n    c->changed = false;\n    c->space_changed = false;\n    c->balance_num = 0;\n\n    InitializeListHead(&c->space);\n    InitializeListHead(&c->space_size);\n    InitializeListHead(&c->deleting);\n    InitializeListHead(&c->changed_extents);\n\n    InitializeListHead(&c->range_locks);\n    ExInitializeResourceLite(&c->range_locks_lock);\n    KeInitializeEvent(&c->range_locks_event, NotificationEvent, false);\n\n    InitializeListHead(&c->partial_stripes);\n    ExInitializeResourceLite(&c->partial_stripes_lock);\n\n    ExInitializeResourceLite(&c->lock);\n    ExInitializeResourceLite(&c->changed_extents_lock);\n\n    s = ExAllocatePoolWithTag(NonPagedPool, sizeof(space), ALLOC_TAG);\n    if (!s) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    s->address = c->offset;\n    s->size = c->chunk_item->size;\n    InsertTailList(&c->space, &s->list_entry);\n    InsertTailList(&c->space_size, &s->list_entry_size);\n\n    protect_superblocks(c);\n\n    for (i = 0; i < num_stripes; i++) {\n        stripes[i].device->devitem.bytes_used += stripe_size;\n\n        space_list_subtract2(&stripes[i].device->space, NULL, cis[i].offset, stripe_size, NULL, NULL);\n    }\n\n    Status = STATUS_SUCCESS;\n\n    if (flags & BLOCK_FLAG_RAID5 || flags & BLOCK_FLAG_RAID6)\n        Vcb->superblock.incompat_flags |= BTRFS_INCOMPAT_FLAGS_RAID56;\n\nend:\n    if (stripes)\n        ExFreePool(stripes);\n\n    if (!NT_SUCCESS(Status)) {\n        if (c) {\n            if (c->devices)\n                ExFreePool(c->devices);\n\n            if (c->chunk_item)\n                ExFreePool(c->chunk_item);\n\n            ExFreePool(c);\n        }\n\n        if (s) ExFreePool(s);\n    } else {\n        bool done = false;\n\n        le = Vcb->chunks.Flink;\n        while (le != &Vcb->chunks) {\n            chunk* c2 = CONTAINING_RECORD(le, chunk, list_entry);\n\n            if (c2->offset > c->offset) {\n                InsertHeadList(le->Blink, &c->list_entry);\n                done = true;\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        if (!done)\n            InsertTailList(&Vcb->chunks, &c->list_entry);\n\n        c->created = true;\n        c->changed = true;\n        c->space_changed = true;\n        c->list_entry_balance.Flink = NULL;\n\n        *pc = c;\n    }\n\n    return Status;\n}\n\n__attribute__((nonnull(1,3,5,8)))\nstatic NTSTATUS prepare_raid0_write(_Pre_satisfies_(_Curr_->chunk_item->num_stripes>0) _In_ chunk* c, _In_ uint64_t address, _In_reads_bytes_(length) void* data,\n                                    _In_ uint32_t length, _In_ write_stripe* stripes, _In_ PIRP Irp, _In_ uint64_t irp_offset, _In_ write_data_context* wtc) {\n    uint64_t startoff, endoff;\n    uint16_t startoffstripe, endoffstripe, stripenum;\n    uint64_t pos, *stripeoff;\n    uint32_t i;\n    bool file_write = Irp && Irp->MdlAddress && (Irp->MdlAddress->ByteOffset == 0);\n    PMDL master_mdl;\n    PFN_NUMBER* pfns;\n\n    stripeoff = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t) * c->chunk_item->num_stripes, ALLOC_TAG);\n    if (!stripeoff) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    get_raid0_offset(address - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &startoff, &startoffstripe);\n    get_raid0_offset(address + length - c->offset - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes, &endoff, &endoffstripe);\n\n    if (file_write) {\n        master_mdl = Irp->MdlAddress;\n\n        pfns = (PFN_NUMBER*)(Irp->MdlAddress + 1);\n        pfns = &pfns[irp_offset >> PAGE_SHIFT];\n    } else if (((ULONG_PTR)data % PAGE_SIZE) != 0) {\n        wtc->scratch = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG);\n        if (!wtc->scratch) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(wtc->scratch, data, length);\n\n        master_mdl = IoAllocateMdl(wtc->scratch, length, false, false, NULL);\n        if (!master_mdl) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        MmBuildMdlForNonPagedPool(master_mdl);\n\n        wtc->mdl = master_mdl;\n\n        pfns = (PFN_NUMBER*)(master_mdl + 1);\n    } else {\n        NTSTATUS Status = STATUS_SUCCESS;\n\n        master_mdl = IoAllocateMdl(data, length, false, false, NULL);\n        if (!master_mdl) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        try {\n            MmProbeAndLockPages(master_mdl, KernelMode, IoReadAccess);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n            IoFreeMdl(master_mdl);\n            return Status;\n        }\n\n        wtc->mdl = master_mdl;\n\n        pfns = (PFN_NUMBER*)(master_mdl + 1);\n    }\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (startoffstripe > i)\n            stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n        else if (startoffstripe == i)\n            stripes[i].start = startoff;\n        else\n            stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length);\n\n        if (endoffstripe > i)\n            stripes[i].end = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n        else if (endoffstripe == i)\n            stripes[i].end = endoff + 1;\n        else\n            stripes[i].end = endoff - (endoff % c->chunk_item->stripe_length);\n\n        if (stripes[i].start != stripes[i].end) {\n            stripes[i].mdl = IoAllocateMdl(NULL, (ULONG)(stripes[i].end - stripes[i].start), false, false, NULL);\n            if (!stripes[i].mdl) {\n                ERR(\"IoAllocateMdl failed\\n\");\n                ExFreePool(stripeoff);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n        }\n    }\n\n    pos = 0;\n    RtlZeroMemory(stripeoff, sizeof(uint64_t) * c->chunk_item->num_stripes);\n\n    stripenum = startoffstripe;\n\n    while (pos < length) {\n        PFN_NUMBER* stripe_pfns = (PFN_NUMBER*)(stripes[stripenum].mdl + 1);\n\n        if (pos == 0) {\n            uint32_t writelen = (uint32_t)min(stripes[stripenum].end - stripes[stripenum].start,\n                                          c->chunk_item->stripe_length - (stripes[stripenum].start % c->chunk_item->stripe_length));\n\n            RtlCopyMemory(stripe_pfns, pfns, writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n            stripeoff[stripenum] += writelen;\n            pos += writelen;\n        } else if (length - pos < c->chunk_item->stripe_length) {\n            RtlCopyMemory(&stripe_pfns[stripeoff[stripenum] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)((length - pos) * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n            break;\n        } else {\n            RtlCopyMemory(&stripe_pfns[stripeoff[stripenum] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n\n            stripeoff[stripenum] += c->chunk_item->stripe_length;\n            pos += c->chunk_item->stripe_length;\n        }\n\n        stripenum = (stripenum + 1) % c->chunk_item->num_stripes;\n    }\n\n    ExFreePool(stripeoff);\n\n    return STATUS_SUCCESS;\n}\n\n__attribute__((nonnull(1,3,5,8)))\nstatic NTSTATUS prepare_raid10_write(_Pre_satisfies_(_Curr_->chunk_item->sub_stripes>0&&_Curr_->chunk_item->num_stripes>=_Curr_->chunk_item->sub_stripes) _In_ chunk* c,\n                                     _In_ uint64_t address, _In_reads_bytes_(length) void* data, _In_ uint32_t length, _In_ write_stripe* stripes,\n                                     _In_ PIRP Irp, _In_ uint64_t irp_offset, _In_ write_data_context* wtc) {\n    uint64_t startoff, endoff;\n    uint16_t startoffstripe, endoffstripe, stripenum;\n    uint64_t pos, *stripeoff;\n    uint32_t i;\n    bool file_write = Irp && Irp->MdlAddress && (Irp->MdlAddress->ByteOffset == 0);\n    PMDL master_mdl;\n    PFN_NUMBER* pfns;\n\n    get_raid0_offset(address - c->offset, c->chunk_item->stripe_length, c->chunk_item->num_stripes / c->chunk_item->sub_stripes, &startoff, &startoffstripe);\n    get_raid0_offset(address + length - c->offset - 1, c->chunk_item->stripe_length, c->chunk_item->num_stripes / c->chunk_item->sub_stripes, &endoff, &endoffstripe);\n\n    stripenum = startoffstripe;\n    startoffstripe *= c->chunk_item->sub_stripes;\n    endoffstripe *= c->chunk_item->sub_stripes;\n\n    if (file_write) {\n        master_mdl = Irp->MdlAddress;\n\n        pfns = (PFN_NUMBER*)(Irp->MdlAddress + 1);\n        pfns = &pfns[irp_offset >> PAGE_SHIFT];\n    } else if (((ULONG_PTR)data % PAGE_SIZE) != 0) {\n        wtc->scratch = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG);\n        if (!wtc->scratch) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(wtc->scratch, data, length);\n\n        master_mdl = IoAllocateMdl(wtc->scratch, length, false, false, NULL);\n        if (!master_mdl) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        MmBuildMdlForNonPagedPool(master_mdl);\n\n        wtc->mdl = master_mdl;\n\n        pfns = (PFN_NUMBER*)(master_mdl + 1);\n    } else {\n        NTSTATUS Status = STATUS_SUCCESS;\n\n        master_mdl = IoAllocateMdl(data, length, false, false, NULL);\n        if (!master_mdl) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        try {\n            MmProbeAndLockPages(master_mdl, KernelMode, IoReadAccess);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n            IoFreeMdl(master_mdl);\n            return Status;\n        }\n\n        wtc->mdl = master_mdl;\n\n        pfns = (PFN_NUMBER*)(master_mdl + 1);\n    }\n\n    for (i = 0; i < c->chunk_item->num_stripes; i += c->chunk_item->sub_stripes) {\n        uint16_t j;\n\n        if (startoffstripe > i)\n            stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n        else if (startoffstripe == i)\n            stripes[i].start = startoff;\n        else\n            stripes[i].start = startoff - (startoff % c->chunk_item->stripe_length);\n\n        if (endoffstripe > i)\n            stripes[i].end = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n        else if (endoffstripe == i)\n            stripes[i].end = endoff + 1;\n        else\n            stripes[i].end = endoff - (endoff % c->chunk_item->stripe_length);\n\n        stripes[i].mdl = IoAllocateMdl(NULL, (ULONG)(stripes[i].end - stripes[i].start), false, false, NULL);\n        if (!stripes[i].mdl) {\n            ERR(\"IoAllocateMdl failed\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        for (j = 1; j < c->chunk_item->sub_stripes; j++) {\n            stripes[i+j].start = stripes[i].start;\n            stripes[i+j].end = stripes[i].end;\n            stripes[i+j].data = stripes[i].data;\n            stripes[i+j].mdl = stripes[i].mdl;\n        }\n    }\n\n    pos = 0;\n\n    stripeoff = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t) * c->chunk_item->num_stripes / c->chunk_item->sub_stripes, ALLOC_TAG);\n    if (!stripeoff) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(stripeoff, sizeof(uint64_t) * c->chunk_item->num_stripes / c->chunk_item->sub_stripes);\n\n    while (pos < length) {\n        PFN_NUMBER* stripe_pfns = (PFN_NUMBER*)(stripes[stripenum * c->chunk_item->sub_stripes].mdl + 1);\n\n        if (pos == 0) {\n            uint32_t writelen = (uint32_t)min(stripes[stripenum * c->chunk_item->sub_stripes].end - stripes[stripenum * c->chunk_item->sub_stripes].start,\n                                          c->chunk_item->stripe_length - (stripes[stripenum * c->chunk_item->sub_stripes].start % c->chunk_item->stripe_length));\n\n            RtlCopyMemory(stripe_pfns, pfns, writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n            stripeoff[stripenum] += writelen;\n            pos += writelen;\n        } else if (length - pos < c->chunk_item->stripe_length) {\n            RtlCopyMemory(&stripe_pfns[stripeoff[stripenum] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)((length - pos) * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n            break;\n        } else {\n            RtlCopyMemory(&stripe_pfns[stripeoff[stripenum] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n\n            stripeoff[stripenum] += c->chunk_item->stripe_length;\n            pos += c->chunk_item->stripe_length;\n        }\n\n        stripenum = (stripenum + 1) % (c->chunk_item->num_stripes / c->chunk_item->sub_stripes);\n    }\n\n    ExFreePool(stripeoff);\n\n    return STATUS_SUCCESS;\n}\n\n__attribute__((nonnull(1,2,5)))\nstatic NTSTATUS add_partial_stripe(device_extension* Vcb, chunk* c, uint64_t address, uint32_t length, void* data) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    partial_stripe* ps;\n    uint64_t stripe_addr;\n    uint16_t num_data_stripes;\n\n    num_data_stripes = c->chunk_item->num_stripes - (c->chunk_item->type & BLOCK_FLAG_RAID5 ? 1 : 2);\n    stripe_addr = address - ((address - c->offset) % (num_data_stripes * c->chunk_item->stripe_length));\n\n    ExAcquireResourceExclusiveLite(&c->partial_stripes_lock, true);\n\n    le = c->partial_stripes.Flink;\n    while (le != &c->partial_stripes) {\n        ps = CONTAINING_RECORD(le, partial_stripe, list_entry);\n\n        if (ps->address == stripe_addr) {\n            // update existing entry\n\n            RtlCopyMemory(ps->data + address - stripe_addr, data, length);\n            RtlClearBits(&ps->bmp, (ULONG)((address - stripe_addr) >> Vcb->sector_shift), length >> Vcb->sector_shift);\n\n            // if now filled, flush\n            if (RtlAreBitsClear(&ps->bmp, 0, (ULONG)((num_data_stripes * c->chunk_item->stripe_length) >> Vcb->sector_shift))) {\n                Status = flush_partial_stripe(Vcb, c, ps);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"flush_partial_stripe returned %08lx\\n\", Status);\n                    goto end;\n                }\n\n                RemoveEntryList(&ps->list_entry);\n\n                if (ps->bmparr)\n                    ExFreePool(ps->bmparr);\n\n                ExFreePool(ps);\n            }\n\n            Status = STATUS_SUCCESS;\n            goto end;\n        } else if (ps->address > stripe_addr)\n            break;\n\n        le = le->Flink;\n    }\n\n    // add new entry\n\n    ps = ExAllocatePoolWithTag(NonPagedPool, offsetof(partial_stripe, data[0]) + (ULONG)(num_data_stripes * c->chunk_item->stripe_length), ALLOC_TAG);\n    if (!ps) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    ps->bmplen = (ULONG)(num_data_stripes * c->chunk_item->stripe_length) >> Vcb->sector_shift;\n\n    ps->address = stripe_addr;\n    ps->bmparr = ExAllocatePoolWithTag(NonPagedPool, (size_t)sector_align(((ps->bmplen / 8) + 1), sizeof(ULONG)), ALLOC_TAG);\n    if (!ps->bmparr) {\n        ERR(\"out of memory\\n\");\n        ExFreePool(ps);\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto end;\n    }\n\n    RtlInitializeBitMap(&ps->bmp, ps->bmparr, ps->bmplen);\n    RtlSetAllBits(&ps->bmp);\n\n    RtlCopyMemory(ps->data + address - stripe_addr, data, length);\n    RtlClearBits(&ps->bmp, (ULONG)((address - stripe_addr) >> Vcb->sector_shift), length >> Vcb->sector_shift);\n\n    InsertHeadList(le->Blink, &ps->list_entry);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    ExReleaseResourceLite(&c->partial_stripes_lock);\n\n    return Status;\n}\n\ntypedef struct {\n    PMDL mdl;\n    PFN_NUMBER* pfns;\n} log_stripe;\n\n__attribute__((nonnull(1,2,4,6,10)))\nstatic NTSTATUS prepare_raid5_write(device_extension* Vcb, chunk* c, uint64_t address, void* data, uint32_t length, write_stripe* stripes, PIRP Irp,\n                                    uint64_t irp_offset, ULONG priority, write_data_context* wtc) {\n    uint64_t startoff, endoff, parity_start, parity_end;\n    uint16_t startoffstripe, endoffstripe, parity, num_data_stripes = c->chunk_item->num_stripes - 1;\n    uint64_t pos, parity_pos, *stripeoff = NULL;\n    uint32_t i;\n    bool file_write = Irp && Irp->MdlAddress && (Irp->MdlAddress->ByteOffset == 0);\n    PMDL master_mdl;\n    NTSTATUS Status;\n    PFN_NUMBER *pfns, *parity_pfns;\n    log_stripe* log_stripes = NULL;\n\n    if ((address + length - c->offset) % (num_data_stripes * c->chunk_item->stripe_length) > 0) {\n        uint64_t delta = (address + length - c->offset) % (num_data_stripes * c->chunk_item->stripe_length);\n\n        delta = min(length, delta);\n        Status = add_partial_stripe(Vcb, c, address + length - delta, (uint32_t)delta, (uint8_t*)data + length - delta);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_partial_stripe returned %08lx\\n\", Status);\n            goto exit;\n        }\n\n        length -= (uint32_t)delta;\n    }\n\n    if (length > 0 && (address - c->offset) % (num_data_stripes * c->chunk_item->stripe_length) > 0) {\n        uint64_t delta = (num_data_stripes * c->chunk_item->stripe_length) - ((address - c->offset) % (num_data_stripes * c->chunk_item->stripe_length));\n\n        Status = add_partial_stripe(Vcb, c, address, (uint32_t)delta, data);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_partial_stripe returned %08lx\\n\", Status);\n            goto exit;\n        }\n\n        address += delta;\n        length -= (uint32_t)delta;\n        irp_offset += delta;\n        data = (uint8_t*)data + delta;\n    }\n\n    if (length == 0) {\n        Status = STATUS_SUCCESS;\n        goto exit;\n    }\n\n    get_raid0_offset(address - c->offset, c->chunk_item->stripe_length, num_data_stripes, &startoff, &startoffstripe);\n    get_raid0_offset(address + length - c->offset - 1, c->chunk_item->stripe_length, num_data_stripes, &endoff, &endoffstripe);\n\n    pos = 0;\n    while (pos < length) {\n        parity = (((address - c->offset + pos) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes;\n\n        if (pos == 0) {\n            uint16_t stripe = (parity + startoffstripe + 1) % c->chunk_item->num_stripes;\n            ULONG skip, writelen;\n\n            i = startoffstripe;\n            while (stripe != parity) {\n                if (i == startoffstripe) {\n                    writelen = (ULONG)min(length, c->chunk_item->stripe_length - (startoff % c->chunk_item->stripe_length));\n\n                    stripes[stripe].start = startoff;\n                    stripes[stripe].end = startoff + writelen;\n\n                    pos += writelen;\n\n                    if (pos == length)\n                        break;\n                } else {\n                    writelen = (ULONG)min(length - pos, c->chunk_item->stripe_length);\n\n                    stripes[stripe].start = startoff - (startoff % c->chunk_item->stripe_length);\n                    stripes[stripe].end = stripes[stripe].start + writelen;\n\n                    pos += writelen;\n\n                    if (pos == length)\n                        break;\n                }\n\n                i++;\n                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n            }\n\n            if (pos == length)\n                break;\n\n            for (i = 0; i < startoffstripe; i++) {\n                stripe = (parity + i + 1) % c->chunk_item->num_stripes;\n\n                stripes[stripe].start = stripes[stripe].end = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n            }\n\n            stripes[parity].start = stripes[parity].end = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n\n            if (length - pos > c->chunk_item->num_stripes * num_data_stripes * c->chunk_item->stripe_length) {\n                skip = (ULONG)(((length - pos) / (c->chunk_item->num_stripes * num_data_stripes * c->chunk_item->stripe_length)) - 1);\n\n                for (i = 0; i < c->chunk_item->num_stripes; i++) {\n                    stripes[i].end += skip * c->chunk_item->num_stripes * c->chunk_item->stripe_length;\n                }\n\n                pos += skip * num_data_stripes * c->chunk_item->num_stripes * c->chunk_item->stripe_length;\n            }\n        } else if (length - pos >= c->chunk_item->stripe_length * num_data_stripes) {\n            for (i = 0; i < c->chunk_item->num_stripes; i++) {\n                stripes[i].end += c->chunk_item->stripe_length;\n            }\n\n            pos += c->chunk_item->stripe_length * num_data_stripes;\n        } else {\n            uint16_t stripe = (parity + 1) % c->chunk_item->num_stripes;\n\n            i = 0;\n            while (stripe != parity) {\n                if (endoffstripe == i) {\n                    stripes[stripe].end = endoff + 1;\n                    break;\n                } else if (endoffstripe > i)\n                    stripes[stripe].end = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n\n                i++;\n                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n            }\n\n            break;\n        }\n    }\n\n    parity_start = 0xffffffffffffffff;\n    parity_end = 0;\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (stripes[i].start != 0 || stripes[i].end != 0) {\n            parity_start = min(stripes[i].start, parity_start);\n            parity_end = max(stripes[i].end, parity_end);\n        }\n    }\n\n    if (parity_end == parity_start) {\n        Status = STATUS_SUCCESS;\n        goto exit;\n    }\n\n    parity = (((address - c->offset) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes;\n    stripes[parity].start = parity_start;\n\n    parity = (((address - c->offset + length - 1) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes;\n    stripes[parity].end = parity_end;\n\n    log_stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(log_stripe) * num_data_stripes, ALLOC_TAG);\n    if (!log_stripes) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    RtlZeroMemory(log_stripes, sizeof(log_stripe) * num_data_stripes);\n\n    for (i = 0; i < num_data_stripes; i++) {\n        log_stripes[i].mdl = IoAllocateMdl(NULL, (ULONG)(parity_end - parity_start), false, false, NULL);\n        if (!log_stripes[i].mdl) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        log_stripes[i].mdl->MdlFlags |= MDL_PARTIAL;\n        log_stripes[i].pfns = (PFN_NUMBER*)(log_stripes[i].mdl + 1);\n    }\n\n    wtc->parity1 = ExAllocatePoolWithTag(NonPagedPool, (ULONG)(parity_end - parity_start), ALLOC_TAG);\n    if (!wtc->parity1) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    wtc->parity1_mdl = IoAllocateMdl(wtc->parity1, (ULONG)(parity_end - parity_start), false, false, NULL);\n    if (!wtc->parity1_mdl) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    MmBuildMdlForNonPagedPool(wtc->parity1_mdl);\n\n    if (file_write)\n        master_mdl = Irp->MdlAddress;\n    else if (((ULONG_PTR)data % PAGE_SIZE) != 0) {\n        wtc->scratch = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG);\n        if (!wtc->scratch) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        RtlCopyMemory(wtc->scratch, data, length);\n\n        master_mdl = IoAllocateMdl(wtc->scratch, length, false, false, NULL);\n        if (!master_mdl) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        MmBuildMdlForNonPagedPool(master_mdl);\n\n        wtc->mdl = master_mdl;\n    } else {\n        master_mdl = IoAllocateMdl(data, length, false, false, NULL);\n        if (!master_mdl) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        Status = STATUS_SUCCESS;\n\n        try {\n            MmProbeAndLockPages(master_mdl, KernelMode, IoReadAccess);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n            IoFreeMdl(master_mdl);\n            return Status;\n        }\n\n        wtc->mdl = master_mdl;\n    }\n\n    pfns = (PFN_NUMBER*)(master_mdl + 1);\n    parity_pfns = (PFN_NUMBER*)(wtc->parity1_mdl + 1);\n\n    if (file_write)\n        pfns = &pfns[irp_offset >> PAGE_SHIFT];\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (stripes[i].start != stripes[i].end) {\n            stripes[i].mdl = IoAllocateMdl((uint8_t*)MmGetMdlVirtualAddress(master_mdl) + irp_offset, (ULONG)(stripes[i].end - stripes[i].start), false, false, NULL);\n            if (!stripes[i].mdl) {\n                ERR(\"IoAllocateMdl failed\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n        }\n    }\n\n    stripeoff = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t) * c->chunk_item->num_stripes, ALLOC_TAG);\n    if (!stripeoff) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    RtlZeroMemory(stripeoff, sizeof(uint64_t) * c->chunk_item->num_stripes);\n\n    pos = 0;\n    parity_pos = 0;\n\n    while (pos < length) {\n        PFN_NUMBER* stripe_pfns;\n\n        parity = (((address - c->offset + pos) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes;\n\n        if (pos == 0) {\n            uint16_t stripe = (parity + startoffstripe + 1) % c->chunk_item->num_stripes;\n            uint32_t writelen = (uint32_t)min(length - pos, min(stripes[stripe].end - stripes[stripe].start,\n                                                            c->chunk_item->stripe_length - (stripes[stripe].start % c->chunk_item->stripe_length)));\n            uint32_t maxwritelen = writelen;\n\n            stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1);\n\n            RtlCopyMemory(stripe_pfns, pfns, writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n            RtlCopyMemory(log_stripes[startoffstripe].pfns, pfns, writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n            log_stripes[startoffstripe].pfns += writelen >> PAGE_SHIFT;\n\n            stripeoff[stripe] = writelen;\n            pos += writelen;\n\n            stripe = (stripe + 1) % c->chunk_item->num_stripes;\n            i = startoffstripe + 1;\n\n            while (stripe != parity) {\n                stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1);\n                writelen = (uint32_t)min(length - pos, min(stripes[stripe].end - stripes[stripe].start, c->chunk_item->stripe_length));\n\n                if (writelen == 0)\n                    break;\n\n                if (writelen > maxwritelen)\n                    maxwritelen = writelen;\n\n                RtlCopyMemory(stripe_pfns, &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                RtlCopyMemory(log_stripes[i].pfns, &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n                log_stripes[i].pfns += writelen >> PAGE_SHIFT;\n\n                stripeoff[stripe] = writelen;\n                pos += writelen;\n\n                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n                i++;\n            }\n\n            stripe_pfns = (PFN_NUMBER*)(stripes[parity].mdl + 1);\n\n            RtlCopyMemory(stripe_pfns, parity_pfns, maxwritelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n            stripeoff[parity] = maxwritelen;\n            parity_pos = maxwritelen;\n        } else if (length - pos >= c->chunk_item->stripe_length * num_data_stripes) {\n            uint16_t stripe = (parity + 1) % c->chunk_item->num_stripes;\n\n            i = 0;\n            while (stripe != parity) {\n                stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1);\n\n                RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n\n                RtlCopyMemory(log_stripes[i].pfns, &pfns[pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n                log_stripes[i].pfns += c->chunk_item->stripe_length >> PAGE_SHIFT;\n\n                stripeoff[stripe] += c->chunk_item->stripe_length;\n                pos += c->chunk_item->stripe_length;\n\n                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n                i++;\n            }\n\n            stripe_pfns = (PFN_NUMBER*)(stripes[parity].mdl + 1);\n\n            RtlCopyMemory(&stripe_pfns[stripeoff[parity] >> PAGE_SHIFT], &parity_pfns[parity_pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n            stripeoff[parity] += c->chunk_item->stripe_length;\n            parity_pos += c->chunk_item->stripe_length;\n        } else {\n            uint16_t stripe = (parity + 1) % c->chunk_item->num_stripes;\n            uint32_t writelen, maxwritelen = 0;\n\n            i = 0;\n            while (pos < length) {\n                stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1);\n                writelen = (uint32_t)min(length - pos, min(stripes[stripe].end - stripes[stripe].start, c->chunk_item->stripe_length));\n\n                if (writelen == 0)\n                    break;\n\n                if (writelen > maxwritelen)\n                    maxwritelen = writelen;\n\n                RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                RtlCopyMemory(log_stripes[i].pfns, &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n                log_stripes[i].pfns += writelen >> PAGE_SHIFT;\n\n                stripeoff[stripe] += writelen;\n                pos += writelen;\n\n                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n                i++;\n            }\n\n            stripe_pfns = (PFN_NUMBER*)(stripes[parity].mdl + 1);\n\n            RtlCopyMemory(&stripe_pfns[stripeoff[parity] >> PAGE_SHIFT], &parity_pfns[parity_pos >> PAGE_SHIFT], maxwritelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n        }\n    }\n\n    for (i = 0; i < num_data_stripes; i++) {\n        uint8_t* ss = MmGetSystemAddressForMdlSafe(log_stripes[i].mdl, priority);\n\n        if (i == 0)\n            RtlCopyMemory(wtc->parity1, ss, (uint32_t)(parity_end - parity_start));\n        else\n            do_xor(wtc->parity1, ss, (uint32_t)(parity_end - parity_start));\n    }\n\n    Status = STATUS_SUCCESS;\n\nexit:\n    if (log_stripes) {\n        for (i = 0; i < num_data_stripes; i++) {\n            if (log_stripes[i].mdl)\n                IoFreeMdl(log_stripes[i].mdl);\n        }\n\n        ExFreePool(log_stripes);\n    }\n\n    if (stripeoff)\n        ExFreePool(stripeoff);\n\n    return Status;\n}\n\n__attribute__((nonnull(1,2,4,6,10)))\nstatic NTSTATUS prepare_raid6_write(device_extension* Vcb, chunk* c, uint64_t address, void* data, uint32_t length, write_stripe* stripes, PIRP Irp,\n                                    uint64_t irp_offset, ULONG priority, write_data_context* wtc) {\n    uint64_t startoff, endoff, parity_start, parity_end;\n    uint16_t startoffstripe, endoffstripe, parity1, num_data_stripes = c->chunk_item->num_stripes - 2;\n    uint64_t pos, parity_pos, *stripeoff = NULL;\n    uint32_t i;\n    bool file_write = Irp && Irp->MdlAddress && (Irp->MdlAddress->ByteOffset == 0);\n    PMDL master_mdl;\n    NTSTATUS Status;\n    PFN_NUMBER *pfns, *parity1_pfns, *parity2_pfns;\n    log_stripe* log_stripes = NULL;\n\n    if ((address + length - c->offset) % (num_data_stripes * c->chunk_item->stripe_length) > 0) {\n        uint64_t delta = (address + length - c->offset) % (num_data_stripes * c->chunk_item->stripe_length);\n\n        delta = min(length, delta);\n        Status = add_partial_stripe(Vcb, c, address + length - delta, (uint32_t)delta, (uint8_t*)data + length - delta);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_partial_stripe returned %08lx\\n\", Status);\n            goto exit;\n        }\n\n        length -= (uint32_t)delta;\n    }\n\n    if (length > 0 && (address - c->offset) % (num_data_stripes * c->chunk_item->stripe_length) > 0) {\n        uint64_t delta = (num_data_stripes * c->chunk_item->stripe_length) - ((address - c->offset) % (num_data_stripes * c->chunk_item->stripe_length));\n\n        Status = add_partial_stripe(Vcb, c, address, (uint32_t)delta, data);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"add_partial_stripe returned %08lx\\n\", Status);\n            goto exit;\n        }\n\n        address += delta;\n        length -= (uint32_t)delta;\n        irp_offset += delta;\n        data = (uint8_t*)data + delta;\n    }\n\n    if (length == 0) {\n        Status = STATUS_SUCCESS;\n        goto exit;\n    }\n\n    get_raid0_offset(address - c->offset, c->chunk_item->stripe_length, num_data_stripes, &startoff, &startoffstripe);\n    get_raid0_offset(address + length - c->offset - 1, c->chunk_item->stripe_length, num_data_stripes, &endoff, &endoffstripe);\n\n    pos = 0;\n    while (pos < length) {\n        parity1 = (((address - c->offset + pos) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes;\n\n        if (pos == 0) {\n            uint16_t stripe = (parity1 + startoffstripe + 2) % c->chunk_item->num_stripes;\n            uint16_t parity2 = (parity1 + 1) % c->chunk_item->num_stripes;\n            ULONG skip, writelen;\n\n            i = startoffstripe;\n            while (stripe != parity1) {\n                if (i == startoffstripe) {\n                    writelen = (ULONG)min(length, c->chunk_item->stripe_length - (startoff % c->chunk_item->stripe_length));\n\n                    stripes[stripe].start = startoff;\n                    stripes[stripe].end = startoff + writelen;\n\n                    pos += writelen;\n\n                    if (pos == length)\n                        break;\n                } else {\n                    writelen = (ULONG)min(length - pos, c->chunk_item->stripe_length);\n\n                    stripes[stripe].start = startoff - (startoff % c->chunk_item->stripe_length);\n                    stripes[stripe].end = stripes[stripe].start + writelen;\n\n                    pos += writelen;\n\n                    if (pos == length)\n                        break;\n                }\n\n                i++;\n                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n            }\n\n            if (pos == length)\n                break;\n\n            for (i = 0; i < startoffstripe; i++) {\n                stripe = (parity1 + i + 2) % c->chunk_item->num_stripes;\n\n                stripes[stripe].start = stripes[stripe].end = startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n            }\n\n            stripes[parity1].start = stripes[parity1].end = stripes[parity2].start = stripes[parity2].end =\n                startoff - (startoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n\n            if (length - pos > c->chunk_item->num_stripes * num_data_stripes * c->chunk_item->stripe_length) {\n                skip = (ULONG)(((length - pos) / (c->chunk_item->num_stripes * num_data_stripes * c->chunk_item->stripe_length)) - 1);\n\n                for (i = 0; i < c->chunk_item->num_stripes; i++) {\n                    stripes[i].end += skip * c->chunk_item->num_stripes * c->chunk_item->stripe_length;\n                }\n\n                pos += skip * num_data_stripes * c->chunk_item->num_stripes * c->chunk_item->stripe_length;\n            }\n        } else if (length - pos >= c->chunk_item->stripe_length * num_data_stripes) {\n            for (i = 0; i < c->chunk_item->num_stripes; i++) {\n                stripes[i].end += c->chunk_item->stripe_length;\n            }\n\n            pos += c->chunk_item->stripe_length * num_data_stripes;\n        } else {\n            uint16_t stripe = (parity1 + 2) % c->chunk_item->num_stripes;\n\n            i = 0;\n            while (stripe != parity1) {\n                if (endoffstripe == i) {\n                    stripes[stripe].end = endoff + 1;\n                    break;\n                } else if (endoffstripe > i)\n                    stripes[stripe].end = endoff - (endoff % c->chunk_item->stripe_length) + c->chunk_item->stripe_length;\n\n                i++;\n                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n            }\n\n            break;\n        }\n    }\n\n    parity_start = 0xffffffffffffffff;\n    parity_end = 0;\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (stripes[i].start != 0 || stripes[i].end != 0) {\n            parity_start = min(stripes[i].start, parity_start);\n            parity_end = max(stripes[i].end, parity_end);\n        }\n    }\n\n    if (parity_end == parity_start) {\n        Status = STATUS_SUCCESS;\n        goto exit;\n    }\n\n    parity1 = (((address - c->offset) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes;\n    stripes[parity1].start = stripes[(parity1 + 1) % c->chunk_item->num_stripes].start = parity_start;\n\n    parity1 = (((address - c->offset + length - 1) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes;\n    stripes[parity1].end = stripes[(parity1 + 1) % c->chunk_item->num_stripes].end = parity_end;\n\n    log_stripes = ExAllocatePoolWithTag(NonPagedPool, sizeof(log_stripe) * num_data_stripes, ALLOC_TAG);\n    if (!log_stripes) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    RtlZeroMemory(log_stripes, sizeof(log_stripe) * num_data_stripes);\n\n    for (i = 0; i < num_data_stripes; i++) {\n        log_stripes[i].mdl = IoAllocateMdl(NULL, (ULONG)(parity_end - parity_start), false, false, NULL);\n        if (!log_stripes[i].mdl) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        log_stripes[i].mdl->MdlFlags |= MDL_PARTIAL;\n        log_stripes[i].pfns = (PFN_NUMBER*)(log_stripes[i].mdl + 1);\n    }\n\n    wtc->parity1 = ExAllocatePoolWithTag(NonPagedPool, (ULONG)(parity_end - parity_start), ALLOC_TAG);\n    if (!wtc->parity1) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    wtc->parity2 = ExAllocatePoolWithTag(NonPagedPool, (ULONG)(parity_end - parity_start), ALLOC_TAG);\n    if (!wtc->parity2) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    wtc->parity1_mdl = IoAllocateMdl(wtc->parity1, (ULONG)(parity_end - parity_start), false, false, NULL);\n    if (!wtc->parity1_mdl) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    MmBuildMdlForNonPagedPool(wtc->parity1_mdl);\n\n    wtc->parity2_mdl = IoAllocateMdl(wtc->parity2, (ULONG)(parity_end - parity_start), false, false, NULL);\n    if (!wtc->parity2_mdl) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    MmBuildMdlForNonPagedPool(wtc->parity2_mdl);\n\n    if (file_write)\n        master_mdl = Irp->MdlAddress;\n    else if (((ULONG_PTR)data % PAGE_SIZE) != 0) {\n        wtc->scratch = ExAllocatePoolWithTag(NonPagedPool, length, ALLOC_TAG);\n        if (!wtc->scratch) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        RtlCopyMemory(wtc->scratch, data, length);\n\n        master_mdl = IoAllocateMdl(wtc->scratch, length, false, false, NULL);\n        if (!master_mdl) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        MmBuildMdlForNonPagedPool(master_mdl);\n\n        wtc->mdl = master_mdl;\n    } else {\n        master_mdl = IoAllocateMdl(data, length, false, false, NULL);\n        if (!master_mdl) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n\n        Status = STATUS_SUCCESS;\n\n        try {\n            MmProbeAndLockPages(master_mdl, KernelMode, IoReadAccess);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n            IoFreeMdl(master_mdl);\n            goto exit;\n        }\n\n        wtc->mdl = master_mdl;\n    }\n\n    pfns = (PFN_NUMBER*)(master_mdl + 1);\n    parity1_pfns = (PFN_NUMBER*)(wtc->parity1_mdl + 1);\n    parity2_pfns = (PFN_NUMBER*)(wtc->parity2_mdl + 1);\n\n    if (file_write)\n        pfns = &pfns[irp_offset >> PAGE_SHIFT];\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (stripes[i].start != stripes[i].end) {\n            stripes[i].mdl = IoAllocateMdl((uint8_t*)MmGetMdlVirtualAddress(master_mdl) + irp_offset, (ULONG)(stripes[i].end - stripes[i].start), false, false, NULL);\n            if (!stripes[i].mdl) {\n                ERR(\"IoAllocateMdl failed\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto exit;\n            }\n        }\n    }\n\n    stripeoff = ExAllocatePoolWithTag(PagedPool, sizeof(uint64_t) * c->chunk_item->num_stripes, ALLOC_TAG);\n    if (!stripeoff) {\n        ERR(\"out of memory\\n\");\n        Status = STATUS_INSUFFICIENT_RESOURCES;\n        goto exit;\n    }\n\n    RtlZeroMemory(stripeoff, sizeof(uint64_t) * c->chunk_item->num_stripes);\n\n    pos = 0;\n    parity_pos = 0;\n\n    while (pos < length) {\n        PFN_NUMBER* stripe_pfns;\n\n        parity1 = (((address - c->offset + pos) / (num_data_stripes * c->chunk_item->stripe_length)) + num_data_stripes) % c->chunk_item->num_stripes;\n\n        if (pos == 0) {\n            uint16_t stripe = (parity1 + startoffstripe + 2) % c->chunk_item->num_stripes, parity2;\n            uint32_t writelen = (uint32_t)min(length - pos, min(stripes[stripe].end - stripes[stripe].start,\n                                                            c->chunk_item->stripe_length - (stripes[stripe].start % c->chunk_item->stripe_length)));\n            uint32_t maxwritelen = writelen;\n\n            stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1);\n\n            RtlCopyMemory(stripe_pfns, pfns, writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n            RtlCopyMemory(log_stripes[startoffstripe].pfns, pfns, writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n            log_stripes[startoffstripe].pfns += writelen >> PAGE_SHIFT;\n\n            stripeoff[stripe] = writelen;\n            pos += writelen;\n\n            stripe = (stripe + 1) % c->chunk_item->num_stripes;\n            i = startoffstripe + 1;\n\n            while (stripe != parity1) {\n                stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1);\n                writelen = (uint32_t)min(length - pos, min(stripes[stripe].end - stripes[stripe].start, c->chunk_item->stripe_length));\n\n                if (writelen == 0)\n                    break;\n\n                if (writelen > maxwritelen)\n                    maxwritelen = writelen;\n\n                RtlCopyMemory(stripe_pfns, &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                RtlCopyMemory(log_stripes[i].pfns, &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n                log_stripes[i].pfns += writelen >> PAGE_SHIFT;\n\n                stripeoff[stripe] = writelen;\n                pos += writelen;\n\n                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n                i++;\n            }\n\n            stripe_pfns = (PFN_NUMBER*)(stripes[parity1].mdl + 1);\n            RtlCopyMemory(stripe_pfns, parity1_pfns, maxwritelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n            stripeoff[parity1] = maxwritelen;\n\n            parity2 = (parity1 + 1) % c->chunk_item->num_stripes;\n\n            stripe_pfns = (PFN_NUMBER*)(stripes[parity2].mdl + 1);\n            RtlCopyMemory(stripe_pfns, parity2_pfns, maxwritelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n            stripeoff[parity2] = maxwritelen;\n\n            parity_pos = maxwritelen;\n        } else if (length - pos >= c->chunk_item->stripe_length * num_data_stripes) {\n            uint16_t stripe = (parity1 + 2) % c->chunk_item->num_stripes, parity2;\n\n            i = 0;\n            while (stripe != parity1) {\n                stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1);\n\n                RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n\n                RtlCopyMemory(log_stripes[i].pfns, &pfns[pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n                log_stripes[i].pfns += c->chunk_item->stripe_length >> PAGE_SHIFT;\n\n                stripeoff[stripe] += c->chunk_item->stripe_length;\n                pos += c->chunk_item->stripe_length;\n\n                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n                i++;\n            }\n\n            stripe_pfns = (PFN_NUMBER*)(stripes[parity1].mdl + 1);\n            RtlCopyMemory(&stripe_pfns[stripeoff[parity1] >> PAGE_SHIFT], &parity1_pfns[parity_pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n            stripeoff[parity1] += c->chunk_item->stripe_length;\n\n            parity2 = (parity1 + 1) % c->chunk_item->num_stripes;\n\n            stripe_pfns = (PFN_NUMBER*)(stripes[parity2].mdl + 1);\n            RtlCopyMemory(&stripe_pfns[stripeoff[parity2] >> PAGE_SHIFT], &parity2_pfns[parity_pos >> PAGE_SHIFT], (ULONG)(c->chunk_item->stripe_length * sizeof(PFN_NUMBER) >> PAGE_SHIFT));\n            stripeoff[parity2] += c->chunk_item->stripe_length;\n\n            parity_pos += c->chunk_item->stripe_length;\n        } else {\n            uint16_t stripe = (parity1 + 2) % c->chunk_item->num_stripes, parity2;\n            uint32_t writelen, maxwritelen = 0;\n\n            i = 0;\n            while (pos < length) {\n                stripe_pfns = (PFN_NUMBER*)(stripes[stripe].mdl + 1);\n                writelen = (uint32_t)min(length - pos, min(stripes[stripe].end - stripes[stripe].start, c->chunk_item->stripe_length));\n\n                if (writelen == 0)\n                    break;\n\n                if (writelen > maxwritelen)\n                    maxwritelen = writelen;\n\n                RtlCopyMemory(&stripe_pfns[stripeoff[stripe] >> PAGE_SHIFT], &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n                RtlCopyMemory(log_stripes[i].pfns, &pfns[pos >> PAGE_SHIFT], writelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n                log_stripes[i].pfns += writelen >> PAGE_SHIFT;\n\n                stripeoff[stripe] += writelen;\n                pos += writelen;\n\n                stripe = (stripe + 1) % c->chunk_item->num_stripes;\n                i++;\n            }\n\n            stripe_pfns = (PFN_NUMBER*)(stripes[parity1].mdl + 1);\n            RtlCopyMemory(&stripe_pfns[stripeoff[parity1] >> PAGE_SHIFT], &parity1_pfns[parity_pos >> PAGE_SHIFT], maxwritelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n\n            parity2 = (parity1 + 1) % c->chunk_item->num_stripes;\n\n            stripe_pfns = (PFN_NUMBER*)(stripes[parity2].mdl + 1);\n            RtlCopyMemory(&stripe_pfns[stripeoff[parity2] >> PAGE_SHIFT], &parity2_pfns[parity_pos >> PAGE_SHIFT], maxwritelen * sizeof(PFN_NUMBER) >> PAGE_SHIFT);\n        }\n    }\n\n    for (i = 0; i < num_data_stripes; i++) {\n        uint8_t* ss = MmGetSystemAddressForMdlSafe(log_stripes[c->chunk_item->num_stripes - 3 - i].mdl, priority);\n\n        if (i == 0) {\n            RtlCopyMemory(wtc->parity1, ss, (ULONG)(parity_end - parity_start));\n            RtlCopyMemory(wtc->parity2, ss, (ULONG)(parity_end - parity_start));\n        } else {\n            do_xor(wtc->parity1, ss, (uint32_t)(parity_end - parity_start));\n\n            galois_double(wtc->parity2, (uint32_t)(parity_end - parity_start));\n            do_xor(wtc->parity2, ss, (uint32_t)(parity_end - parity_start));\n        }\n    }\n\n    Status = STATUS_SUCCESS;\n\nexit:\n    if (log_stripes) {\n        for (i = 0; i < num_data_stripes; i++) {\n            if (log_stripes[i].mdl)\n                IoFreeMdl(log_stripes[i].mdl);\n        }\n\n        ExFreePool(log_stripes);\n    }\n\n    if (stripeoff)\n        ExFreePool(stripeoff);\n\n    return Status;\n}\n\n__attribute__((nonnull(1,3,5)))\nNTSTATUS write_data(_In_ device_extension* Vcb, _In_ uint64_t address, _In_reads_bytes_(length) void* data, _In_ uint32_t length, _In_ write_data_context* wtc,\n                    _In_opt_ PIRP Irp, _In_opt_ chunk* c, _In_ bool file_write, _In_ uint64_t irp_offset, _In_ ULONG priority) {\n    NTSTATUS Status;\n    uint32_t i;\n    CHUNK_ITEM_STRIPE* cis;\n    write_stripe* stripes = NULL;\n    uint64_t total_writing = 0;\n    ULONG allowed_missing, missing;\n\n    TRACE(\"(%p, %I64x, %p, %x)\\n\", Vcb, address, data, length);\n\n    if (!c) {\n        c = get_chunk_from_address(Vcb, address);\n        if (!c) {\n            ERR(\"could not get chunk for address %I64x\\n\", address);\n            return STATUS_INTERNAL_ERROR;\n        }\n    }\n\n    stripes = ExAllocatePoolWithTag(PagedPool, sizeof(write_stripe) * c->chunk_item->num_stripes, ALLOC_TAG);\n    if (!stripes) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    RtlZeroMemory(stripes, sizeof(write_stripe) * c->chunk_item->num_stripes);\n\n    cis = (CHUNK_ITEM_STRIPE*)&c->chunk_item[1];\n\n    if (c->chunk_item->type & BLOCK_FLAG_RAID0) {\n        Status = prepare_raid0_write(c, address, data, length, stripes, file_write ? Irp : NULL, irp_offset, wtc);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"prepare_raid0_write returned %08lx\\n\", Status);\n            goto prepare_failed;\n        }\n\n        allowed_missing = 0;\n    } else if (c->chunk_item->type & BLOCK_FLAG_RAID10) {\n        Status = prepare_raid10_write(c, address, data, length, stripes, file_write ? Irp : NULL, irp_offset, wtc);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"prepare_raid10_write returned %08lx\\n\", Status);\n            goto prepare_failed;\n        }\n\n        allowed_missing = 1;\n    } else if (c->chunk_item->type & BLOCK_FLAG_RAID5) {\n        Status = prepare_raid5_write(Vcb, c, address, data, length, stripes, file_write ? Irp : NULL, irp_offset, priority, wtc);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"prepare_raid5_write returned %08lx\\n\", Status);\n            goto prepare_failed;\n        }\n\n        allowed_missing = 1;\n    } else if (c->chunk_item->type & BLOCK_FLAG_RAID6) {\n        Status = prepare_raid6_write(Vcb, c, address, data, length, stripes, file_write ? Irp : NULL, irp_offset, priority, wtc);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"prepare_raid6_write returned %08lx\\n\", Status);\n            goto prepare_failed;\n        }\n\n        allowed_missing = 2;\n    } else {  // write same data to every location - SINGLE, DUP, RAID1, RAID1C3, RAID1C4\n        for (i = 0; i < c->chunk_item->num_stripes; i++) {\n            stripes[i].start = address - c->offset;\n            stripes[i].end = stripes[i].start + length;\n            stripes[i].data = data;\n            stripes[i].irp_offset = irp_offset;\n\n            if (c->devices[i]->devobj) {\n                if (file_write) {\n                    uint8_t* va;\n                    ULONG writelen = (ULONG)(stripes[i].end - stripes[i].start);\n\n                    va = (uint8_t*)MmGetMdlVirtualAddress(Irp->MdlAddress) + stripes[i].irp_offset;\n\n                    stripes[i].mdl = IoAllocateMdl(va, writelen, false, false, NULL);\n                    if (!stripes[i].mdl) {\n                        ERR(\"IoAllocateMdl failed\\n\");\n                        Status = STATUS_INSUFFICIENT_RESOURCES;\n                        goto prepare_failed;\n                    }\n\n                    IoBuildPartialMdl(Irp->MdlAddress, stripes[i].mdl, va, writelen);\n                } else {\n                    stripes[i].mdl = IoAllocateMdl(stripes[i].data, (ULONG)(stripes[i].end - stripes[i].start), false, false, NULL);\n                    if (!stripes[i].mdl) {\n                        ERR(\"IoAllocateMdl failed\\n\");\n                        Status = STATUS_INSUFFICIENT_RESOURCES;\n                        goto prepare_failed;\n                    }\n\n                    Status = STATUS_SUCCESS;\n\n                    try {\n                        MmProbeAndLockPages(stripes[i].mdl, KernelMode, IoReadAccess);\n                    } except (EXCEPTION_EXECUTE_HANDLER) {\n                        Status = GetExceptionCode();\n                    }\n\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n                        IoFreeMdl(stripes[i].mdl);\n                        stripes[i].mdl = NULL;\n                        goto prepare_failed;\n                    }\n                }\n            }\n        }\n\n        allowed_missing = c->chunk_item->num_stripes - 1;\n    }\n\n    missing = 0;\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (!c->devices[i]->devobj)\n            missing++;\n    }\n\n    if (missing > allowed_missing) {\n        ERR(\"cannot write as %lu missing devices (maximum %lu)\\n\", missing, allowed_missing);\n        Status = STATUS_DEVICE_NOT_READY;\n        goto prepare_failed;\n    }\n\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        write_data_stripe* stripe;\n        PIO_STACK_LOCATION IrpSp;\n\n        stripe = ExAllocatePoolWithTag(NonPagedPool, sizeof(write_data_stripe), ALLOC_TAG);\n        if (!stripe) {\n            ERR(\"out of memory\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto end;\n        }\n\n        if (stripes[i].start == stripes[i].end || !c->devices[i]->devobj) {\n            stripe->status = WriteDataStatus_Ignore;\n            stripe->Irp = NULL;\n            stripe->buf = stripes[i].data;\n            stripe->mdl = NULL;\n        } else {\n            stripe->context = (struct _write_data_context*)wtc;\n            stripe->buf = stripes[i].data;\n            stripe->device = c->devices[i];\n            RtlZeroMemory(&stripe->iosb, sizeof(IO_STATUS_BLOCK));\n            stripe->status = WriteDataStatus_Pending;\n            stripe->mdl = stripes[i].mdl;\n\n            if (!Irp) {\n                stripe->Irp = IoAllocateIrp(stripe->device->devobj->StackSize, false);\n\n                if (!stripe->Irp) {\n                    ERR(\"IoAllocateIrp failed\\n\");\n                    ExFreePool(stripe);\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto end;\n                }\n            } else {\n                stripe->Irp = IoMakeAssociatedIrp(Irp, stripe->device->devobj->StackSize);\n\n                if (!stripe->Irp) {\n                    ERR(\"IoMakeAssociatedIrp failed\\n\");\n                    ExFreePool(stripe);\n                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                    goto end;\n                }\n            }\n\n            IrpSp = IoGetNextIrpStackLocation(stripe->Irp);\n            IrpSp->MajorFunction = IRP_MJ_WRITE;\n            IrpSp->FileObject = stripe->device->fileobj;\n\n            if (stripe->device->devobj->Flags & DO_BUFFERED_IO) {\n                stripe->Irp->AssociatedIrp.SystemBuffer = MmGetSystemAddressForMdlSafe(stripes[i].mdl, priority);\n\n                stripe->Irp->Flags = IRP_BUFFERED_IO;\n            } else if (stripe->device->devobj->Flags & DO_DIRECT_IO)\n                stripe->Irp->MdlAddress = stripe->mdl;\n            else\n                stripe->Irp->UserBuffer = MmGetSystemAddressForMdlSafe(stripes[i].mdl, priority);\n\n#ifdef DEBUG_PARANOID\n            if (stripes[i].end < stripes[i].start) {\n                ERR(\"trying to write stripe with negative length (%I64x < %I64x)\\n\", stripes[i].end, stripes[i].start);\n                int3;\n            }\n#endif\n\n            IrpSp->Parameters.Write.Length = (ULONG)(stripes[i].end - stripes[i].start);\n            IrpSp->Parameters.Write.ByteOffset.QuadPart = stripes[i].start + cis[i].offset;\n\n            total_writing += IrpSp->Parameters.Write.Length;\n\n            stripe->Irp->UserIosb = &stripe->iosb;\n            wtc->stripes_left++;\n\n            IoSetCompletionRoutine(stripe->Irp, write_data_completion, stripe, true, true, true);\n        }\n\n        InsertTailList(&wtc->stripes, &stripe->list_entry);\n    }\n\n    if (diskacc)\n        fFsRtlUpdateDiskCounters(0, total_writing);\n\n    Status = STATUS_SUCCESS;\n\nend:\n\n    if (stripes) ExFreePool(stripes);\n\n    if (!NT_SUCCESS(Status))\n        free_write_data_stripes(wtc);\n\n    return Status;\n\nprepare_failed:\n    for (i = 0; i < c->chunk_item->num_stripes; i++) {\n        if (stripes[i].mdl && (i == 0 || stripes[i].mdl != stripes[i-1].mdl)) {\n            if (stripes[i].mdl->MdlFlags & MDL_PAGES_LOCKED)\n                MmUnlockPages(stripes[i].mdl);\n\n            IoFreeMdl(stripes[i].mdl);\n        }\n    }\n\n    if (wtc->parity1_mdl) {\n        if (wtc->parity1_mdl->MdlFlags & MDL_PAGES_LOCKED)\n            MmUnlockPages(wtc->parity1_mdl);\n\n        IoFreeMdl(wtc->parity1_mdl);\n        wtc->parity1_mdl = NULL;\n    }\n\n    if (wtc->parity2_mdl) {\n        if (wtc->parity2_mdl->MdlFlags & MDL_PAGES_LOCKED)\n            MmUnlockPages(wtc->parity2_mdl);\n\n        IoFreeMdl(wtc->parity2_mdl);\n        wtc->parity2_mdl = NULL;\n    }\n\n    if (wtc->mdl) {\n        if (wtc->mdl->MdlFlags & MDL_PAGES_LOCKED)\n            MmUnlockPages(wtc->mdl);\n\n        IoFreeMdl(wtc->mdl);\n        wtc->mdl = NULL;\n    }\n\n    if (wtc->parity1) {\n        ExFreePool(wtc->parity1);\n        wtc->parity1 = NULL;\n    }\n\n    if (wtc->parity2) {\n        ExFreePool(wtc->parity2);\n        wtc->parity2 = NULL;\n    }\n\n    if (wtc->scratch) {\n        ExFreePool(wtc->scratch);\n        wtc->scratch = NULL;\n    }\n\n    ExFreePool(stripes);\n    return Status;\n}\n\n__attribute__((nonnull(1,4,5)))\nvoid get_raid56_lock_range(chunk* c, uint64_t address, uint64_t length, uint64_t* lockaddr, uint64_t* locklen) {\n    uint64_t startoff, endoff;\n    uint16_t startoffstripe, endoffstripe, datastripes;\n\n    datastripes = c->chunk_item->num_stripes - (c->chunk_item->type & BLOCK_FLAG_RAID5 ? 1 : 2);\n\n    get_raid0_offset(address - c->offset, c->chunk_item->stripe_length, datastripes, &startoff, &startoffstripe);\n    get_raid0_offset(address + length - c->offset - 1, c->chunk_item->stripe_length, datastripes, &endoff, &endoffstripe);\n\n    startoff -= startoff % c->chunk_item->stripe_length;\n    endoff = sector_align(endoff, c->chunk_item->stripe_length);\n\n    *lockaddr = c->offset + (startoff * datastripes);\n    *locklen = (endoff - startoff) * datastripes;\n}\n\n__attribute__((nonnull(1,3)))\nNTSTATUS write_data_complete(device_extension* Vcb, uint64_t address, void* data, uint32_t length, PIRP Irp, chunk* c, bool file_write, uint64_t irp_offset, ULONG priority) {\n    write_data_context wtc;\n    NTSTATUS Status;\n    uint64_t lockaddr, locklen;\n\n    KeInitializeEvent(&wtc.Event, NotificationEvent, false);\n    InitializeListHead(&wtc.stripes);\n    wtc.stripes_left = 0;\n    wtc.parity1 = wtc.parity2 = wtc.scratch = NULL;\n    wtc.mdl = wtc.parity1_mdl = wtc.parity2_mdl = NULL;\n\n    if (!c) {\n        c = get_chunk_from_address(Vcb, address);\n        if (!c) {\n            ERR(\"could not get chunk for address %I64x\\n\", address);\n            return STATUS_INTERNAL_ERROR;\n        }\n    }\n\n    if (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6) {\n        get_raid56_lock_range(c, address, length, &lockaddr, &locklen);\n        chunk_lock_range(Vcb, c, lockaddr, locklen);\n    }\n\n    try {\n        Status = write_data(Vcb, address, data, length, &wtc, Irp, c, file_write, irp_offset, priority);\n    } except (EXCEPTION_EXECUTE_HANDLER) {\n        Status = GetExceptionCode();\n    }\n\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"write_data returned %08lx\\n\", Status);\n\n        if (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6)\n            chunk_unlock_range(Vcb, c, lockaddr, locklen);\n\n        free_write_data_stripes(&wtc);\n        return Status;\n    }\n\n    if (wtc.stripes.Flink != &wtc.stripes) {\n        // launch writes and wait\n        LIST_ENTRY* le = wtc.stripes.Flink;\n        bool no_wait = true;\n\n        while (le != &wtc.stripes) {\n            write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry);\n\n            if (stripe->status != WriteDataStatus_Ignore) {\n                IoCallDriver(stripe->device->devobj, stripe->Irp);\n                no_wait = false;\n            }\n\n            le = le->Flink;\n        }\n\n        if (!no_wait)\n            KeWaitForSingleObject(&wtc.Event, Executive, KernelMode, false, NULL);\n\n        le = wtc.stripes.Flink;\n        while (le != &wtc.stripes) {\n            write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry);\n\n            if (stripe->status != WriteDataStatus_Ignore && !NT_SUCCESS(stripe->iosb.Status)) {\n                Status = stripe->iosb.Status;\n\n                log_device_error(Vcb, stripe->device, BTRFS_DEV_STAT_WRITE_ERRORS);\n                break;\n            }\n\n            le = le->Flink;\n        }\n\n        free_write_data_stripes(&wtc);\n    }\n\n    if (c->chunk_item->type & BLOCK_FLAG_RAID5 || c->chunk_item->type & BLOCK_FLAG_RAID6)\n        chunk_unlock_range(Vcb, c, lockaddr, locklen);\n\n    return Status;\n}\n\n__attribute__((nonnull(2,3)))\n_Function_class_(IO_COMPLETION_ROUTINE)\nstatic NTSTATUS __stdcall write_data_completion(PDEVICE_OBJECT DeviceObject, PIRP Irp, PVOID conptr) {\n    write_data_stripe* stripe = conptr;\n    write_data_context* context = (write_data_context*)stripe->context;\n    LIST_ENTRY* le;\n\n    UNUSED(DeviceObject);\n\n    // FIXME - we need a lock here\n\n    if (stripe->status == WriteDataStatus_Cancelling) {\n        stripe->status = WriteDataStatus_Cancelled;\n        goto end;\n    }\n\n    stripe->iosb = Irp->IoStatus;\n\n    if (NT_SUCCESS(Irp->IoStatus.Status)) {\n        stripe->status = WriteDataStatus_Success;\n    } else {\n        le = context->stripes.Flink;\n\n        stripe->status = WriteDataStatus_Error;\n\n        while (le != &context->stripes) {\n            write_data_stripe* s2 = CONTAINING_RECORD(le, write_data_stripe, list_entry);\n\n            if (s2->status == WriteDataStatus_Pending) {\n                s2->status = WriteDataStatus_Cancelling;\n                IoCancelIrp(s2->Irp);\n            }\n\n            le = le->Flink;\n        }\n    }\n\nend:\n    if (InterlockedDecrement(&context->stripes_left) == 0)\n        KeSetEvent(&context->Event, 0, false);\n\n    return STATUS_MORE_PROCESSING_REQUIRED;\n}\n\n__attribute__((nonnull(1)))\nvoid free_write_data_stripes(write_data_context* wtc) {\n    LIST_ENTRY* le;\n    PMDL last_mdl = NULL;\n\n    if (wtc->parity1_mdl) {\n        if (wtc->parity1_mdl->MdlFlags & MDL_PAGES_LOCKED)\n            MmUnlockPages(wtc->parity1_mdl);\n\n        IoFreeMdl(wtc->parity1_mdl);\n    }\n\n    if (wtc->parity2_mdl) {\n        if (wtc->parity2_mdl->MdlFlags & MDL_PAGES_LOCKED)\n            MmUnlockPages(wtc->parity2_mdl);\n\n        IoFreeMdl(wtc->parity2_mdl);\n    }\n\n    if (wtc->mdl) {\n        if (wtc->mdl->MdlFlags & MDL_PAGES_LOCKED)\n            MmUnlockPages(wtc->mdl);\n\n        IoFreeMdl(wtc->mdl);\n    }\n\n    if (wtc->parity1)\n        ExFreePool(wtc->parity1);\n\n    if (wtc->parity2)\n        ExFreePool(wtc->parity2);\n\n    if (wtc->scratch)\n        ExFreePool(wtc->scratch);\n\n    le = wtc->stripes.Flink;\n    while (le != &wtc->stripes) {\n        write_data_stripe* stripe = CONTAINING_RECORD(le, write_data_stripe, list_entry);\n\n        if (stripe->mdl && stripe->mdl != last_mdl) {\n            if (stripe->mdl->MdlFlags & MDL_PAGES_LOCKED)\n                MmUnlockPages(stripe->mdl);\n\n            IoFreeMdl(stripe->mdl);\n        }\n\n        last_mdl = stripe->mdl;\n\n        if (stripe->Irp)\n            IoFreeIrp(stripe->Irp);\n\n        le = le->Flink;\n    }\n\n    while (!IsListEmpty(&wtc->stripes)) {\n        write_data_stripe* stripe = CONTAINING_RECORD(RemoveHeadList(&wtc->stripes), write_data_stripe, list_entry);\n\n        ExFreePool(stripe);\n    }\n}\n\n__attribute__((nonnull(1,2,3)))\nvoid add_extent(_In_ fcb* fcb, _In_ LIST_ENTRY* prevextle, _In_ __drv_aliasesMem extent* newext) {\n    LIST_ENTRY* le = prevextle->Flink;\n\n    while (le != &fcb->extents) {\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if (ext->offset >= newext->offset) {\n            InsertHeadList(ext->list_entry.Blink, &newext->list_entry);\n            return;\n        }\n\n        le = le->Flink;\n    }\n\n    InsertTailList(&fcb->extents, &newext->list_entry);\n}\n\n__attribute__((nonnull(1,2,6)))\nNTSTATUS excise_extents(device_extension* Vcb, fcb* fcb, uint64_t start_data, uint64_t end_data, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n\n    le = fcb->extents.Flink;\n\n    while (le != &fcb->extents) {\n        LIST_ENTRY* le2 = le->Flink;\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if (!ext->ignore) {\n            EXTENT_DATA* ed = &ext->extent_data;\n            uint64_t len;\n\n            if (ed->type == EXTENT_TYPE_INLINE)\n                len = ed->decoded_size;\n            else\n                len = ((EXTENT_DATA2*)ed->data)->num_bytes;\n\n            if (ext->offset < end_data && ext->offset + len > start_data) {\n                if (ed->type == EXTENT_TYPE_INLINE) {\n                    if (start_data <= ext->offset && end_data >= ext->offset + len) { // remove all\n                        remove_fcb_extent(fcb, ext, rollback);\n\n                        fcb->inode_item.st_blocks -= len;\n                        fcb->inode_item_changed = true;\n                    } else {\n                        ERR(\"trying to split inline extent\\n\");\n#ifdef DEBUG_PARANOID\n                        int3;\n#endif\n                        return STATUS_INTERNAL_ERROR;\n                    }\n                } else {\n                    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n\n                    if (start_data <= ext->offset && end_data >= ext->offset + len) { // remove all\n                        if (ed2->size != 0) {\n                            chunk* c;\n\n                            fcb->inode_item.st_blocks -= len;\n                            fcb->inode_item_changed = true;\n\n                            c = get_chunk_from_address(Vcb, ed2->address);\n\n                            if (!c) {\n                                ERR(\"get_chunk_from_address(%I64x) failed\\n\", ed2->address);\n                            } else {\n                                Status = update_changed_extent_ref(Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, -1,\n                                                                   fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                                    goto end;\n                                }\n                            }\n                        }\n\n                        remove_fcb_extent(fcb, ext, rollback);\n                    } else if (start_data <= ext->offset && end_data < ext->offset + len) { // remove beginning\n                        EXTENT_DATA2* ned2;\n                        extent* newext;\n\n                        if (ed2->size != 0) {\n                            fcb->inode_item.st_blocks -= end_data - ext->offset;\n                            fcb->inode_item_changed = true;\n                        }\n\n                        newext = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG);\n                        if (!newext) {\n                            ERR(\"out of memory\\n\");\n                            Status = STATUS_INSUFFICIENT_RESOURCES;\n                            goto end;\n                        }\n\n                        ned2 = (EXTENT_DATA2*)newext->extent_data.data;\n\n                        newext->extent_data.generation = Vcb->superblock.generation;\n                        newext->extent_data.decoded_size = ed->decoded_size;\n                        newext->extent_data.compression = ed->compression;\n                        newext->extent_data.encryption = ed->encryption;\n                        newext->extent_data.encoding = ed->encoding;\n                        newext->extent_data.type = ed->type;\n                        ned2->address = ed2->address;\n                        ned2->size = ed2->size;\n                        ned2->offset = ed2->offset + (end_data - ext->offset);\n                        ned2->num_bytes = ed2->num_bytes - (end_data - ext->offset);\n\n                        newext->offset = end_data;\n                        newext->datalen = sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2);\n                        newext->unique = ext->unique;\n                        newext->ignore = false;\n                        newext->inserted = true;\n\n                        if (ext->csum) {\n                            if (ed->compression == BTRFS_COMPRESSION_NONE) {\n                                newext->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ned2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG);\n                                if (!newext->csum) {\n                                    ERR(\"out of memory\\n\");\n                                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                                    ExFreePool(newext);\n                                    goto end;\n                                }\n\n                                RtlCopyMemory(newext->csum, (uint8_t*)ext->csum + (((end_data - ext->offset) * Vcb->csum_size) >> Vcb->sector_shift),\n                                              (ULONG)((ned2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift));\n                            } else {\n                                newext->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG);\n                                if (!newext->csum) {\n                                    ERR(\"out of memory\\n\");\n                                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                                    ExFreePool(newext);\n                                    goto end;\n                                }\n\n                                RtlCopyMemory(newext->csum, ext->csum, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift));\n                            }\n                        } else\n                            newext->csum = NULL;\n\n                        add_extent(fcb, &ext->list_entry, newext);\n\n                        remove_fcb_extent(fcb, ext, rollback);\n                    } else if (start_data > ext->offset && end_data >= ext->offset + len) { // remove end\n                        EXTENT_DATA2* ned2;\n                        extent* newext;\n\n                        if (ed2->size != 0) {\n                            fcb->inode_item.st_blocks -= ext->offset + len - start_data;\n                            fcb->inode_item_changed = true;\n                        }\n\n                        newext = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG);\n                        if (!newext) {\n                            ERR(\"out of memory\\n\");\n                            Status = STATUS_INSUFFICIENT_RESOURCES;\n                            goto end;\n                        }\n\n                        ned2 = (EXTENT_DATA2*)newext->extent_data.data;\n\n                        newext->extent_data.generation = Vcb->superblock.generation;\n                        newext->extent_data.decoded_size = ed->decoded_size;\n                        newext->extent_data.compression = ed->compression;\n                        newext->extent_data.encryption = ed->encryption;\n                        newext->extent_data.encoding = ed->encoding;\n                        newext->extent_data.type = ed->type;\n                        ned2->address = ed2->address;\n                        ned2->size = ed2->size;\n                        ned2->offset = ed2->offset;\n                        ned2->num_bytes = start_data - ext->offset;\n\n                        newext->offset = ext->offset;\n                        newext->datalen = sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2);\n                        newext->unique = ext->unique;\n                        newext->ignore = false;\n                        newext->inserted = true;\n\n                        if (ext->csum) {\n                            if (ed->compression == BTRFS_COMPRESSION_NONE) {\n                                newext->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ned2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG);\n                                if (!newext->csum) {\n                                    ERR(\"out of memory\\n\");\n                                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                                    ExFreePool(newext);\n                                    goto end;\n                                }\n\n                                RtlCopyMemory(newext->csum, ext->csum, (ULONG)((ned2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift));\n                            } else {\n                                newext->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG);\n                                if (!newext->csum) {\n                                    ERR(\"out of memory\\n\");\n                                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                                    ExFreePool(newext);\n                                    goto end;\n                                }\n\n                                RtlCopyMemory(newext->csum, ext->csum, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift));\n                            }\n                        } else\n                            newext->csum = NULL;\n\n                        InsertHeadList(&ext->list_entry, &newext->list_entry);\n\n                        remove_fcb_extent(fcb, ext, rollback);\n                    } else if (start_data > ext->offset && end_data < ext->offset + len) { // remove middle\n                        EXTENT_DATA2 *neda2, *nedb2;\n                        extent *newext1, *newext2;\n\n                        if (ed2->size != 0) {\n                            chunk* c;\n\n                            fcb->inode_item.st_blocks -= end_data - start_data;\n                            fcb->inode_item_changed = true;\n\n                            c = get_chunk_from_address(Vcb, ed2->address);\n\n                            if (!c) {\n                                ERR(\"get_chunk_from_address(%I64x) failed\\n\", ed2->address);\n                            } else {\n                                Status = update_changed_extent_ref(Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, 1,\n                                                                   fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp);\n                                if (!NT_SUCCESS(Status)) {\n                                    ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                                    goto end;\n                                }\n                            }\n                        }\n\n                        newext1 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG);\n                        if (!newext1) {\n                            ERR(\"out of memory\\n\");\n                            Status = STATUS_INSUFFICIENT_RESOURCES;\n                            goto end;\n                        }\n\n                        newext2 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2), ALLOC_TAG);\n                        if (!newext2) {\n                            ERR(\"out of memory\\n\");\n                            Status = STATUS_INSUFFICIENT_RESOURCES;\n                            ExFreePool(newext1);\n                            goto end;\n                        }\n\n                        neda2 = (EXTENT_DATA2*)newext1->extent_data.data;\n\n                        newext1->extent_data.generation = Vcb->superblock.generation;\n                        newext1->extent_data.decoded_size = ed->decoded_size;\n                        newext1->extent_data.compression = ed->compression;\n                        newext1->extent_data.encryption = ed->encryption;\n                        newext1->extent_data.encoding = ed->encoding;\n                        newext1->extent_data.type = ed->type;\n                        neda2->address = ed2->address;\n                        neda2->size = ed2->size;\n                        neda2->offset = ed2->offset;\n                        neda2->num_bytes = start_data - ext->offset;\n\n                        nedb2 = (EXTENT_DATA2*)newext2->extent_data.data;\n\n                        newext2->extent_data.generation = Vcb->superblock.generation;\n                        newext2->extent_data.decoded_size = ed->decoded_size;\n                        newext2->extent_data.compression = ed->compression;\n                        newext2->extent_data.encryption = ed->encryption;\n                        newext2->extent_data.encoding = ed->encoding;\n                        newext2->extent_data.type = ed->type;\n                        nedb2->address = ed2->address;\n                        nedb2->size = ed2->size;\n                        nedb2->offset = ed2->offset + (end_data - ext->offset);\n                        nedb2->num_bytes = ext->offset + len - end_data;\n\n                        newext1->offset = ext->offset;\n                        newext1->datalen = sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2);\n                        newext1->unique = ext->unique;\n                        newext1->ignore = false;\n                        newext1->inserted = true;\n\n                        newext2->offset = end_data;\n                        newext2->datalen = sizeof(EXTENT_DATA) - 1 + sizeof(EXTENT_DATA2);\n                        newext2->unique = ext->unique;\n                        newext2->ignore = false;\n                        newext2->inserted = true;\n\n                        if (ext->csum) {\n                            if (ed->compression == BTRFS_COMPRESSION_NONE) {\n                                newext1->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((neda2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG);\n                                if (!newext1->csum) {\n                                    ERR(\"out of memory\\n\");\n                                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                                    ExFreePool(newext1);\n                                    ExFreePool(newext2);\n                                    goto end;\n                                }\n\n                                newext2->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((nedb2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG);\n                                if (!newext2->csum) {\n                                    ERR(\"out of memory\\n\");\n                                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                                    ExFreePool(newext1->csum);\n                                    ExFreePool(newext1);\n                                    ExFreePool(newext2);\n                                    goto end;\n                                }\n\n                                RtlCopyMemory(newext1->csum, ext->csum, (ULONG)((neda2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift));\n                                RtlCopyMemory(newext2->csum, (uint8_t*)ext->csum + (((end_data - ext->offset) * Vcb->csum_size) >> Vcb->sector_shift),\n                                              (ULONG)((nedb2->num_bytes * Vcb->csum_size) >> Vcb->sector_shift));\n                            } else {\n                                newext1->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG);\n                                if (!newext1->csum) {\n                                    ERR(\"out of memory\\n\");\n                                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                                    ExFreePool(newext1);\n                                    ExFreePool(newext2);\n                                    goto end;\n                                }\n\n                                newext2->csum = ExAllocatePoolWithTag(PagedPool, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift), ALLOC_TAG);\n                                if (!newext2->csum) {\n                                    ERR(\"out of memory\\n\");\n                                    Status = STATUS_INSUFFICIENT_RESOURCES;\n                                    ExFreePool(newext1->csum);\n                                    ExFreePool(newext1);\n                                    ExFreePool(newext2);\n                                    goto end;\n                                }\n\n                                RtlCopyMemory(newext1->csum, ext->csum, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift));\n                                RtlCopyMemory(newext2->csum, ext->csum, (ULONG)((ed2->size * Vcb->csum_size) >> Vcb->sector_shift));\n                            }\n                        } else {\n                            newext1->csum = NULL;\n                            newext2->csum = NULL;\n                        }\n\n                        InsertHeadList(&ext->list_entry, &newext1->list_entry);\n                        add_extent(fcb, &newext1->list_entry, newext2);\n\n                        remove_fcb_extent(fcb, ext, rollback);\n                    }\n                }\n            }\n        }\n\n        le = le2;\n    }\n\n    Status = STATUS_SUCCESS;\n\nend:\n    fcb->extents_changed = true;\n    mark_fcb_dirty(fcb);\n\n    return Status;\n}\n\n__attribute__((nonnull(1,2,3)))\nstatic void add_insert_extent_rollback(LIST_ENTRY* rollback, fcb* fcb, extent* ext) {\n    rollback_extent* re;\n\n    re = ExAllocatePoolWithTag(NonPagedPool, sizeof(rollback_extent), ALLOC_TAG);\n    if (!re) {\n        ERR(\"out of memory\\n\");\n        return;\n    }\n\n    re->fcb = fcb;\n    re->ext = ext;\n\n    add_rollback(rollback, ROLLBACK_INSERT_EXTENT, re);\n}\n\n#ifdef _MSC_VER\n#pragma warning(push)\n#pragma warning(suppress: 28194)\n#endif\n__attribute__((nonnull(1,3,7)))\nNTSTATUS add_extent_to_fcb(_In_ fcb* fcb, _In_ uint64_t offset, _In_reads_bytes_(edsize) EXTENT_DATA* ed, _In_ uint16_t edsize,\n                           _In_ bool unique, _In_opt_ _When_(return >= 0, __drv_aliasesMem) void* csum, _In_ LIST_ENTRY* rollback) {\n    extent* ext;\n    LIST_ENTRY* le;\n\n    ext = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + edsize, ALLOC_TAG);\n    if (!ext) {\n        ERR(\"out of memory\\n\");\n        return STATUS_INSUFFICIENT_RESOURCES;\n    }\n\n    ext->offset = offset;\n    ext->datalen = edsize;\n    ext->unique = unique;\n    ext->ignore = false;\n    ext->inserted = true;\n    ext->csum = csum;\n\n    RtlCopyMemory(&ext->extent_data, ed, edsize);\n\n    le = fcb->extents.Flink;\n    while (le != &fcb->extents) {\n        extent* oldext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if (oldext->offset >= offset) {\n            InsertHeadList(le->Blink, &ext->list_entry);\n            goto end;\n        }\n\n        le = le->Flink;\n    }\n\n    InsertTailList(&fcb->extents, &ext->list_entry);\n\nend:\n    add_insert_extent_rollback(rollback, fcb, ext);\n\n    return STATUS_SUCCESS;\n}\n#ifdef _MSC_VER\n#pragma warning(pop)\n#endif\n\n__attribute__((nonnull(1, 2, 3)))\nstatic void remove_fcb_extent(fcb* fcb, extent* ext, LIST_ENTRY* rollback) {\n    if (!ext->ignore) {\n        rollback_extent* re;\n\n        ext->ignore = true;\n\n        re = ExAllocatePoolWithTag(NonPagedPool, sizeof(rollback_extent), ALLOC_TAG);\n        if (!re) {\n            ERR(\"out of memory\\n\");\n            return;\n        }\n\n        re->fcb = fcb;\n        re->ext = ext;\n\n        add_rollback(rollback, ROLLBACK_DELETE_EXTENT, re);\n    }\n}\n\n_Requires_lock_held_(c->lock)\n_When_(return != 0, _Releases_lock_(c->lock))\n__attribute__((nonnull(1,2,3,9)))\nbool insert_extent_chunk(_In_ device_extension* Vcb, _In_ fcb* fcb, _In_ chunk* c, _In_ uint64_t start_data, _In_ uint64_t length, _In_ bool prealloc, _In_opt_ void* data,\n                         _In_opt_ PIRP Irp, _In_ LIST_ENTRY* rollback, _In_ uint8_t compression, _In_ uint64_t decoded_size, _In_ bool file_write, _In_ uint64_t irp_offset) {\n    uint64_t address;\n    NTSTATUS Status;\n    EXTENT_DATA* ed;\n    EXTENT_DATA2* ed2;\n    uint16_t edsize = (uint16_t)(offsetof(EXTENT_DATA, data[0]) + sizeof(EXTENT_DATA2));\n    void* csum = NULL;\n\n    TRACE(\"(%p, (%I64x, %I64x), %I64x, %I64x, %I64x, %u, %p, %p)\\n\", Vcb, fcb->subvol->id, fcb->inode, c->offset, start_data, length, prealloc, data, rollback);\n\n    if (!find_data_address_in_chunk(Vcb, c, length, &address))\n        return false;\n\n    // add extent data to inode\n    ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG);\n    if (!ed) {\n        ERR(\"out of memory\\n\");\n        return false;\n    }\n\n    ed->generation = Vcb->superblock.generation;\n    ed->decoded_size = decoded_size;\n    ed->compression = compression;\n    ed->encryption = BTRFS_ENCRYPTION_NONE;\n    ed->encoding = BTRFS_ENCODING_NONE;\n    ed->type = prealloc ? EXTENT_TYPE_PREALLOC : EXTENT_TYPE_REGULAR;\n\n    ed2 = (EXTENT_DATA2*)ed->data;\n    ed2->address = address;\n    ed2->size = length;\n    ed2->offset = 0;\n    ed2->num_bytes = decoded_size;\n\n    if (!prealloc && data && !(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) {\n        ULONG sl = (ULONG)(length >> Vcb->sector_shift);\n\n        csum = ExAllocatePoolWithTag(PagedPool, sl * Vcb->csum_size, ALLOC_TAG);\n        if (!csum) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(ed);\n            return false;\n        }\n\n        do_calc_job(Vcb, data, sl, csum);\n    }\n\n    Status = add_extent_to_fcb(fcb, start_data, ed, edsize, true, csum, rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"add_extent_to_fcb returned %08lx\\n\", Status);\n        if (csum) ExFreePool(csum);\n        ExFreePool(ed);\n        return false;\n    }\n\n    ExFreePool(ed);\n\n    c->used += length;\n    space_list_subtract(c, address, length, rollback);\n\n    fcb->inode_item.st_blocks += decoded_size;\n\n    fcb->extents_changed = true;\n    fcb->inode_item_changed = true;\n    mark_fcb_dirty(fcb);\n\n    ExAcquireResourceExclusiveLite(&c->changed_extents_lock, true);\n\n    add_changed_extent_ref(c, address, length, fcb->subvol->id, fcb->inode, start_data, 1, fcb->inode_item.flags & BTRFS_INODE_NODATASUM);\n\n    ExReleaseResourceLite(&c->changed_extents_lock);\n\n    release_chunk_lock(c, Vcb);\n\n    if (data) {\n        Status = write_data_complete(Vcb, address, data, (uint32_t)length, Irp, NULL, file_write, irp_offset,\n                                     fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? HighPagePriority : NormalPagePriority);\n        if (!NT_SUCCESS(Status))\n            ERR(\"write_data_complete returned %08lx\\n\", Status);\n    }\n\n    return true;\n}\n\n__attribute__((nonnull(1,2,5,7,10)))\nstatic bool try_extend_data(device_extension* Vcb, fcb* fcb, uint64_t start_data, uint64_t length, void* data,\n                            PIRP Irp, uint64_t* written, bool file_write, uint64_t irp_offset, LIST_ENTRY* rollback) {\n    bool success = false;\n    EXTENT_DATA* ed;\n    EXTENT_DATA2* ed2;\n    chunk* c;\n    LIST_ENTRY* le;\n    extent* ext = NULL;\n\n    le = fcb->extents.Flink;\n\n    while (le != &fcb->extents) {\n        extent* nextext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if (!nextext->ignore) {\n            if (nextext->offset == start_data) {\n                ext = nextext;\n                break;\n            } else if (nextext->offset > start_data)\n                break;\n\n            ext = nextext;\n        }\n\n        le = le->Flink;\n    }\n\n    if (!ext)\n        return false;\n\n    ed = &ext->extent_data;\n\n    if (ed->type != EXTENT_TYPE_REGULAR && ed->type != EXTENT_TYPE_PREALLOC) {\n        TRACE(\"not extending extent which is not regular or prealloc\\n\");\n        return false;\n    }\n\n    ed2 = (EXTENT_DATA2*)ed->data;\n\n    if (ext->offset + ed2->num_bytes != start_data) {\n        TRACE(\"last EXTENT_DATA does not run up to start_data (%I64x + %I64x != %I64x)\\n\", ext->offset, ed2->num_bytes, start_data);\n        return false;\n    }\n\n    c = get_chunk_from_address(Vcb, ed2->address);\n\n    if (c->reloc || c->readonly || c->chunk_item->type != Vcb->data_flags)\n        return false;\n\n    acquire_chunk_lock(c, Vcb);\n\n    if (length > c->chunk_item->size - c->used) {\n        release_chunk_lock(c, Vcb);\n        return false;\n    }\n\n    if (!c->cache_loaded) {\n        NTSTATUS Status = load_cache_chunk(Vcb, c, NULL);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"load_cache_chunk returned %08lx\\n\", Status);\n            release_chunk_lock(c, Vcb);\n            return false;\n        }\n    }\n\n    le = c->space.Flink;\n    while (le != &c->space) {\n        space* s = CONTAINING_RECORD(le, space, list_entry);\n\n        if (s->address == ed2->address + ed2->size) {\n            uint64_t newlen = min(min(s->size, length), MAX_EXTENT_SIZE);\n\n            success = insert_extent_chunk(Vcb, fcb, c, start_data, newlen, false, data, Irp, rollback, BTRFS_COMPRESSION_NONE, newlen, file_write, irp_offset);\n\n            if (success)\n                *written += newlen;\n            else\n                release_chunk_lock(c, Vcb);\n\n            return success;\n        } else if (s->address > ed2->address + ed2->size)\n            break;\n\n        le = le->Flink;\n    }\n\n    release_chunk_lock(c, Vcb);\n\n    return false;\n}\n\n__attribute__((nonnull(1)))\nstatic NTSTATUS insert_chunk_fragmented(fcb* fcb, uint64_t start, uint64_t length, uint8_t* data, bool prealloc, LIST_ENTRY* rollback) {\n    LIST_ENTRY* le;\n    uint64_t flags = fcb->Vcb->data_flags;\n    bool page_file = fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE;\n    NTSTATUS Status;\n    chunk* c;\n\n    ExAcquireResourceSharedLite(&fcb->Vcb->chunk_lock, true);\n\n    // first create as many chunks as we can\n    do {\n        Status = alloc_chunk(fcb->Vcb, flags, &c, false);\n    } while (NT_SUCCESS(Status));\n\n    if (Status != STATUS_DISK_FULL) {\n        ERR(\"alloc_chunk returned %08lx\\n\", Status);\n        ExReleaseResourceLite(&fcb->Vcb->chunk_lock);\n        return Status;\n    }\n\n    le = fcb->Vcb->chunks.Flink;\n    while (le != &fcb->Vcb->chunks) {\n        c = CONTAINING_RECORD(le, chunk, list_entry);\n\n        if (!c->readonly && !c->reloc) {\n            acquire_chunk_lock(c, fcb->Vcb);\n\n            if (c->chunk_item->type == flags) {\n                while (!IsListEmpty(&c->space_size) && length > 0) {\n                    space* s = CONTAINING_RECORD(c->space_size.Flink, space, list_entry_size);\n                    uint64_t extlen = min(length, s->size);\n\n                    if (insert_extent_chunk(fcb->Vcb, fcb, c, start, extlen, prealloc && !page_file, data, NULL, rollback, BTRFS_COMPRESSION_NONE, extlen, false, 0)) {\n                        start += extlen;\n                        length -= extlen;\n                        if (data) data += extlen;\n\n                        acquire_chunk_lock(c, fcb->Vcb);\n                    }\n                }\n            }\n\n            release_chunk_lock(c, fcb->Vcb);\n\n            if (length == 0)\n                break;\n        }\n\n        le = le->Flink;\n    }\n\n    ExReleaseResourceLite(&fcb->Vcb->chunk_lock);\n\n    return length == 0 ? STATUS_SUCCESS : STATUS_DISK_FULL;\n}\n\n__attribute__((nonnull(1,4)))\nstatic NTSTATUS insert_prealloc_extent(fcb* fcb, uint64_t start, uint64_t length, LIST_ENTRY* rollback) {\n    LIST_ENTRY* le;\n    chunk* c;\n    uint64_t flags;\n    NTSTATUS Status;\n    bool page_file = fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE;\n\n    flags = fcb->Vcb->data_flags;\n\n    do {\n        uint64_t extlen = min(MAX_EXTENT_SIZE, length);\n\n        ExAcquireResourceSharedLite(&fcb->Vcb->chunk_lock, true);\n\n        le = fcb->Vcb->chunks.Flink;\n        while (le != &fcb->Vcb->chunks) {\n            c = CONTAINING_RECORD(le, chunk, list_entry);\n\n            if (!c->readonly && !c->reloc) {\n                acquire_chunk_lock(c, fcb->Vcb);\n\n                if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= extlen) {\n                    if (insert_extent_chunk(fcb->Vcb, fcb, c, start, extlen, !page_file, NULL, NULL, rollback, BTRFS_COMPRESSION_NONE, extlen, false, 0)) {\n                        ExReleaseResourceLite(&fcb->Vcb->chunk_lock);\n                        goto cont;\n                    }\n                }\n\n                release_chunk_lock(c, fcb->Vcb);\n            }\n\n            le = le->Flink;\n        }\n\n        ExReleaseResourceLite(&fcb->Vcb->chunk_lock);\n\n        ExAcquireResourceExclusiveLite(&fcb->Vcb->chunk_lock, true);\n\n        Status = alloc_chunk(fcb->Vcb, flags, &c, false);\n\n        ExReleaseResourceLite(&fcb->Vcb->chunk_lock);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"alloc_chunk returned %08lx\\n\", Status);\n            goto end;\n        }\n\n        acquire_chunk_lock(c, fcb->Vcb);\n\n        if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= extlen) {\n            if (insert_extent_chunk(fcb->Vcb, fcb, c, start, extlen, !page_file, NULL, NULL, rollback, BTRFS_COMPRESSION_NONE, extlen, false, 0))\n                goto cont;\n        }\n\n        release_chunk_lock(c, fcb->Vcb);\n\n        Status = insert_chunk_fragmented(fcb, start, length, NULL, true, rollback);\n        if (!NT_SUCCESS(Status))\n            ERR(\"insert_chunk_fragmented returned %08lx\\n\", Status);\n\n        goto end;\n\ncont:\n        length -= extlen;\n        start += extlen;\n    } while (length > 0);\n\n    Status = STATUS_SUCCESS;\n\nend:\n    return Status;\n}\n\n__attribute__((nonnull(1,2,5,9)))\nstatic NTSTATUS insert_extent(device_extension* Vcb, fcb* fcb, uint64_t start_data, uint64_t length, void* data,\n                              PIRP Irp, bool file_write, uint64_t irp_offset, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    LIST_ENTRY* le;\n    chunk* c;\n    uint64_t flags, orig_length = length, written = 0;\n\n    TRACE(\"(%p, (%I64x, %I64x), %I64x, %I64x, %p)\\n\", Vcb, fcb->subvol->id, fcb->inode, start_data, length, data);\n\n    if (start_data > 0) {\n        try_extend_data(Vcb, fcb, start_data, length, data, Irp, &written, file_write, irp_offset, rollback);\n\n        if (written == length)\n            return STATUS_SUCCESS;\n        else if (written > 0) {\n            start_data += written;\n            irp_offset += written;\n            length -= written;\n            data = &((uint8_t*)data)[written];\n        }\n    }\n\n    flags = Vcb->data_flags;\n\n    while (written < orig_length) {\n        uint64_t newlen = min(length, MAX_EXTENT_SIZE);\n        bool done = false;\n\n        // Rather than necessarily writing the whole extent at once, we deal with it in blocks of 128 MB.\n        // First, see if we can write the extent part to an existing chunk.\n\n        ExAcquireResourceSharedLite(&Vcb->chunk_lock, true);\n\n        le = Vcb->chunks.Flink;\n        while (le != &Vcb->chunks) {\n            c = CONTAINING_RECORD(le, chunk, list_entry);\n\n            if (!c->readonly && !c->reloc) {\n                acquire_chunk_lock(c, Vcb);\n\n                if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= newlen &&\n                    insert_extent_chunk(Vcb, fcb, c, start_data, newlen, false, data, Irp, rollback, BTRFS_COMPRESSION_NONE, newlen, file_write, irp_offset)) {\n                    written += newlen;\n\n                    if (written == orig_length) {\n                        ExReleaseResourceLite(&Vcb->chunk_lock);\n                        return STATUS_SUCCESS;\n                    } else {\n                        done = true;\n                        start_data += newlen;\n                        irp_offset += newlen;\n                        length -= newlen;\n                        data = &((uint8_t*)data)[newlen];\n                        break;\n                    }\n                } else\n                    release_chunk_lock(c, Vcb);\n            }\n\n            le = le->Flink;\n        }\n\n        ExReleaseResourceLite(&Vcb->chunk_lock);\n\n        if (done) continue;\n\n        // Otherwise, see if we can put it in a new chunk.\n\n        ExAcquireResourceExclusiveLite(&Vcb->chunk_lock, true);\n\n        Status = alloc_chunk(Vcb, flags, &c, false);\n\n        ExReleaseResourceLite(&Vcb->chunk_lock);\n\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"alloc_chunk returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (c) {\n            acquire_chunk_lock(c, Vcb);\n\n            if (c->chunk_item->type == flags && (c->chunk_item->size - c->used) >= newlen &&\n                insert_extent_chunk(Vcb, fcb, c, start_data, newlen, false, data, Irp, rollback, BTRFS_COMPRESSION_NONE, newlen, file_write, irp_offset)) {\n                written += newlen;\n\n                if (written == orig_length)\n                    return STATUS_SUCCESS;\n                else {\n                    done = true;\n                    start_data += newlen;\n                    irp_offset += newlen;\n                    length -= newlen;\n                    data = &((uint8_t*)data)[newlen];\n                }\n            } else\n                release_chunk_lock(c, Vcb);\n        }\n\n        if (!done) {\n            Status = insert_chunk_fragmented(fcb, start_data, length, data, false, rollback);\n            if (!NT_SUCCESS(Status))\n                ERR(\"insert_chunk_fragmented returned %08lx\\n\", Status);\n\n            return Status;\n        }\n    }\n\n    return STATUS_DISK_FULL;\n}\n\n__attribute__((nonnull(1,4)))\nNTSTATUS truncate_file(fcb* fcb, uint64_t end, PIRP Irp, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n\n    // FIXME - convert into inline extent if short enough\n\n    if (end > 0 && fcb_is_inline(fcb)) {\n        uint8_t* buf;\n        bool make_inline = end <= fcb->Vcb->options.max_inline;\n\n        buf = ExAllocatePoolWithTag(PagedPool, (ULONG)(make_inline ? (offsetof(EXTENT_DATA, data[0]) + end) : sector_align(end, fcb->Vcb->superblock.sector_size)), ALLOC_TAG);\n        if (!buf) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        Status = read_file(fcb, make_inline ? (buf + offsetof(EXTENT_DATA, data[0])) : buf, 0, end, NULL, Irp);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"read_file returned %08lx\\n\", Status);\n            ExFreePool(buf);\n            return Status;\n        }\n\n        Status = excise_extents(fcb->Vcb, fcb, 0, fcb->inode_item.st_size, Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"excise_extents returned %08lx\\n\", Status);\n            ExFreePool(buf);\n            return Status;\n        }\n\n        if (!make_inline) {\n            RtlZeroMemory(buf + end, (ULONG)(sector_align(end, fcb->Vcb->superblock.sector_size) - end));\n\n            Status = do_write_file(fcb, 0, sector_align(end, fcb->Vcb->superblock.sector_size), buf, Irp, false, 0, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"do_write_file returned %08lx\\n\", Status);\n                ExFreePool(buf);\n                return Status;\n            }\n        } else {\n            EXTENT_DATA* ed = (EXTENT_DATA*)buf;\n\n            ed->generation = fcb->Vcb->superblock.generation;\n            ed->decoded_size = end;\n            ed->compression = BTRFS_COMPRESSION_NONE;\n            ed->encryption = BTRFS_ENCRYPTION_NONE;\n            ed->encoding = BTRFS_ENCODING_NONE;\n            ed->type = EXTENT_TYPE_INLINE;\n\n            Status = add_extent_to_fcb(fcb, 0, ed, (uint16_t)(offsetof(EXTENT_DATA, data[0]) + end), false, NULL, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"add_extent_to_fcb returned %08lx\\n\", Status);\n                ExFreePool(buf);\n                return Status;\n            }\n\n            fcb->inode_item.st_blocks += end;\n\n            fcb->inode_item.st_size = end;\n            fcb->inode_item_changed = true;\n            TRACE(\"setting st_size to %I64x\\n\", end);\n\n            fcb->Header.AllocationSize.QuadPart = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);\n            fcb->Header.FileSize.QuadPart = fcb->inode_item.st_size;\n            fcb->Header.ValidDataLength.QuadPart = fcb->inode_item.st_size;\n        }\n\n        ExFreePool(buf);\n        return STATUS_SUCCESS;\n    }\n\n    Status = excise_extents(fcb->Vcb, fcb, sector_align(end, fcb->Vcb->superblock.sector_size),\n                            sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size), Irp, rollback);\n    if (!NT_SUCCESS(Status)) {\n        ERR(\"excise_extents returned %08lx\\n\", Status);\n        return Status;\n    }\n\n    fcb->inode_item.st_size = end;\n    fcb->inode_item_changed = true;\n    TRACE(\"setting st_size to %I64x\\n\", end);\n\n    fcb->Header.AllocationSize.QuadPart = sector_align(fcb->inode_item.st_size, fcb->Vcb->superblock.sector_size);\n    fcb->Header.FileSize.QuadPart = fcb->inode_item.st_size;\n    fcb->Header.ValidDataLength.QuadPart = fcb->inode_item.st_size;\n    // FIXME - inform cache manager of this\n\n    TRACE(\"fcb %p FileSize = %I64x\\n\", fcb, fcb->Header.FileSize.QuadPart);\n\n    return STATUS_SUCCESS;\n}\n\n__attribute__((nonnull(1,6)))\nNTSTATUS extend_file(fcb* fcb, file_ref* fileref, uint64_t end, bool prealloc, PIRP Irp, LIST_ENTRY* rollback) {\n    uint64_t oldalloc, newalloc;\n    bool cur_inline;\n    NTSTATUS Status;\n\n    TRACE(\"(%p, %p, %I64x, %u)\\n\", fcb, fileref, end, prealloc);\n\n    if (fcb->ads) {\n        if (end > 0xffff)\n            return STATUS_DISK_FULL;\n\n        return stream_set_end_of_file_information(fcb->Vcb, (uint16_t)end, fcb, fileref, false);\n    } else {\n        extent* ext = NULL;\n        LIST_ENTRY* le;\n\n        le = fcb->extents.Blink;\n        while (le != &fcb->extents) {\n            extent* ext2 = CONTAINING_RECORD(le, extent, list_entry);\n\n            if (!ext2->ignore) {\n                ext = ext2;\n                break;\n            }\n\n            le = le->Blink;\n        }\n\n        oldalloc = 0;\n        if (ext) {\n            EXTENT_DATA* ed = &ext->extent_data;\n            EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n\n            oldalloc = ext->offset + (ed->type == EXTENT_TYPE_INLINE ? ed->decoded_size : ed2->num_bytes);\n            cur_inline = ed->type == EXTENT_TYPE_INLINE;\n\n            if (cur_inline && end > fcb->Vcb->options.max_inline) {\n                uint64_t origlength, length;\n                uint8_t* data;\n\n                TRACE(\"giving inline file proper extents\\n\");\n\n                origlength = ed->decoded_size;\n\n                cur_inline = false;\n\n                length = sector_align(origlength, fcb->Vcb->superblock.sector_size);\n\n                data = ExAllocatePoolWithTag(PagedPool, (ULONG)length, ALLOC_TAG);\n                if (!data) {\n                    ERR(\"could not allocate %I64x bytes for data\\n\", length);\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                Status = read_file(fcb, data, 0, origlength, NULL, Irp);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"read_file returned %08lx\\n\", Status);\n                    ExFreePool(data);\n                    return Status;\n                }\n\n                RtlZeroMemory(data + origlength, (ULONG)(length - origlength));\n\n                Status = excise_extents(fcb->Vcb, fcb, 0, fcb->inode_item.st_size, Irp, rollback);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"excise_extents returned %08lx\\n\", Status);\n                    ExFreePool(data);\n                    return Status;\n                }\n\n                Status = do_write_file(fcb, 0, length, data, Irp, false, 0, rollback);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"do_write_file returned %08lx\\n\", Status);\n                    ExFreePool(data);\n                    return Status;\n                }\n\n                oldalloc = ext->offset + length;\n\n                ExFreePool(data);\n            }\n\n            if (cur_inline) {\n                uint16_t edsize;\n\n                if (end > oldalloc) {\n                    edsize = (uint16_t)(offsetof(EXTENT_DATA, data[0]) + end - ext->offset);\n                    ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG);\n\n                    if (!ed) {\n                        ERR(\"out of memory\\n\");\n                        return STATUS_INSUFFICIENT_RESOURCES;\n                    }\n\n                    ed->generation = fcb->Vcb->superblock.generation;\n                    ed->decoded_size = end - ext->offset;\n                    ed->compression = BTRFS_COMPRESSION_NONE;\n                    ed->encryption = BTRFS_ENCRYPTION_NONE;\n                    ed->encoding = BTRFS_ENCODING_NONE;\n                    ed->type = EXTENT_TYPE_INLINE;\n\n                    Status = read_file(fcb, ed->data, ext->offset, oldalloc, NULL, Irp);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"read_file returned %08lx\\n\", Status);\n                        ExFreePool(ed);\n                        return Status;\n                    }\n\n                    RtlZeroMemory(ed->data + oldalloc - ext->offset, (ULONG)(end - oldalloc));\n\n                    remove_fcb_extent(fcb, ext, rollback);\n\n                    Status = add_extent_to_fcb(fcb, ext->offset, ed, edsize, ext->unique, NULL, rollback);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"add_extent_to_fcb returned %08lx\\n\", Status);\n                        ExFreePool(ed);\n                        return Status;\n                    }\n\n                    ExFreePool(ed);\n\n                    fcb->extents_changed = true;\n                    mark_fcb_dirty(fcb);\n                }\n\n                TRACE(\"extending inline file (oldalloc = %I64x, end = %I64x)\\n\", oldalloc, end);\n\n                fcb->inode_item.st_size = end;\n                TRACE(\"setting st_size to %I64x\\n\", end);\n\n                fcb->inode_item.st_blocks = end;\n\n                fcb->Header.AllocationSize.QuadPart = fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end;\n            } else {\n                newalloc = sector_align(end, fcb->Vcb->superblock.sector_size);\n\n                if (newalloc > oldalloc) {\n                    if (prealloc) {\n                        // FIXME - try and extend previous extent first\n\n                        Status = insert_prealloc_extent(fcb, oldalloc, newalloc - oldalloc, rollback);\n\n                        if (!NT_SUCCESS(Status) && Status != STATUS_DISK_FULL) {\n                            ERR(\"insert_prealloc_extent returned %08lx\\n\", Status);\n                            return Status;\n                        }\n                    }\n\n                    fcb->extents_changed = true;\n                }\n\n                fcb->inode_item.st_size = end;\n                fcb->inode_item_changed = true;\n                mark_fcb_dirty(fcb);\n\n                TRACE(\"setting st_size to %I64x\\n\", end);\n\n                TRACE(\"newalloc = %I64x\\n\", newalloc);\n\n                fcb->Header.AllocationSize.QuadPart = newalloc;\n                fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end;\n            }\n        } else {\n            if (end > fcb->Vcb->options.max_inline) {\n                newalloc = sector_align(end, fcb->Vcb->superblock.sector_size);\n\n                if (prealloc) {\n                    Status = insert_prealloc_extent(fcb, 0, newalloc, rollback);\n\n                    if (!NT_SUCCESS(Status) && Status != STATUS_DISK_FULL) {\n                        ERR(\"insert_prealloc_extent returned %08lx\\n\", Status);\n                        return Status;\n                    }\n                }\n\n                fcb->extents_changed = true;\n                fcb->inode_item_changed = true;\n                mark_fcb_dirty(fcb);\n\n                fcb->inode_item.st_size = end;\n                TRACE(\"setting st_size to %I64x\\n\", end);\n\n                TRACE(\"newalloc = %I64x\\n\", newalloc);\n\n                fcb->Header.AllocationSize.QuadPart = newalloc;\n                fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end;\n            } else {\n                EXTENT_DATA* ed;\n                uint16_t edsize;\n\n                edsize = (uint16_t)(offsetof(EXTENT_DATA, data[0]) + end);\n                ed = ExAllocatePoolWithTag(PagedPool, edsize, ALLOC_TAG);\n\n                if (!ed) {\n                    ERR(\"out of memory\\n\");\n                    return STATUS_INSUFFICIENT_RESOURCES;\n                }\n\n                ed->generation = fcb->Vcb->superblock.generation;\n                ed->decoded_size = end;\n                ed->compression = BTRFS_COMPRESSION_NONE;\n                ed->encryption = BTRFS_ENCRYPTION_NONE;\n                ed->encoding = BTRFS_ENCODING_NONE;\n                ed->type = EXTENT_TYPE_INLINE;\n\n                RtlZeroMemory(ed->data, (ULONG)end);\n\n                Status = add_extent_to_fcb(fcb, 0, ed, edsize, false, NULL, rollback);\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"add_extent_to_fcb returned %08lx\\n\", Status);\n                    ExFreePool(ed);\n                    return Status;\n                }\n\n                ExFreePool(ed);\n\n                fcb->extents_changed = true;\n                fcb->inode_item_changed = true;\n                mark_fcb_dirty(fcb);\n\n                fcb->inode_item.st_size = end;\n                TRACE(\"setting st_size to %I64x\\n\", end);\n\n                fcb->inode_item.st_blocks = end;\n\n                fcb->Header.AllocationSize.QuadPart = fcb->Header.FileSize.QuadPart = fcb->Header.ValidDataLength.QuadPart = end;\n            }\n        }\n    }\n\n    return STATUS_SUCCESS;\n}\n\n__attribute__((nonnull(1,2,5,6,11)))\nstatic NTSTATUS do_write_file_prealloc(fcb* fcb, extent* ext, uint64_t start_data, uint64_t end_data, void* data, uint64_t* written,\n                                       PIRP Irp, bool file_write, uint64_t irp_offset, ULONG priority, LIST_ENTRY* rollback) {\n    EXTENT_DATA* ed = &ext->extent_data;\n    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n    NTSTATUS Status;\n    chunk* c = NULL;\n\n    if (start_data <= ext->offset && end_data >= ext->offset + ed2->num_bytes) { // replace all\n        extent* newext;\n\n        newext = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG);\n        if (!newext) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(&newext->extent_data, &ext->extent_data, ext->datalen);\n\n        newext->extent_data.type = EXTENT_TYPE_REGULAR;\n\n        Status = write_data_complete(fcb->Vcb, ed2->address + ed2->offset, (uint8_t*)data + ext->offset - start_data, (uint32_t)ed2->num_bytes, Irp,\n                                     NULL, file_write, irp_offset + ext->offset - start_data, priority);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"write_data_complete returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) {\n            ULONG sl = (ULONG)(ed2->num_bytes >> fcb->Vcb->sector_shift);\n            void* csum = ExAllocatePoolWithTag(PagedPool, sl * fcb->Vcb->csum_size, ALLOC_TAG);\n\n            if (!csum) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(newext);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            do_calc_job(fcb->Vcb, (uint8_t*)data + ext->offset - start_data, sl, csum);\n\n            newext->csum = csum;\n        } else\n            newext->csum = NULL;\n\n        *written = ed2->num_bytes;\n\n        newext->offset = ext->offset;\n        newext->datalen = ext->datalen;\n        newext->unique = ext->unique;\n        newext->ignore = false;\n        newext->inserted = true;\n        InsertHeadList(&ext->list_entry, &newext->list_entry);\n\n        add_insert_extent_rollback(rollback, fcb, newext);\n\n        remove_fcb_extent(fcb, ext, rollback);\n\n        c = get_chunk_from_address(fcb->Vcb, ed2->address);\n    } else if (start_data <= ext->offset && end_data < ext->offset + ed2->num_bytes) { // replace beginning\n        EXTENT_DATA2* ned2;\n        extent *newext1, *newext2;\n\n        newext1 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG);\n        if (!newext1) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        newext2 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG);\n        if (!newext2) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(newext1);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(&newext1->extent_data, &ext->extent_data, ext->datalen);\n        newext1->extent_data.type = EXTENT_TYPE_REGULAR;\n        ned2 = (EXTENT_DATA2*)newext1->extent_data.data;\n        ned2->num_bytes = end_data - ext->offset;\n\n        RtlCopyMemory(&newext2->extent_data, &ext->extent_data, ext->datalen);\n        ned2 = (EXTENT_DATA2*)newext2->extent_data.data;\n        ned2->offset += end_data - ext->offset;\n        ned2->num_bytes -= end_data - ext->offset;\n\n        Status = write_data_complete(fcb->Vcb, ed2->address + ed2->offset, (uint8_t*)data + ext->offset - start_data, (uint32_t)(end_data - ext->offset),\n                                     Irp, NULL, file_write, irp_offset + ext->offset - start_data, priority);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"write_data_complete returned %08lx\\n\", Status);\n            ExFreePool(newext1);\n            ExFreePool(newext2);\n            return Status;\n        }\n\n        if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) {\n            ULONG sl = (ULONG)((end_data - ext->offset) >> fcb->Vcb->sector_shift);\n            void* csum = ExAllocatePoolWithTag(PagedPool, sl * fcb->Vcb->csum_size, ALLOC_TAG);\n\n            if (!csum) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(newext1);\n                ExFreePool(newext2);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            do_calc_job(fcb->Vcb, (uint8_t*)data + ext->offset - start_data, sl, csum);\n\n            newext1->csum = csum;\n        } else\n            newext1->csum = NULL;\n\n        *written = end_data - ext->offset;\n\n        newext1->offset = ext->offset;\n        newext1->datalen = ext->datalen;\n        newext1->unique = ext->unique;\n        newext1->ignore = false;\n        newext1->inserted = true;\n        InsertHeadList(&ext->list_entry, &newext1->list_entry);\n\n        add_insert_extent_rollback(rollback, fcb, newext1);\n\n        newext2->offset = end_data;\n        newext2->datalen = ext->datalen;\n        newext2->unique = ext->unique;\n        newext2->ignore = false;\n        newext2->inserted = true;\n        newext2->csum = NULL;\n        add_extent(fcb, &newext1->list_entry, newext2);\n\n        add_insert_extent_rollback(rollback, fcb, newext2);\n\n        c = get_chunk_from_address(fcb->Vcb, ed2->address);\n\n        if (!c)\n            ERR(\"get_chunk_from_address(%I64x) failed\\n\", ed2->address);\n        else {\n            Status = update_changed_extent_ref(fcb->Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, 1,\n                                                fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        remove_fcb_extent(fcb, ext, rollback);\n    } else if (start_data > ext->offset && end_data >= ext->offset + ed2->num_bytes) { // replace end\n        EXTENT_DATA2* ned2;\n        extent *newext1, *newext2;\n\n        newext1 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG);\n        if (!newext1) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        newext2 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG);\n        if (!newext2) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(newext1);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(&newext1->extent_data, &ext->extent_data, ext->datalen);\n\n        ned2 = (EXTENT_DATA2*)newext1->extent_data.data;\n        ned2->num_bytes = start_data - ext->offset;\n\n        RtlCopyMemory(&newext2->extent_data, &ext->extent_data, ext->datalen);\n\n        newext2->extent_data.type = EXTENT_TYPE_REGULAR;\n        ned2 = (EXTENT_DATA2*)newext2->extent_data.data;\n        ned2->offset += start_data - ext->offset;\n        ned2->num_bytes = ext->offset + ed2->num_bytes - start_data;\n\n        Status = write_data_complete(fcb->Vcb, ed2->address + ned2->offset, data, (uint32_t)ned2->num_bytes, Irp, NULL, file_write, irp_offset, priority);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"write_data_complete returned %08lx\\n\", Status);\n            ExFreePool(newext1);\n            ExFreePool(newext2);\n            return Status;\n        }\n\n        if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) {\n            ULONG sl = (ULONG)(ned2->num_bytes >> fcb->Vcb->sector_shift);\n            void* csum = ExAllocatePoolWithTag(PagedPool, sl * fcb->Vcb->csum_size, ALLOC_TAG);\n\n            if (!csum) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(newext1);\n                ExFreePool(newext2);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            do_calc_job(fcb->Vcb, data, sl, csum);\n\n            newext2->csum = csum;\n        } else\n            newext2->csum = NULL;\n\n        *written = ned2->num_bytes;\n\n        newext1->offset = ext->offset;\n        newext1->datalen = ext->datalen;\n        newext1->unique = ext->unique;\n        newext1->ignore = false;\n        newext1->inserted = true;\n        newext1->csum = NULL;\n        InsertHeadList(&ext->list_entry, &newext1->list_entry);\n\n        add_insert_extent_rollback(rollback, fcb, newext1);\n\n        newext2->offset = start_data;\n        newext2->datalen = ext->datalen;\n        newext2->unique = ext->unique;\n        newext2->ignore = false;\n        newext2->inserted = true;\n        add_extent(fcb, &newext1->list_entry, newext2);\n\n        add_insert_extent_rollback(rollback, fcb, newext2);\n\n        c = get_chunk_from_address(fcb->Vcb, ed2->address);\n\n        if (!c)\n            ERR(\"get_chunk_from_address(%I64x) failed\\n\", ed2->address);\n        else {\n            Status = update_changed_extent_ref(fcb->Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, 1,\n                                               fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        remove_fcb_extent(fcb, ext, rollback);\n    } else if (start_data > ext->offset && end_data < ext->offset + ed2->num_bytes) { // replace middle\n        EXTENT_DATA2* ned2;\n        extent *newext1, *newext2, *newext3;\n\n        newext1 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG);\n        if (!newext1) {\n            ERR(\"out of memory\\n\");\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        newext2 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG);\n        if (!newext2) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(newext1);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        newext3 = ExAllocatePoolWithTag(PagedPool, offsetof(extent, extent_data) + ext->datalen, ALLOC_TAG);\n        if (!newext3) {\n            ERR(\"out of memory\\n\");\n            ExFreePool(newext1);\n            ExFreePool(newext2);\n            return STATUS_INSUFFICIENT_RESOURCES;\n        }\n\n        RtlCopyMemory(&newext1->extent_data, &ext->extent_data, ext->datalen);\n        RtlCopyMemory(&newext2->extent_data, &ext->extent_data, ext->datalen);\n        RtlCopyMemory(&newext3->extent_data, &ext->extent_data, ext->datalen);\n\n        ned2 = (EXTENT_DATA2*)newext1->extent_data.data;\n        ned2->num_bytes = start_data - ext->offset;\n\n        newext2->extent_data.type = EXTENT_TYPE_REGULAR;\n        ned2 = (EXTENT_DATA2*)newext2->extent_data.data;\n        ned2->offset += start_data - ext->offset;\n        ned2->num_bytes = end_data - start_data;\n\n        ned2 = (EXTENT_DATA2*)newext3->extent_data.data;\n        ned2->offset += end_data - ext->offset;\n        ned2->num_bytes -= end_data - ext->offset;\n\n        ned2 = (EXTENT_DATA2*)newext2->extent_data.data;\n        Status = write_data_complete(fcb->Vcb, ed2->address + ned2->offset, data, (uint32_t)(end_data - start_data), Irp, NULL, file_write, irp_offset, priority);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"write_data_complete returned %08lx\\n\", Status);\n            ExFreePool(newext1);\n            ExFreePool(newext2);\n            ExFreePool(newext3);\n            return Status;\n        }\n\n        if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) {\n            ULONG sl = (ULONG)((end_data - start_data) >> fcb->Vcb->sector_shift);\n            void* csum = ExAllocatePoolWithTag(PagedPool, sl * fcb->Vcb->csum_size, ALLOC_TAG);\n\n            if (!csum) {\n                ERR(\"out of memory\\n\");\n                ExFreePool(newext1);\n                ExFreePool(newext2);\n                ExFreePool(newext3);\n                return STATUS_INSUFFICIENT_RESOURCES;\n            }\n\n            do_calc_job(fcb->Vcb, data, sl, csum);\n\n            newext2->csum = csum;\n        } else\n            newext2->csum = NULL;\n\n        *written = end_data - start_data;\n\n        newext1->offset = ext->offset;\n        newext1->datalen = ext->datalen;\n        newext1->unique = ext->unique;\n        newext1->ignore = false;\n        newext1->inserted = true;\n        newext1->csum = NULL;\n        InsertHeadList(&ext->list_entry, &newext1->list_entry);\n\n        add_insert_extent_rollback(rollback, fcb, newext1);\n\n        newext2->offset = start_data;\n        newext2->datalen = ext->datalen;\n        newext2->unique = ext->unique;\n        newext2->ignore = false;\n        newext2->inserted = true;\n        add_extent(fcb, &newext1->list_entry, newext2);\n\n        add_insert_extent_rollback(rollback, fcb, newext2);\n\n        newext3->offset = end_data;\n        newext3->datalen = ext->datalen;\n        newext3->unique = ext->unique;\n        newext3->ignore = false;\n        newext3->inserted = true;\n        newext3->csum = NULL;\n        add_extent(fcb, &newext2->list_entry, newext3);\n\n        add_insert_extent_rollback(rollback, fcb, newext3);\n\n        c = get_chunk_from_address(fcb->Vcb, ed2->address);\n\n        if (!c)\n            ERR(\"get_chunk_from_address(%I64x) failed\\n\", ed2->address);\n        else {\n            Status = update_changed_extent_ref(fcb->Vcb, c, ed2->address, ed2->size, fcb->subvol->id, fcb->inode, ext->offset - ed2->offset, 2,\n                                               fcb->inode_item.flags & BTRFS_INODE_NODATASUM, false, Irp);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"update_changed_extent_ref returned %08lx\\n\", Status);\n                return Status;\n            }\n        }\n\n        remove_fcb_extent(fcb, ext, rollback);\n    }\n\n    if (c)\n        c->changed = true;\n\n    return STATUS_SUCCESS;\n}\n\n__attribute__((nonnull(1, 4)))\nNTSTATUS do_write_file(fcb* fcb, uint64_t start, uint64_t end_data, void* data, PIRP Irp, bool file_write, uint32_t irp_offset, LIST_ENTRY* rollback) {\n    NTSTATUS Status;\n    LIST_ENTRY *le, *le2;\n    uint64_t written = 0, length = end_data - start;\n    uint64_t last_cow_start;\n    ULONG priority = fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? HighPagePriority : NormalPagePriority;\n#ifdef DEBUG_PARANOID\n    uint64_t last_off;\n#endif\n    bool extents_changed = false;\n\n    last_cow_start = 0;\n\n    le = fcb->extents.Flink;\n    while (le != &fcb->extents) {\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        le2 = le->Flink;\n\n        if (!ext->ignore) {\n            EXTENT_DATA* ed = &ext->extent_data;\n            uint64_t len;\n\n            if (ed->type == EXTENT_TYPE_INLINE)\n                len = ed->decoded_size;\n            else\n                len = ((EXTENT_DATA2*)ed->data)->num_bytes;\n\n            if (ext->offset + len <= start)\n                goto nextitem;\n\n            if (ext->offset > start + written + length)\n                break;\n\n            if ((fcb->inode_item.flags & BTRFS_INODE_NODATACOW || ed->type == EXTENT_TYPE_PREALLOC) && ext->unique && ed->compression == BTRFS_COMPRESSION_NONE) {\n                if (max(last_cow_start, start + written) < ext->offset) {\n                    uint64_t start_write = max(last_cow_start, start + written);\n\n                    extents_changed = true;\n\n                    Status = excise_extents(fcb->Vcb, fcb, start_write, ext->offset, Irp, rollback);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"excise_extents returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    Status = insert_extent(fcb->Vcb, fcb, start_write, ext->offset - start_write, (uint8_t*)data + written, Irp, file_write, irp_offset + written, rollback);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"insert_extent returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    written += ext->offset - start_write;\n                    length -= ext->offset - start_write;\n\n                    if (length == 0)\n                        break;\n                }\n\n                if (ed->type == EXTENT_TYPE_REGULAR) {\n                    EXTENT_DATA2* ed2 = (EXTENT_DATA2*)ed->data;\n                    uint64_t writeaddr = ed2->address + ed2->offset + start + written - ext->offset;\n                    uint64_t write_len = min(len, length);\n                    chunk* c;\n\n                    TRACE(\"doing non-COW write to %I64x\\n\", writeaddr);\n\n                    Status = write_data_complete(fcb->Vcb, writeaddr, (uint8_t*)data + written, (uint32_t)write_len, Irp, NULL, file_write, irp_offset + written, priority);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"write_data_complete returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    c = get_chunk_from_address(fcb->Vcb, writeaddr);\n                    if (c)\n                        c->changed = true;\n\n                    // This shouldn't ever get called - nocow files should always also be nosum.\n                    if (!(fcb->inode_item.flags & BTRFS_INODE_NODATASUM)) {\n                        do_calc_job(fcb->Vcb, (uint8_t*)data + written, (uint32_t)(write_len >> fcb->Vcb->sector_shift),\n                                    (uint8_t*)ext->csum + (((start + written - ext->offset) * fcb->Vcb->csum_size) >> fcb->Vcb->sector_shift));\n\n                        ext->inserted = true;\n                        extents_changed = true;\n                    }\n\n                    written += write_len;\n                    length -= write_len;\n\n                    if (length == 0)\n                        break;\n                } else if (ed->type == EXTENT_TYPE_PREALLOC) {\n                    uint64_t write_len;\n\n                    Status = do_write_file_prealloc(fcb, ext, start + written, end_data, (uint8_t*)data + written, &write_len,\n                                                    Irp, file_write, irp_offset + written, priority, rollback);\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"do_write_file_prealloc returned %08lx\\n\", Status);\n                        return Status;\n                    }\n\n                    extents_changed = true;\n\n                    written += write_len;\n                    length -= write_len;\n\n                    if (length == 0)\n                        break;\n                }\n\n                last_cow_start = ext->offset + len;\n            }\n        }\n\nnextitem:\n        le = le2;\n    }\n\n    if (length > 0) {\n        uint64_t start_write = max(last_cow_start, start + written);\n\n        extents_changed = true;\n\n        Status = excise_extents(fcb->Vcb, fcb, start_write, end_data, Irp, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"excise_extents returned %08lx\\n\", Status);\n            return Status;\n        }\n\n        Status = insert_extent(fcb->Vcb, fcb, start_write, end_data - start_write, (uint8_t*)data + written, Irp, file_write, irp_offset + written, rollback);\n        if (!NT_SUCCESS(Status)) {\n            ERR(\"insert_extent returned %08lx\\n\", Status);\n            return Status;\n        }\n    }\n\n#ifdef DEBUG_PARANOID\n    last_off = 0xffffffffffffffff;\n\n    le = fcb->extents.Flink;\n    while (le != &fcb->extents) {\n        extent* ext = CONTAINING_RECORD(le, extent, list_entry);\n\n        if (!ext->ignore) {\n            if (ext->offset == last_off) {\n                ERR(\"offset %I64x duplicated\\n\", ext->offset);\n                int3;\n            } else if (ext->offset < last_off && last_off != 0xffffffffffffffff) {\n                ERR(\"offsets out of order\\n\");\n                int3;\n            }\n\n            last_off = ext->offset;\n        }\n\n        le = le->Flink;\n    }\n#endif\n\n    if (extents_changed) {\n        fcb->extents_changed = true;\n        mark_fcb_dirty(fcb);\n    }\n\n    return STATUS_SUCCESS;\n}\n\n__attribute__((nonnull(1,2,4,5,11)))\nNTSTATUS write_file2(device_extension* Vcb, PIRP Irp, LARGE_INTEGER offset, void* buf, ULONG* length, bool paging_io, bool no_cache,\n                     bool wait, bool deferred_write, bool write_irp, LIST_ENTRY* rollback) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    EXTENT_DATA* ed2;\n    uint64_t off64, newlength, start_data, end_data;\n    uint32_t bufhead;\n    bool make_inline;\n    INODE_ITEM* origii;\n    bool changed_length = false;\n    NTSTATUS Status;\n    LARGE_INTEGER time;\n    BTRFS_TIME now;\n    fcb* fcb;\n    ccb* ccb;\n    file_ref* fileref;\n    bool paging_lock = false, acquired_fcb_lock = false, acquired_tree_lock = false, pagefile;\n    ULONG filter = 0;\n\n    TRACE(\"(%p, %p, %I64x, %p, %lx, %u, %u)\\n\", Vcb, FileObject, offset.QuadPart, buf, *length, paging_io, no_cache);\n\n    if (*length == 0) {\n        TRACE(\"returning success for zero-length write\\n\");\n        return STATUS_SUCCESS;\n    }\n\n    if (!FileObject) {\n        ERR(\"error - FileObject was NULL\\n\");\n        return STATUS_ACCESS_DENIED;\n    }\n\n    fcb = FileObject->FsContext;\n    ccb = FileObject->FsContext2;\n    fileref = ccb ? ccb->fileref : NULL;\n\n    if (!fcb->ads && fcb->type != BTRFS_TYPE_FILE && fcb->type != BTRFS_TYPE_SYMLINK) {\n        WARN(\"tried to write to something other than a file or symlink (inode %I64x, type %u, %p, %p)\\n\", fcb->inode, fcb->type, &fcb->type, fcb);\n        return STATUS_INVALID_DEVICE_REQUEST;\n    }\n\n    if (offset.LowPart == FILE_WRITE_TO_END_OF_FILE && offset.HighPart == -1)\n        offset = fcb->Header.FileSize;\n\n    off64 = offset.QuadPart;\n\n    TRACE(\"fcb->Header.Flags = %x\\n\", fcb->Header.Flags);\n\n    if (!no_cache && !CcCanIWrite(FileObject, *length, wait, deferred_write))\n        return STATUS_PENDING;\n\n    if (!wait && no_cache)\n        return STATUS_PENDING;\n\n    if (no_cache && !paging_io && FileObject->SectionObjectPointer->DataSectionObject) {\n        IO_STATUS_BLOCK iosb;\n\n        ExAcquireResourceExclusiveLite(fcb->Header.PagingIoResource, true);\n\n        CcFlushCache(FileObject->SectionObjectPointer, &offset, *length, &iosb);\n\n        if (!NT_SUCCESS(iosb.Status)) {\n            ExReleaseResourceLite(fcb->Header.PagingIoResource);\n            ERR(\"CcFlushCache returned %08lx\\n\", iosb.Status);\n            return iosb.Status;\n        }\n\n        paging_lock = true;\n\n        CcPurgeCacheSection(FileObject->SectionObjectPointer, &offset, *length, false);\n    }\n\n    if (paging_io) {\n        if (!ExAcquireResourceSharedLite(fcb->Header.PagingIoResource, wait)) {\n            Status = STATUS_PENDING;\n            goto end;\n        } else\n            paging_lock = true;\n    }\n\n    pagefile = fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE && paging_io;\n\n    if (!pagefile && !ExIsResourceAcquiredExclusiveLite(&Vcb->tree_lock)) {\n        if (!ExAcquireResourceSharedLite(&Vcb->tree_lock, wait)) {\n            Status = STATUS_PENDING;\n            goto end;\n        } else\n            acquired_tree_lock = true;\n    }\n\n    if (pagefile) {\n        if (!ExAcquireResourceSharedLite(fcb->Header.Resource, wait)) {\n            Status = STATUS_PENDING;\n            goto end;\n        } else\n            acquired_fcb_lock = true;\n    } else if (!ExIsResourceAcquiredExclusiveLite(fcb->Header.Resource)) {\n        if (!ExAcquireResourceExclusiveLite(fcb->Header.Resource, wait)) {\n            Status = STATUS_PENDING;\n            goto end;\n        } else\n            acquired_fcb_lock = true;\n    }\n\n    newlength = fcb->ads ? fcb->adsdata.Length : fcb->inode_item.st_size;\n\n    if (fcb->deleted)\n        newlength = 0;\n\n    TRACE(\"newlength = %I64x\\n\", newlength);\n\n    if (off64 + *length > newlength) {\n        if (paging_io) {\n            if (off64 >= newlength) {\n                TRACE(\"paging IO tried to write beyond end of file (file size = %I64x, offset = %I64x, length = %lx)\\n\", newlength, off64, *length);\n                TRACE(\"FileObject: AllocationSize = %I64x, FileSize = %I64x, ValidDataLength = %I64x\\n\",\n                    fcb->Header.AllocationSize.QuadPart, fcb->Header.FileSize.QuadPart, fcb->Header.ValidDataLength.QuadPart);\n                Irp->IoStatus.Information = 0;\n                Status = STATUS_SUCCESS;\n                goto end;\n            }\n\n            *length = (ULONG)(newlength - off64);\n        } else {\n            newlength = off64 + *length;\n            changed_length = true;\n\n            TRACE(\"extending length to %I64x\\n\", newlength);\n        }\n    }\n\n    if (fcb->ads)\n        make_inline = false;\n    else\n        make_inline = newlength <= fcb->Vcb->options.max_inline;\n\n    if (changed_length) {\n        if (newlength > (uint64_t)fcb->Header.AllocationSize.QuadPart) {\n            if (!acquired_tree_lock) {\n                // We need to acquire the tree lock if we don't have it already -\n                // we can't give an inline file proper extents at the same time as we're\n                // doing a flush.\n                if (!ExAcquireResourceSharedLite(&Vcb->tree_lock, wait)) {\n                    Status = STATUS_PENDING;\n                    goto end;\n                } else\n                    acquired_tree_lock = true;\n            }\n\n            Status = extend_file(fcb, fileref, newlength, false, Irp, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"extend_file returned %08lx\\n\", Status);\n                goto end;\n            }\n        } else if (!fcb->ads)\n            fcb->inode_item.st_size = newlength;\n\n        fcb->Header.FileSize.QuadPart = newlength;\n        fcb->Header.ValidDataLength.QuadPart = newlength;\n\n        TRACE(\"AllocationSize = %I64x\\n\", fcb->Header.AllocationSize.QuadPart);\n        TRACE(\"FileSize = %I64x\\n\", fcb->Header.FileSize.QuadPart);\n        TRACE(\"ValidDataLength = %I64x\\n\", fcb->Header.ValidDataLength.QuadPart);\n    }\n\n    if (!no_cache) {\n        Status = STATUS_SUCCESS;\n\n        try {\n            if (!FileObject->PrivateCacheMap || changed_length) {\n                CC_FILE_SIZES ccfs;\n\n                ccfs.AllocationSize = fcb->Header.AllocationSize;\n                ccfs.FileSize = fcb->Header.FileSize;\n                ccfs.ValidDataLength = fcb->Header.ValidDataLength;\n\n                if (!FileObject->PrivateCacheMap)\n                    init_file_cache(FileObject, &ccfs);\n\n                CcSetFileSizes(FileObject, &ccfs);\n            }\n\n            if (IrpSp->MinorFunction & IRP_MN_MDL) {\n                CcPrepareMdlWrite(FileObject, &offset, *length, &Irp->MdlAddress, &Irp->IoStatus);\n\n                Status = Irp->IoStatus.Status;\n                goto end;\n            } else {\n                /* We have to wait in CcCopyWrite - if we return STATUS_PENDING and add this to the work queue,\n                 * it can result in CcFlushCache being called before the job has run. See ifstest ReadWriteTest. */\n\n                if (fCcCopyWriteEx) {\n                    TRACE(\"CcCopyWriteEx(%p, %I64x, %lx, %u, %p, %p)\\n\", FileObject, off64, *length, true, buf, Irp->Tail.Overlay.Thread);\n                    if (!fCcCopyWriteEx(FileObject, &offset, *length, true, buf, Irp->Tail.Overlay.Thread)) {\n                        Status = STATUS_PENDING;\n                        goto end;\n                    }\n                    TRACE(\"CcCopyWriteEx finished\\n\");\n                } else {\n                    TRACE(\"CcCopyWrite(%p, %I64x, %lx, %u, %p)\\n\", FileObject, off64, *length, true, buf);\n                    if (!CcCopyWrite(FileObject, &offset, *length, true, buf)) {\n                        Status = STATUS_PENDING;\n                        goto end;\n                    }\n                    TRACE(\"CcCopyWrite finished\\n\");\n                }\n\n                Irp->IoStatus.Information = *length;\n            }\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n        }\n\n        if (changed_length) {\n            queue_notification_fcb(fcb->ads ? fileref->parent : fileref, fcb->ads ? FILE_NOTIFY_CHANGE_STREAM_SIZE : FILE_NOTIFY_CHANGE_SIZE,\n                                   fcb->ads ? FILE_ACTION_MODIFIED_STREAM : FILE_ACTION_MODIFIED, fcb->ads && fileref->dc ? &fileref->dc->name : NULL);\n        }\n\n        goto end;\n    }\n\n    if (fcb->ads) {\n        if (changed_length) {\n            char* data2;\n\n            if (newlength > fcb->adsmaxlen) {\n                ERR(\"error - xattr too long (%I64u > %lu)\\n\", newlength, fcb->adsmaxlen);\n                Status = STATUS_DISK_FULL;\n                goto end;\n            }\n\n            data2 = ExAllocatePoolWithTag(PagedPool, (ULONG)newlength, ALLOC_TAG);\n            if (!data2) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            if (fcb->adsdata.Buffer) {\n                RtlCopyMemory(data2, fcb->adsdata.Buffer, fcb->adsdata.Length);\n                ExFreePool(fcb->adsdata.Buffer);\n            }\n\n            if (newlength > fcb->adsdata.Length)\n                RtlZeroMemory(&data2[fcb->adsdata.Length], (ULONG)(newlength - fcb->adsdata.Length));\n\n\n            fcb->adsdata.Buffer = data2;\n            fcb->adsdata.Length = fcb->adsdata.MaximumLength = (USHORT)newlength;\n\n            fcb->Header.AllocationSize.QuadPart = newlength;\n            fcb->Header.FileSize.QuadPart = newlength;\n            fcb->Header.ValidDataLength.QuadPart = newlength;\n        }\n\n        if (*length > 0)\n            RtlCopyMemory(&fcb->adsdata.Buffer[off64], buf, *length);\n\n        fcb->Header.ValidDataLength.QuadPart = newlength;\n\n        mark_fcb_dirty(fcb);\n\n        if (fileref)\n            mark_fileref_dirty(fileref);\n    } else {\n        bool compress = write_fcb_compressed(fcb), no_buf = false;\n        uint8_t* data;\n\n        if (make_inline) {\n            start_data = 0;\n            end_data = sector_align(newlength, fcb->Vcb->superblock.sector_size);\n            bufhead = sizeof(EXTENT_DATA) - 1;\n        } else if (compress) {\n            start_data = off64 & ~(uint64_t)(COMPRESSED_EXTENT_SIZE - 1);\n            end_data = min(sector_align(off64 + *length, COMPRESSED_EXTENT_SIZE),\n                           sector_align(newlength, fcb->Vcb->superblock.sector_size));\n            bufhead = 0;\n        } else {\n            start_data = off64 & ~(uint64_t)(fcb->Vcb->superblock.sector_size - 1);\n            end_data = sector_align(off64 + *length, fcb->Vcb->superblock.sector_size);\n            bufhead = 0;\n        }\n\n        if (fcb_is_inline(fcb))\n            end_data = max(end_data, sector_align(fcb->inode_item.st_size, Vcb->superblock.sector_size));\n\n        fcb->Header.ValidDataLength.QuadPart = newlength;\n        TRACE(\"fcb %p FileSize = %I64x\\n\", fcb, fcb->Header.FileSize.QuadPart);\n\n        if (!make_inline && !compress && off64 == start_data && off64 + *length == end_data) {\n            data = buf;\n            no_buf = true;\n        } else {\n            data = ExAllocatePoolWithTag(PagedPool, (ULONG)(end_data - start_data + bufhead), ALLOC_TAG);\n            if (!data) {\n                ERR(\"out of memory\\n\");\n                Status = STATUS_INSUFFICIENT_RESOURCES;\n                goto end;\n            }\n\n            RtlZeroMemory(data + bufhead, (ULONG)(end_data - start_data));\n\n            TRACE(\"start_data = %I64x\\n\", start_data);\n            TRACE(\"end_data = %I64x\\n\", end_data);\n\n            if (off64 > start_data || off64 + *length < end_data) {\n                if (changed_length) {\n                    if (fcb->inode_item.st_size > start_data)\n                        Status = read_file(fcb, data + bufhead, start_data, fcb->inode_item.st_size - start_data, NULL, Irp);\n                    else\n                        Status = STATUS_SUCCESS;\n                } else\n                    Status = read_file(fcb, data + bufhead, start_data, end_data - start_data, NULL, Irp);\n\n                if (!NT_SUCCESS(Status)) {\n                    ERR(\"read_file returned %08lx\\n\", Status);\n                    ExFreePool(data);\n                    goto end;\n                }\n            }\n\n            RtlCopyMemory(data + bufhead + off64 - start_data, buf, *length);\n        }\n\n        if (make_inline) {\n            Status = excise_extents(fcb->Vcb, fcb, start_data, end_data, Irp, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"error - excise_extents returned %08lx\\n\", Status);\n                ExFreePool(data);\n                goto end;\n            }\n\n            ed2 = (EXTENT_DATA*)data;\n            ed2->generation = fcb->Vcb->superblock.generation;\n            ed2->decoded_size = newlength;\n            ed2->compression = BTRFS_COMPRESSION_NONE;\n            ed2->encryption = BTRFS_ENCRYPTION_NONE;\n            ed2->encoding = BTRFS_ENCODING_NONE;\n            ed2->type = EXTENT_TYPE_INLINE;\n\n            Status = add_extent_to_fcb(fcb, 0, ed2, (uint16_t)(offsetof(EXTENT_DATA, data[0]) + newlength), false, NULL, rollback);\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"add_extent_to_fcb returned %08lx\\n\", Status);\n                ExFreePool(data);\n                goto end;\n            }\n\n            fcb->inode_item.st_blocks += newlength;\n        } else if (compress) {\n            Status = write_compressed(fcb, start_data, end_data, data, Irp, rollback);\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"write_compressed returned %08lx\\n\", Status);\n                ExFreePool(data);\n                goto end;\n            }\n        } else {\n            if (write_irp && Irp->MdlAddress && no_buf) {\n                bool locked = Irp->MdlAddress->MdlFlags & (MDL_PAGES_LOCKED | MDL_PARTIAL);\n\n                if (!locked) {\n                    Status = STATUS_SUCCESS;\n\n                    try {\n                        MmProbeAndLockPages(Irp->MdlAddress, KernelMode, IoReadAccess);\n                    } except (EXCEPTION_EXECUTE_HANDLER) {\n                        Status = GetExceptionCode();\n                    }\n\n                    if (!NT_SUCCESS(Status)) {\n                        ERR(\"MmProbeAndLockPages threw exception %08lx\\n\", Status);\n                        goto end;\n                    }\n                }\n\n                try {\n                    Status = do_write_file(fcb, start_data, end_data, data, Irp, true, 0, rollback);\n                } except (EXCEPTION_EXECUTE_HANDLER) {\n                    Status = GetExceptionCode();\n                }\n\n                if (!locked)\n                    MmUnlockPages(Irp->MdlAddress);\n            } else {\n                try {\n                    Status = do_write_file(fcb, start_data, end_data, data, Irp, false, 0, rollback);\n                } except (EXCEPTION_EXECUTE_HANDLER) {\n                    Status = GetExceptionCode();\n                }\n            }\n\n            if (!NT_SUCCESS(Status)) {\n                ERR(\"do_write_file returned %08lx\\n\", Status);\n                if (!no_buf) ExFreePool(data);\n                goto end;\n            }\n        }\n\n        if (!no_buf)\n            ExFreePool(data);\n    }\n\n    KeQuerySystemTime(&time);\n    win_time_to_unix(time, &now);\n\n    if (!pagefile) {\n        if (fcb->ads) {\n            if (fileref && fileref->parent)\n                origii = &fileref->parent->fcb->inode_item;\n            else {\n                ERR(\"no parent fcb found for stream\\n\");\n                Status = STATUS_INTERNAL_ERROR;\n                goto end;\n            }\n        } else\n            origii = &fcb->inode_item;\n\n        origii->transid = Vcb->superblock.generation;\n        origii->sequence++;\n\n        if (!ccb->user_set_change_time)\n            origii->st_ctime = now;\n\n        if (!fcb->ads) {\n            if (changed_length) {\n                TRACE(\"setting st_size to %I64x\\n\", newlength);\n                origii->st_size = newlength;\n                filter |= FILE_NOTIFY_CHANGE_SIZE;\n            }\n\n            fcb->inode_item_changed = true;\n        } else {\n            fileref->parent->fcb->inode_item_changed = true;\n\n            if (changed_length)\n                filter |= FILE_NOTIFY_CHANGE_STREAM_SIZE;\n\n            filter |= FILE_NOTIFY_CHANGE_STREAM_WRITE;\n        }\n\n        if (!ccb->user_set_write_time) {\n            origii->st_mtime = now;\n            filter |= FILE_NOTIFY_CHANGE_LAST_WRITE;\n        }\n\n        mark_fcb_dirty(fcb->ads ? fileref->parent->fcb : fcb);\n    }\n\n    if (changed_length) {\n        CC_FILE_SIZES ccfs;\n\n        ccfs.AllocationSize = fcb->Header.AllocationSize;\n        ccfs.FileSize = fcb->Header.FileSize;\n        ccfs.ValidDataLength = fcb->Header.ValidDataLength;\n\n        try {\n            CcSetFileSizes(FileObject, &ccfs);\n        } except (EXCEPTION_EXECUTE_HANDLER) {\n            Status = GetExceptionCode();\n            goto end;\n        }\n    }\n\n    fcb->subvol->root_item.ctransid = Vcb->superblock.generation;\n    fcb->subvol->root_item.ctime = now;\n\n    Status = STATUS_SUCCESS;\n    Irp->IoStatus.Information = *length;\n\n    if (filter != 0)\n        queue_notification_fcb(fcb->ads ? fileref->parent : fileref, filter, fcb->ads ? FILE_ACTION_MODIFIED_STREAM : FILE_ACTION_MODIFIED,\n                               fcb->ads && fileref->dc ? &fileref->dc->name : NULL);\n\nend:\n    if (NT_SUCCESS(Status) && FileObject->Flags & FO_SYNCHRONOUS_IO && !paging_io) {\n        TRACE(\"CurrentByteOffset was: %I64x\\n\", FileObject->CurrentByteOffset.QuadPart);\n        FileObject->CurrentByteOffset.QuadPart = offset.QuadPart + (NT_SUCCESS(Status) ? *length : 0);\n        TRACE(\"CurrentByteOffset now: %I64x\\n\", FileObject->CurrentByteOffset.QuadPart);\n    }\n\n    if (acquired_fcb_lock)\n        ExReleaseResourceLite(fcb->Header.Resource);\n\n    if (acquired_tree_lock)\n        ExReleaseResourceLite(&Vcb->tree_lock);\n\n    if (paging_lock)\n        ExReleaseResourceLite(fcb->Header.PagingIoResource);\n\n    return Status;\n}\n\n__attribute__((nonnull(1,2)))\nNTSTATUS write_file(device_extension* Vcb, PIRP Irp, bool wait, bool deferred_write) {\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    void* buf;\n    NTSTATUS Status;\n    LARGE_INTEGER offset = IrpSp->Parameters.Write.ByteOffset;\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    fcb* fcb = FileObject ? FileObject->FsContext : NULL;\n    LIST_ENTRY rollback;\n\n    InitializeListHead(&rollback);\n\n    TRACE(\"write\\n\");\n\n    Irp->IoStatus.Information = 0;\n\n    TRACE(\"offset = %I64x\\n\", offset.QuadPart);\n    TRACE(\"length = %lx\\n\", IrpSp->Parameters.Write.Length);\n\n    if (!Irp->AssociatedIrp.SystemBuffer) {\n        buf = map_user_buffer(Irp, fcb && fcb->Header.Flags2 & FSRTL_FLAG2_IS_PAGING_FILE ? HighPagePriority : NormalPagePriority);\n\n        if (Irp->MdlAddress && !buf) {\n            ERR(\"MmGetSystemAddressForMdlSafe returned NULL\\n\");\n            Status = STATUS_INSUFFICIENT_RESOURCES;\n            goto exit;\n        }\n    } else\n        buf = Irp->AssociatedIrp.SystemBuffer;\n\n    TRACE(\"buf = %p\\n\", buf);\n\n    if (fcb && !(Irp->Flags & IRP_PAGING_IO) && !FsRtlCheckLockForWriteAccess(&fcb->lock, Irp)) {\n        WARN(\"tried to write to locked region\\n\");\n        Status = STATUS_FILE_LOCK_CONFLICT;\n        goto exit;\n    }\n\n    Status = write_file2(Vcb, Irp, offset, buf, &IrpSp->Parameters.Write.Length, Irp->Flags & IRP_PAGING_IO, Irp->Flags & IRP_NOCACHE,\n                         wait, deferred_write, true, &rollback);\n\n    if (Status == STATUS_PENDING)\n        goto exit;\n    else if (!NT_SUCCESS(Status)) {\n        ERR(\"write_file2 returned %08lx\\n\", Status);\n        goto exit;\n    }\n\n    if (NT_SUCCESS(Status)) {\n        if (diskacc && Status != STATUS_PENDING && Irp->Flags & IRP_NOCACHE) {\n            PETHREAD thread = NULL;\n\n            if (Irp->Tail.Overlay.Thread && !IoIsSystemThread(Irp->Tail.Overlay.Thread))\n                thread = Irp->Tail.Overlay.Thread;\n            else if (!IoIsSystemThread(PsGetCurrentThread()))\n                thread = PsGetCurrentThread();\n            else if (IoIsSystemThread(PsGetCurrentThread()) && IoGetTopLevelIrp() == Irp)\n                thread = PsGetCurrentThread();\n\n            if (thread)\n                fPsUpdateDiskCounters(PsGetThreadProcess(thread), 0, IrpSp->Parameters.Write.Length, 0, 1, 0);\n        }\n    }\n\nexit:\n    if (NT_SUCCESS(Status))\n        clear_rollback(&rollback);\n    else\n        do_rollback(Vcb, &rollback);\n\n    return Status;\n}\n\n_Dispatch_type_(IRP_MJ_WRITE)\n_Function_class_(DRIVER_DISPATCH)\n__attribute__((nonnull(1,2)))\nNTSTATUS __stdcall drv_write(IN PDEVICE_OBJECT DeviceObject, IN PIRP Irp) {\n    NTSTATUS Status;\n    bool top_level;\n    PIO_STACK_LOCATION IrpSp = IoGetCurrentIrpStackLocation(Irp);\n    device_extension* Vcb = DeviceObject->DeviceExtension;\n    PFILE_OBJECT FileObject = IrpSp->FileObject;\n    fcb* fcb = FileObject ? FileObject->FsContext : NULL;\n    ccb* ccb = FileObject ? FileObject->FsContext2 : NULL;\n    bool wait = FileObject ? IoIsOperationSynchronous(Irp) : true;\n\n    FsRtlEnterFileSystem();\n\n    top_level = is_top_level(Irp);\n\n    if (Vcb && Vcb->type == VCB_TYPE_VOLUME) {\n        Status = vol_write(DeviceObject, Irp);\n        goto exit;\n    } else if (!Vcb || Vcb->type != VCB_TYPE_FS) {\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (!fcb) {\n        ERR(\"fcb was NULL\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (!ccb) {\n        ERR(\"ccb was NULL\\n\");\n        Status = STATUS_INVALID_PARAMETER;\n        goto end;\n    }\n\n    if (Irp->RequestorMode == UserMode && !(ccb->access & (FILE_WRITE_DATA | FILE_APPEND_DATA))) {\n        WARN(\"insufficient permissions\\n\");\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    if (fcb == Vcb->volume_fcb) {\n        if (!Vcb->locked || Vcb->locked_fileobj != FileObject) {\n            ERR(\"trying to write to volume when not locked, or locked with another FileObject\\n\");\n            Status = STATUS_ACCESS_DENIED;\n            goto end;\n        }\n\n        TRACE(\"writing directly to volume\\n\");\n\n        IoSkipCurrentIrpStackLocation(Irp);\n\n        Status = IoCallDriver(Vcb->Vpb->RealDevice, Irp);\n        goto exit;\n    }\n\n    if (is_subvol_readonly(fcb->subvol, Irp)) {\n        Status = STATUS_ACCESS_DENIED;\n        goto end;\n    }\n\n    if (Vcb->readonly) {\n        Status = STATUS_MEDIA_WRITE_PROTECTED;\n        goto end;\n    }\n\n    try {\n        if (IrpSp->MinorFunction & IRP_MN_COMPLETE) {\n            CcMdlWriteComplete(IrpSp->FileObject, &IrpSp->Parameters.Write.ByteOffset, Irp->MdlAddress);\n\n            Irp->MdlAddress = NULL;\n            Status = STATUS_SUCCESS;\n        } else {\n            if (!(Irp->Flags & IRP_PAGING_IO))\n                FsRtlCheckOplock(fcb_oplock(fcb), Irp, NULL, NULL, NULL);\n\n            // Don't offload jobs when doing paging IO - otherwise this can lead to\n            // deadlocks in CcCopyWrite.\n            if (Irp->Flags & IRP_PAGING_IO)\n                wait = true;\n\n            Status = write_file(Vcb, Irp, wait, false);\n        }\n    } except (EXCEPTION_EXECUTE_HANDLER) {\n        Status = GetExceptionCode();\n    }\n\nend:\n    Irp->IoStatus.Status = Status;\n\n    TRACE(\"wrote %Iu bytes\\n\", Irp->IoStatus.Information);\n\n    if (Status != STATUS_PENDING)\n        IoCompleteRequest(Irp, IO_NO_INCREMENT);\n    else {\n        IoMarkIrpPending(Irp);\n\n        if (!add_thread_job(Vcb, Irp))\n            Status = do_write_job(Vcb, Irp);\n    }\n\nexit:\n    if (top_level)\n        IoSetTopLevelIrp(NULL);\n\n    TRACE(\"returning %08lx\\n\", Status);\n\n    FsRtlExitFileSystem();\n\n    return Status;\n}\n"
  },
  {
    "path": "src/xor-gas.S",
    "content": "/* Copyright (c) Mark Harmstone 2020\n *\n * This file is part of WinBtrfs.\n *\n * WinBtrfs is free software: you can redistribute it and/or modify\n * it under the terms of the GNU Lesser General Public Licence as published by\n * the Free Software Foundation, either version 3 of the Licence, or\n * (at your option) any later version.\n *\n * WinBtrfs is distributed in the hope that it will be useful,\n * but WITHOUT ANY WARRANTY; without even the implied warranty of\n * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n * GNU Lesser General Public Licence for more details.\n *\n * You should have received a copy of the GNU Lesser General Public Licence\n * along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>. */\n\n.intel_syntax noprefix\n\n#ifdef __x86_64__\n\n.global do_xor_sse2\n\n/* void do_xor_sse2(uint8_t* buf1, uint8_t* buf2, uint32_t len); */\ndo_xor_sse2:\n    /* rcx = buf1\n    *  rdx = buf2\n    *  r8d = len\n    *  rax = tmp1\n    *  r9 = tmp2\n    *  xmm0 = tmp3\n    *  xmm1 = tmp4 */\n\n    mov rax, rcx\n    and rax, 15\n    cmp rax, 0\n    jne stragglers2\n\n    mov rax, rdx\n    and rax, 15\n    cmp rax, 0\n    jne stragglers2\n\ndo_xor_sse2_loop:\n    cmp r8d, 16\n    jl stragglers2\n\n    movdqa xmm0, [rcx]\n    movdqa xmm1, [rdx]\n    pxor xmm0, xmm1\n    movdqa [rcx], xmm0\n\n    add rcx, 16\n    add rdx, 16\n    sub r8d, 16\n\n    jmp do_xor_sse2_loop\n\nstragglers2:\n\n    cmp r8d, 8\n    jl stragglers\n\n    mov rax, [rcx]\n    mov r9, [rdx]\n    xor rax, r9\n    mov [rcx], rax\n\n    add rcx, 8\n    add rdx, 8\n    sub r8d, 8\n\n    jmp stragglers2\n\nstragglers:\n\n    cmp r8d, 0\n    je do_xor_sse2_end\n\n    mov al, [rcx]\n    mov r9b, [rdx]\n    xor al, r9b\n    mov [rcx], al\n\n    inc rcx\n    inc rdx\n    dec r8d\n\n    jmp stragglers\n\ndo_xor_sse2_end:\n    ret\n\n.global do_xor_avx2\n\n/* void do_xor_avx2(uint8_t* buf1, uint8_t* buf2, uint32_t len); */\ndo_xor_avx2:\n    /* rcx = buf1\n    *  rdx = buf2\n    *  r8d = len\n    *  rax = tmp1\n    *  r9 = tmp2\n    *  xmm0 = tmp3\n    *  xmm1 = tmp4 */\n\n    mov rax, rcx\n    and rax, 31\n    cmp rax, 0\n    jne stragglers4\n\n    mov rax, rdx\n    and rax, 31\n    cmp rax, 0\n    jne stragglers4\n\ndo_xor_avx2_loop:\n    cmp r8d, 32\n    jl stragglers4\n\n    vmovdqa ymm0, [rcx]\n    vmovdqa ymm1, [rdx]\n    vpxor ymm0, ymm0, ymm1\n    vmovdqa [rcx], ymm0\n\n    add rcx, 32\n    add rdx, 32\n    sub r8d, 32\n\n    jmp do_xor_avx2_loop\n\nstragglers4:\n\n    cmp r8d, 8\n    jl stragglers3\n\n    mov rax, [rcx]\n    mov r9, [rdx]\n    xor rax, r9\n    mov [rcx], rax\n\n    add rcx, 8\n    add rdx, 8\n    sub r8d, 8\n\n    jmp stragglers4\n\nstragglers3:\n\n    cmp r8d, 0\n    je do_xor_avx2_end\n\n    mov al, [rcx]\n    mov r9b, [rdx]\n    xor al, r9b\n    mov [rcx], al\n\n    inc rcx\n    inc rdx\n    dec r8d\n\n    jmp stragglers3\n\ndo_xor_avx2_end:\n    ret\n\n#else\n\n.global _do_xor_sse2@12\n\n/* void __stdcall do_xor_sse2(uint8_t* buf1, uint8_t* buf2, uint32_t len); */\n_do_xor_sse2@12:\n    /* edi = buf1\n    *  edx = buf2\n    *  esi = len\n    *  eax = tmp1\n    *  ecx = tmp2\n    *  xmm0 = tmp3\n    *  xmm1 = tmp4 */\n\n    push ebp\n    mov ebp, esp\n\n    push esi\n    push edi\n\n    mov edi, [ebp+8]\n    mov edx, [ebp+12]\n    mov esi, [ebp+16]\n\n    mov eax, edi\n    and eax, 15\n    cmp eax, 0\n    jne stragglers2\n\n    mov eax, edx\n    and eax, 15\n    cmp eax, 0\n    jne stragglers2\n\ndo_xor_sse2_loop:\n    cmp esi, 16\n    jl stragglers2\n\n    movdqa xmm0, [edi]\n    movdqa xmm1, [edx]\n    pxor xmm0, xmm1\n    movdqa [edi], xmm0\n\n    add edi, 16\n    add edx, 16\n    sub esi, 16\n\n    jmp do_xor_sse2_loop\n\nstragglers2:\n\n    cmp esi, 4\n    jl stragglers\n\n    mov eax, [edi]\n    mov ecx, [edx]\n    xor eax, ecx\n    mov [edi], eax\n\n    add edi, 4\n    add edx, 4\n    sub esi, 4\n\n    jmp stragglers2\n\nstragglers:\n\n    cmp esi, 0\n    je do_xor_sse2_end\n\n    mov al, [edi]\n    mov cl, [edx]\n    xor al, cl\n    mov [edi], al\n\n    inc edi\n    inc edx\n    dec esi\n\n    jmp stragglers\n\ndo_xor_sse2_end:\n    pop edi\n    pop esi\n    pop ebp\n\n    ret 12\n\n.global _do_xor_avx2@12\n\n/* void __stdcall do_xor_avx2(uint8_t* buf1, uint8_t* buf2, uint32_t len); */\n_do_xor_avx2@12:\n    /* edi = buf1\n    *  edx = buf2\n    *  esi = len\n    *  eax = tmp1\n    *  ecx = tmp2\n    *  xmm0 = tmp3\n    *  xmm1 = tmp4 */\n\n    push ebp\n    mov ebp, esp\n\n    push esi\n    push edi\n\n    mov edi, [ebp+8]\n    mov edx, [ebp+12]\n    mov esi, [ebp+16]\n\n    mov eax, edi\n    and eax, 31\n    cmp eax, 0\n    jne stragglers4\n\n    mov eax, edx\n    and eax, 31\n    cmp eax, 0\n    jne stragglers4\n\ndo_xor_avx2_loop:\n    cmp esi, 32\n    jl stragglers4\n\n    vmovdqa ymm0, [edi]\n    vmovdqa ymm1, [edx]\n    vpxor ymm0, ymm0, ymm1\n    vmovdqa [edi], ymm0\n\n    add edi, 32\n    add edx, 32\n    sub esi, 32\n\n    jmp do_xor_avx2_loop\n\nstragglers4:\n\n    cmp esi, 4\n    jl stragglers3\n\n    mov eax, [edi]\n    mov ecx, [edx]\n    xor eax, ecx\n    mov [edi], eax\n\n    add edi, 4\n    add edx, 4\n    sub esi, 4\n\n    jmp stragglers4\n\nstragglers3:\n\n    cmp esi, 0\n    je do_xor_avx2_end\n\n    mov al, [edi]\n    mov cl, [edx]\n    xor al, cl\n    mov [edi], al\n\n    inc edi\n    inc edx\n    dec esi\n\n    jmp stragglers3\n\ndo_xor_avx2_end:\n    pop edi\n    pop esi\n    pop ebp\n\n    ret 12\n\n#endif\n"
  },
  {
    "path": "src/xor-masm.asm",
    "content": "; Copyright (c) Mark Harmstone 2020\n;\n; This file is part of WinBtrfs.\n;\n; WinBtrfs is free software: you can redistribute it and/or modify\n; it under the terms of the GNU Lesser General Public Licence as published by\n; the Free Software Foundation, either version 3 of the Licence, or\n; (at your option) any later version.\n;\n; WinBtrfs is distributed in the hope that it will be useful,\n; but WITHOUT ANY WARRANTY; without even the implied warranty of\n; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n; GNU Lesser General Public Licence for more details.\n;\n; You should have received a copy of the GNU Lesser General Public Licence\n; along with WinBtrfs.  If not, see <http://www.gnu.org/licenses/>.\n\nIFDEF RAX\nELSE\n.686P\n.xmm\nENDIF\n\n_TEXT  SEGMENT\n\nIFDEF RAX\n\nPUBLIC do_xor_sse2\n\n; void do_xor_sse2(uint8_t* buf1, uint8_t* buf2, uint32_t len);\ndo_xor_sse2:\n    ; rcx = buf1\n    ; rdx = buf2\n    ; r8d = len\n    ; rax = tmp1\n    ; r9 = tmp2\n    ; xmm0 = tmp3\n    ; xmm1 = tmp4\n\n    mov rax, rcx\n    and rax, 15\n    cmp rax, 0\n    jne stragglers2\n\n    mov rax, rdx\n    and rax, 15\n    cmp rax, 0\n    jne stragglers2\n\ndo_xor_sse2_loop:\n    cmp r8d, 16\n    jl stragglers2\n\n    movdqa xmm0, [rcx]\n    movdqa xmm1, [rdx]\n    pxor xmm0, xmm1\n    movdqa [rcx], xmm0\n\n    add rcx, 16\n    add rdx, 16\n    sub r8d, 16\n\n    jmp do_xor_sse2_loop\n\nstragglers2:\n\n    cmp r8d, 8\n    jl stragglers\n\n    mov rax, [rcx]\n    mov r9, [rdx]\n    xor rax, r9\n    mov [rcx], rax\n\n    add rcx, 8\n    add rdx, 8\n    sub r8d, 8\n\n    jmp stragglers2\n\nstragglers:\n\n    cmp r8d, 0\n    je do_xor_sse2_end\n\n    mov al, [rcx]\n    mov r9b, [rdx]\n    xor al, r9b\n    mov [rcx], al\n\n    inc rcx\n    inc rdx\n    dec r8d\n\n    jmp stragglers\n\ndo_xor_sse2_end:\n    ret\n\nPUBLIC do_xor_avx2\n\n; void do_xor_avx2(uint8_t* buf1, uint8_t* buf2, uint32_t len);\ndo_xor_avx2:\n    ; rcx = buf1\n    ; rdx = buf2\n    ; r8d = len\n    ; rax = tmp1\n    ; r9 = tmp2\n    ; xmm0 = tmp3\n    ; xmm1 = tmp4\n\n    mov rax, rcx\n    and rax, 31\n    cmp rax, 0\n    jne stragglers4\n\n    mov rax, rdx\n    and rax, 31\n    cmp rax, 0\n    jne stragglers4\n\ndo_xor_avx2_loop:\n    cmp r8d, 32\n    jl stragglers4\n\n    vmovdqa ymm0, YMMWORD PTR[rcx]\n    vmovdqa ymm1, YMMWORD PTR[rdx]\n    vpxor ymm0, ymm0, ymm1\n    vmovdqa YMMWORD PTR[rcx], ymm0\n\n    add rcx, 32\n    add rdx, 32\n    sub r8d, 32\n\n    jmp do_xor_avx2_loop\n\nstragglers4:\n\n    cmp r8d, 8\n    jl stragglers3\n\n    mov rax, [rcx]\n    mov r9, [rdx]\n    xor rax, r9\n    mov [rcx], rax\n\n    add rcx, 8\n    add rdx, 8\n    sub r8d, 8\n\n    jmp stragglers4\n\nstragglers3:\n\n    cmp r8d, 0\n    je do_xor_avx2_end\n\n    mov al, [rcx]\n    mov r9b, [rdx]\n    xor al, r9b\n    mov [rcx], al\n\n    inc rcx\n    inc rdx\n    dec r8d\n\n    jmp stragglers3\n\ndo_xor_avx2_end:\n    ret\n\nELSE\n\nPUBLIC do_xor_sse2@12\n\n; void __stdcall do_xor_sse2(uint8_t* buf1, uint8_t* buf2, uint32_t len);\ndo_xor_sse2@12:\n    ; edi = buf1\n    ; edx = buf2\n    ; esi = len\n    ; eax = tmp1\n    ; ecx = tmp2\n    ; xmm0 = tmp3\n    ; xmm1 = tmp4\n\n    push ebp\n    mov ebp, esp\n\n    push esi\n    push edi\n\n    mov edi, [ebp+8]\n    mov edx, [ebp+12]\n    mov esi, [ebp+16]\n\n    mov eax, edi\n    and eax, 15\n    cmp eax, 0\n    jne stragglers2\n\n    mov eax, edx\n    and eax, 15\n    cmp eax, 0\n    jne stragglers2\n\ndo_xor_sse2_loop:\n    cmp esi, 16\n    jl stragglers2\n\n    movdqa xmm0, [edi]\n    movdqa xmm1, [edx]\n    pxor xmm0, xmm1\n    movdqa [edi], xmm0\n\n    add edi, 16\n    add edx, 16\n    sub esi, 16\n\n    jmp do_xor_sse2_loop\n\nstragglers2:\n\n    cmp esi, 4\n    jl stragglers\n\n    mov eax, [edi]\n    mov ecx, [edx]\n    xor eax, ecx\n    mov [edi], eax\n\n    add edi, 4\n    add edx, 4\n    sub esi, 4\n\n    jmp stragglers2\n\nstragglers:\n\n    cmp esi, 0\n    je do_xor_sse2_end\n\n    mov al, [edi]\n    mov cl, [edx]\n    xor al, cl\n    mov [edi], al\n\n    inc edi\n    inc edx\n    dec esi\n\n    jmp stragglers\n\ndo_xor_sse2_end:\n    pop edi\n    pop esi\n    pop ebp\n\n    ret 12\n\nPUBLIC do_xor_avx2@12\n\n; void __stdcall do_xor_avx2(uint8_t* buf1, uint8_t* buf2, uint32_t len);\ndo_xor_avx2@12:\n    ; edi = buf1\n    ; edx = buf2\n    ; esi = len\n    ; eax = tmp1\n    ; ecx = tmp2\n    ; xmm0 = tmp3\n    ; xmm1 = tmp4\n\n    push ebp\n    mov ebp, esp\n\n    push esi\n    push edi\n\n    mov edi, [ebp+8]\n    mov edx, [ebp+12]\n    mov esi, [ebp+16]\n\n    mov eax, edi\n    and eax, 31\n    cmp eax, 0\n    jne stragglers4\n\n    mov eax, edx\n    and eax, 31\n    cmp eax, 0\n    jne stragglers4\n\ndo_xor_avx2_loop:\n    cmp esi, 32\n    jl stragglers4\n\n    vmovdqa ymm0, YMMWORD PTR[edi]\n    vmovdqa ymm1, YMMWORD PTR[edx]\n    vpxor ymm0, ymm0, ymm1\n    vmovdqa YMMWORD PTR[edi], ymm0\n\n    add edi, 32\n    add edx, 32\n    sub esi, 32\n\n    jmp do_xor_avx2_loop\n\nstragglers4:\n\n    cmp esi, 4\n    jl stragglers3\n\n    mov eax, [edi]\n    mov ecx, [edx]\n    xor eax, ecx\n    mov [edi], eax\n\n    add edi, 4\n    add edx, 4\n    sub esi, 4\n\n    jmp stragglers4\n\nstragglers3:\n\n    cmp esi, 0\n    je do_xor_avx2_end\n\n    mov al, [edi]\n    mov cl, [edx]\n    xor al, cl\n    mov [edi], al\n\n    inc edi\n    inc edx\n    dec esi\n\n    jmp stragglers3\n\ndo_xor_avx2_end:\n    pop edi\n    pop esi\n    pop ebp\n\n    ret 12\n\nENDIF\n\n_TEXT  ENDS\n\nend\n"
  },
  {
    "path": "src/zstd-shim.h",
    "content": "#include <stddef.h>\n#include <stdint.h>\n\nstatic void* ZSTD_malloc(size_t size) {\n    return NULL;\n}\n\nstatic void* ZSTD_calloc(size_t nmemb, size_t size) {\n    return NULL;\n}\n\nstatic void ZSTD_free(void* ptr) {\n}\n\n#ifdef _MSC_VER\n\n#include <crt/intrin.h>\n\n#pragma intrinsic(_byteswap_uint64)\n#pragma intrinsic(_byteswap_ulong)\n#pragma intrinsic(_rotl)\n#pragma intrinsic(_rotl64)\n\n#endif\n"
  }
]